Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit 638bbb1

Browse files
authored
Merge pull request #16 from AckeeCZ/add/options-skip
Add skip option
2 parents 86a621b + 5fc1ea3 commit 638bbb1

8 files changed

Lines changed: 159 additions & 70 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
### Added
1111
- coveralls integration
1212
- automatic logger name in non-pretty loggers
13+
- `options.skip` settings for custom log filtering in Express
1314

1415
## Changed
1516
- refactoring of express handlers

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,18 @@ app.use(logger.expressError)
113113

114114
All those log messages will contain request and possibly response, error, time from request to response, status code and `user-agent`, `x-deviceid` and `authorization` request headers.
115115

116+
### Request skipping
117+
You might want to omit some requests from logging completely. Right now, there are two ways to do it and you can even use both at once.
118+
1) Use `options.ignoredHttpMethods` to define an array of HTTP methods you want to omit. By default all `OPTIONS` requests are ommited. See [options](#options) for details
119+
2) Use `options.skip` method to define custom rules for requests skipping. Set it to a function which accepts an Express's `Request` and returns `boolean`. If the return value is `true`, request (and corresponding response) will not be logged. You might want to use `matchPath` helper to ignore requests based on the [`req.originalUrl` value](https://expressjs.com/en/4x/api.html#req.originalUrl)
120+
121+
```js
122+
const { matchPath } = require('cosmas/utils');
123+
const logger = require('cosmas').default({
124+
skip: matchPath(/heal.h/),
125+
});
126+
```
127+
116128
## Environment-specific behavior
117129
`cosmas` is meant to be used throughout different environments (development, testing, production) and some of its configuration is setup differently based on the environment it runs in.
118130

