diff --git a/README.md b/README.md index 814c322..3150f1b 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ app.listen(3000) | ------------------ | --------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------- | | `ip` | `boolean` | Display the incoming IP address based on the `X-Forwarded-For` header | `false` | | `customLogMessage` | `string` | Custom log message to display | `🦊 {now} {level} {duration} {method} {pathname} {status} {message} {ip}` | +| `logFilter` | `object` | Filter the logs based on the level, method, and status | `null` | ## `📄` License diff --git a/example/basic.ts b/example/basic.ts index 3e5beb5..90beb64 100644 --- a/example/basic.ts +++ b/example/basic.ts @@ -9,7 +9,12 @@ const app = new Elysia({ config: { ip: true, customLogFormat: - '🦊 {now} {level} {duration} {method} {pathname} {status} {message} {ip}' + '🦊 {now} {level} {duration} {method} {pathname} {status} {message} {ip}', + logFilter: { + level: ['ERROR', 'WARNING'], + status: [500, 404], + method: 'GET' + } } }) ) diff --git a/src/logger.ts b/src/logger.ts index a127bfa..c1e3cbb 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -14,6 +14,55 @@ import { HttpError } from './types' +/** + * Filters log messages. + * + * @param {LogLevel} logLevel The log level. + * @param {number} status The status code. + * @param {string} method The method. + * @param {Options} options The options. + * @returns {boolean} `true` if the log message should be logged, otherwise `false`. + */ +function filterLog( + logLevel: LogLevel, + status: number, + method: string, + options?: Options +): boolean { + const filter = options?.config?.logFilter + + if (!filter) return true + + // Level + if (filter.level) { + if (Array.isArray(filter.level)) { + if (!filter.level.includes(logLevel)) return false + } + } else { + if (filter.level !== logLevel) return false + } + + // Status + if (filter.status) { + if (Array.isArray(filter.status)) { + if (!filter.status.includes(status)) return false + } + } else { + if (filter.status !== status) return false + } + + // Method + if (filter.method) { + if (Array.isArray(filter.method)) { + if (!filter.method.includes(method)) return false + } + } else { + if (filter.method !== method) return false + } + + return true +} + /** * Logs a message. * @@ -30,6 +79,10 @@ function log( store: StoreData, options?: Options ): void { + if (!filterLog(level, data.status || 200, request.method, options)) { + return + } + const logMessage = buildLogMessage(level, request, data, store, options) console.log(logMessage) } diff --git a/src/types.ts b/src/types.ts index ff09f33..82f15ab 100644 --- a/src/types.ts +++ b/src/types.ts @@ -48,6 +48,11 @@ interface Options { config?: { ip?: boolean customLogFormat?: string + logFilter?: { + level?: LogLevel | LogLevel[] + method?: string | string[] + status?: number | number[] + } | null } } diff --git a/test/logixlysia.test.ts b/test/logixlysia.test.ts index b949d14..80729d1 100644 --- a/test/logixlysia.test.ts +++ b/test/logixlysia.test.ts @@ -124,3 +124,106 @@ describe('Logixlysia with IP logging disabled', () => { expect(error).toBeInstanceOf(Error) }) }) + +describe('Logixlysia with log filtering enabled', () => { + let server: Elysia + let app: any + let logs: string[] = [] + + beforeAll(() => { + server = new Elysia() + .use( + logger({ + config: { + logFilter: { + level: 'INFO', + status: [200, 404], + method: 'GET' + } + } + }) + ) + .get('/', () => '🦊 Logixlysia Getting') + .post('logixlysia', () => '🦊 Logixlysia Posting') + .listen(3000) + + app = edenTreaty('http://127.0.0.1:3000') + }) + + beforeEach(() => { + logs = [] + }) + + it("Logs 'GET' requests with status 200 or 404 when log filtering criteria are met", async () => { + const requestCount = 5 + + for (let i = 0; i < requestCount; i++) { + logs.push((await app.get('/')).data) + } + + expect(logs.length).toBe(requestCount) + logs.forEach(log => { + expect(log).toMatch('🦊 Logixlysia Getting') + }) + }) + + it("Doesn't log 'POST' requests when log filtering criteria are not met", async () => { + const requestCount = 5 + + for (let i = 0; i < requestCount; i++) { + await app.post('/logixlysia', {}) + } + + expect(logs.length).toBe(0) + }) + + const otherMethods = ['PUT', 'DELETE', 'PATCH', 'HEAD'] // OPTIONS is failed (IDK why) + otherMethods.forEach(async method => { + it(`Logs '${method}' requests with status 200 or 404 when log filtering criteria are met`, async () => { + const requestCount = 5 + + for (let i = 0; i < requestCount; i++) { + logs.push((await app[method.toLowerCase()]('/')).data) + } + + expect(logs.length).toBe(requestCount) + }) + }) +}) + +describe('Logixlysia with log filtering disabled', () => { + let server: Elysia + let app: any + let logs: string[] = [] + + beforeAll(() => { + server = new Elysia() + .use( + logger({ + config: { + logFilter: null + } + }) + ) + .get('/', () => '🦊 Logixlysia Getting') + .post('logixlysia', () => '🦊 Logixlysia Posting') + .listen(3000) + + app = edenTreaty('http://127.0.0.1:3000') + }) + + beforeEach(() => { + logs = [] + }) + + it('Logs all requests when log filtering is disabled', async () => { + const requestCount = 5 + + for (let i = 0; i < requestCount; i++) { + logs.push((await app.get('/')).data) + logs.push((await app.post('/logixlysia', {})).data) + } + + expect(logs.length).toBe(requestCount * 2) + }) +})