@@ -132,6 +144,7 @@ Options override both default logger configuration and environment-specific conf
132144
- `streams` - list of stream objects, which will be passed directly to [pino-multistream's multistream function](https://github.com/pinojs/pino-multi-stream#pinomsmultistreamstreams) instead of default `cosmas` stream
133145
- `pretty` - if set to `true`, logger will use [pino pretty human-readable logs](https://github.com/pinojs/pino/blob/master/docs/API.md#pretty). This option can be overriden by `streams`
134146
- `disableStackdriverFormat` - if set to `false`, logger will add `severity` field to all log objects, so that log levels in Google Stackdriver work as expected. Defaults to `false`
147+
- `skip` - Function to be used in express middlewares for filtering request logs. If the function returns `true` for a given request, no message will be logged. No default value.
135148
- `config` - object, which will be passed to underlying logger object. Right now, underlying logger is [pino](https://github.com/pinojs/pino), so for available options see [pino API docs](https://github.com/pinojs/pino/blob/master/docs/API.md#pinooptions-stream)
136149

137150
## Default serializers

src/express.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ const expressOnFinished = (logger: AckeeLogger, req: AckeeRequest) => (_err: Err
2727
const error = res[errorSymbol];
2828
const reqOut = `${res.statusCode} ${req.method} ${req.originalUrl} ${res.time} ms ${req.headers['user-agent']}`;
2929
if (logger.options.ignoredHttpMethods && logger.options.ignoredHttpMethods.includes(req.method)) {
30+
// left here for BC
31+
return;
32+
}
33+
if (logger.options.skip && logger.options.skip(req)) {
3034
return;
3135
}
3236
const standardOutput = {

src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ const defaultLogger = (options: AckeeLoggerOptions & { loggerName?: string } = {
6565
const logger = (pino(
6666
// no deep-merging needed, so assign is OK
6767
Object.assign(
68-
{},
6968
{
7069
messageKey,
7170
base: {},

src/interfaces.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Request } from 'express';
12
import * as pino from 'pino';
23

34
interface LoggerOptions extends pino.LoggerOptions {
@@ -20,4 +21,5 @@ export interface AckeeLoggerOptions {
2021
ignoredHttpMethods?: string[];
2122
config?: LoggerOptions;
2223
pretty?: boolean;
24+
skip?: (req: Request) => boolean;
2325
}

src/tests/express.test.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import * as express from 'express';
2+
import 'jest-extended';
3+
import { Writable } from 'stream';
4+
import * as supertest from 'supertest';
5+
6+
let loggerFactory;
7+
8+
beforeEach(() => {
9+
jest.resetModules();
10+
loggerFactory = require('..').default;
11+
});
12+
13+
const testWriteStream = (resolve, assert) => ({
14+
stream: new Writable({
15+
write: (chunk, encoding, next) => {
16+
const json = JSON.parse(chunk);
17+
assert(json);
18+
next();
19+
resolve();
20+
},
21+
}),
22+
});
23+
24+
test('express binds', () => {
25+
const logger = loggerFactory();
26+
const app = express();
27+
const request = supertest(app);
28+
app.use(logger.express);
29+
return request.get('/');
30+
});
31+
32+
test('GET requests are logged by default', () =>
33+
new Promise((resolve, reject) => {
34+
const logger = loggerFactory({
35+
streams: [testWriteStream(resolve, json => expect(json.req.method).toBe('GET'))],
36+
});
37+
const app = express();
38+
const request = supertest(app);
39+
app.use(logger.express);
40+
request.get('/').then(() => null);
41+
}));
42+
43+
test('OPTIONS requests are ignored by default', () => {
44+
const loggerWrites = jest.fn();
45+
const logger = loggerFactory({
46+
streams: [
47+
{
48+
stream: new Writable({
49+
write: (chunk, encoding, next) => {
50+
loggerWrites();
51+
next();
52+
},
53+
}),
54+
},
55+
],
56+
});
57+
const app = express();
58+
const request = supertest(app);
59+
app.use(logger.express);
60+
return request.options('/').then(() => {
61+
expect(loggerWrites).not.toBeCalled();
62+
});
63+
});
64+
65+
['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'].forEach(method => {
66+
test(`${method} HTTP method can be ignored by options`, () => {
67+
const loggerWrites = jest.fn();
68+
const logger = loggerFactory({
69+
ignoredHttpMethods: [method],
70+
streams: [
71+
{
72+
stream: new Writable({
73+
write: (chunk, encoding, next) => {
74+
loggerWrites();
75+
next();
76+
},
77+
}),
78+
},
79+
],
80+
});
81+
const app = express();
82+
const request = supertest(app);
83+
app.use(logger.express);
84+
return request[method.toLowerCase()]('/').then(() => {
85+
expect(loggerWrites).not.toBeCalled();
86+
});
87+
});
88+
});
89+
90+
test('route can be ignored by logger options', () => {
91+
const loggerWrites = jest.fn();
92+
const logger = loggerFactory({
93+
streams: [
94+
{
95+
stream: new Writable({
96+
write: (chunk, encoding, next) => {
97+
loggerWrites();
98+
next();
99+
},
100+
}),
101+
},
102+
],
103+
skip: (req: express.Request) => req.url === '/not-logged',
104+
});
105+
const app = express();
106+
const request = supertest(app);
107+
app.use(logger.express);
108+
return request.get('/not-logged').then(() => {
109+
expect(loggerWrites).not.toBeCalled();
110+
});
111+
});
112+
113+
test('route can be ignored using regexp helper', () => {
114+
const { matchPath } = require('../utils');
115+
const loggerWrites = jest.fn();
116+
const logger = loggerFactory({
117+
streams: [
118+
{
119+
stream: new Writable({
120+
write: (chunk, encoding, next) => {
121+
loggerWrites();
122+
next();
123+
},
124+
}),
125+
},
126+
],
127+
skip: matchPath(/heal.h/),
128+
});
129+
const app = express();
130+
const request = supertest(app);
131+
app.use(logger.express);
132+
return request.get('/healthcheck').then(() => {
133+
expect(loggerWrites).not.toBeCalled();
134+
});
135+
});

src/tests/index.test.ts

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import * as express from 'express';
21
import 'jest-extended';
32
import { Writable } from 'stream';
4-
import * as supertest from 'supertest';
53
import { levels } from '../levels';
64

75
let loggerFactory;
@@ -70,72 +68,6 @@ test('child logger has warning level', () =>
7068
childLogger.warning('Hello');
7169
}));
7270

73-
test('express binds', () => {
74-
const logger = loggerFactory();
75-
const app = express();
76-
const request = supertest(app);
77-
app.use(logger.express);
78-
return request.get('/');
79-
});
80-
81-
test('GET requests are logged by default', () =>
82-
new Promise((resolve, reject) => {
83-
const logger = loggerFactory({
84-
streams: [testWriteStream(resolve, json => expect(json.req.method).toBe('GET'))],
85-
});
86-
const app = express();
87-
const request = supertest(app);
88-
app.use(logger.express);
89-
request.get('/').then(() => null);
90-
}));
91-
92-
test('OPTIONS requests are ignored by default', () => {
93-
const loggerWrites = jest.fn();
94-
const logger = loggerFactory({
95-
streams: [
96-
{
97-
stream: new Writable({
98-
write: (chunk, encoding, next) => {
99-
loggerWrites();
100-
next();
101-
},
102-
}),
103-
},
104-
],
105-
});
106-
const app = express();
107-
const request = supertest(app);
108-
app.use(logger.express);
109-
return request.options('/').then(() => {
110-
expect(loggerWrites).not.toBeCalled();
111-
});
112-
});
113-
114-
['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'].forEach(method => {
115-
test(`${method} HTTP method can be ignored by options`, () => {
116-
const loggerWrites = jest.fn();
117-
const logger = loggerFactory({
118-
ignoredHttpMethods: [method],
119-
streams: [
120-
{
121-
stream: new Writable({
122-
write: (chunk, encoding, next) => {
123-
loggerWrites();
124-
next();
125-
},
126-
}),
127-
},
128-
],
129-
});
130-
const app = express();
131-
const request = supertest(app);
132-
app.use(logger.express);
133-
return request[method.toLowerCase()]('/').then(() => {
134-
expect(loggerWrites).not.toBeCalled();
135-
});
136-
});
137-
});
138-
13971
test('severity field is automatically added to log object', () =>
14072
new Promise((resolve, reject) => {
14173
const logger = loggerFactory({

src/utils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import { Request } from 'express';
12
import { Dictionary } from 'lodash';
23
import isEmpty = require('lodash.isempty');
34
import omit = require('omit-deep');
45

56
const removeEmpty = (obj: Dictionary<any>): object =>
67
omit(obj, Object.keys(obj).filter(key => obj[key] === undefined || isEmpty(obj[key])));
78

8-
export { removeEmpty };
9+
const matchPath = (pattern: RegExp) => (req: Request): boolean => req.originalUrl.match(pattern) !== null;
10+
11+
export { removeEmpty, matchPath };

0 commit comments

Comments
 (0)