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

Commit 8beac39

Browse files
author
Michal Vlasák
committed
Add README, remove useless files, refactor
1 parent 1036ff4 commit 8beac39

5 files changed

Lines changed: 207 additions & 107 deletions

File tree

README.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Simple pino-based logger setup for Ackee purposes
2+
3+
## How to use
4+
5+
First step is to create a root logger. Its configuration can be specified on creation and it will be used for all other loggers created.
6+
7+
### Create root logger with default configuration
8+
9+
```js
10+
const logger = require('ackee-node-logger');
11+
// of
12+
const logger = require('ackee-node-logger')();
13+
```
14+
15+
### Create root logger with custom configuration
16+
17+
```js
18+
const logger = require('ackee-node-logger)({
19+
disableFields: ['error.stack'],
20+
enableFields: ['req.protocol']
21+
});
22+
```
23+
24+
Note: If you want to specify custom configuration it must be done **in the first require** of `ackee-node-logger`. Otherwise, default configuration will be used.
25+
26+
See **Options** for a list of possible options.
27+
28+
After you create a root logger, you may use it or you can create a child logger.
29+
30+
```js
31+
const databaseLogger = require('ackee-node-logger')('database')
32+
```
33+
34+
The only difference between root logger and a child logger is that the child logger will print its name in each log message.
35+
36+
## Logger usage
37+
38+
Logger itself is an enhanced and specifically configured `pino` instance, so you may use all basic `pino` log methods
39+
40+
```js
41+
pino.info('hello world')
42+
pino.error('this is at error level')
43+
pino.info('the answer is %d', 42)
44+
pino.info({ obj: 42 }, 'hello world')
45+
pino.info({ obj: 42, b: 2 }, 'hello world')
46+
pino.info({ obj: { aa: 'bbb' } }, 'another')
47+
```
48+
49+
All `pino` levels are supported and additionaly there is a `warning` level which is equivalent to `warn` level.
50+
51+
Default minimal log level is `debug`.
52+
53+
All loglevels up to warning (exclusive) - trace, debug and info - are logged to `stdout` **only**.
54+
55+
All loglevels from warning up (inclusive) - warning, error, fatal - are logged to `stderr` **only**.
56+
57+
## Express middleware
58+
59+
`ackee-node-logger` contains an express middleware which you can use to log all requests and responses of your express application.
60+
61+
Usage:
62+
```js
63+
const express = require('express');
64+
const logger = require('ackee-node-logger');
65+
66+
const app = express();
67+
// or
68+
const router = express.Router();
69+
70+
app.use(logger.express)
71+
// or
72+
router.use(logger.express)
73+
```
74+
75+
By default, it will log all incoming requests in `debug` level, all outcoming responses with `out` property in `debug` level and all outcoming responses without `out` property on `info` level.
76+
77+
If you use it together with logger's error express middleware, it will also log all errors in `error` level.
78+
79+
```js
80+
app.use(logger.expressError)
81+
```
82+
83+
All those log messages will contain request and possibly response, error, time from request to response and status code.
84+
85+
## Environment-specific behavior
86+
`ackee-node-logger` 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.
87+
88+
### Testing
89+
If the `NODE_ENV` environment variable is set to `test`, all logs are turned off (minimal loglevel is set to `silent` which effectively turns logging off).
90+
91+
### Production
92+
If the `NODE_ENV` environment variable is set to `production`, [standard pino log](https://github.com/pinojs/pino#usage) is used.
93+
94+
### Otherwise
95+
In other cases, minimal loglevel is set to `debug` and [pino pretty human-readable logs](https://github.com/pinojs/pino/blob/master/docs/API.md#pretty) are used.
96+
97+
## Options
98+
Options override both default logger configuration and environment-specific configuration. However, do not forget to specify it during the **first** `ackee-node-logger`. During it, root logger is created and it cannot be changed later.
99+
100+
- `defaultLevel` - set logger's minimal loglevel (default is `debug`)
101+
- `disableFields` - list of paths which will be omitted from the objects being logged (if any)
102+
- `enableFields` - list of paths which will not be omitted by default serializers from objects being logged
103+
- `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 `ackee-node-logger` stream
104+
105+
## Default serializers
106+
`ackee-node-logger` defines some [pino serializers](https://github.com/pinojs/pino/blob/master/docs/API.md#constructor) on its own
107+
108+
- `error` - logs `message`, `code`, `stack` and `data` fields
109+
- `processEnv` - logs `NODE_PATH` and `NODE_ENV`
110+
- `req` - logs `body`, `query`, `url`, `method` and omits `password` and `passwordCheck` from `body` and `query`
111+
- `res` - logs `out` and `time`

express.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const onFinished = require('on-finished');
2+
const onHeaders = require('on-headers');
3+
4+
const expressMiddleware = function(req, response, next) {
5+
this.debug({ req, ackId: req.ackId }, 'Request accepted');
6+
req._startAt = process.hrtime();
7+
onHeaders(response, () => {
8+
response._startAt = process.hrtime();
9+
const diffFromSeconds = (response._startAt[0] - req._startAt[0]) * 1e3;
10+
const diffFromNanoseconds = (response._startAt[1] - req._startAt[1]) * 1e-6;
11+
const ms = diffFromSeconds + diffFromNanoseconds;
12+
response.time = ms.toFixed(3);
13+
});
14+
onFinished(response, (err, res) => {
15+
const error = res[Symbol.for('error')];
16+
if (error) {
17+
this.error({ error, req, res, ackId: req.ackId }, 'Error handler at the end of app');
18+
} else if (res.out) {
19+
this.debug({ req, res, ackId: req.ackId }, `Standard output [${res.statusCode}]`);
20+
} else {
21+
this.info({ req, res, ackId: req.ackId }, `Standard output [${res.statusCode}]`);
22+
}
23+
});
24+
next();
25+
};
26+
27+
const expressErrorMiddleware = (error, req, res, next) => {
28+
res[Symbol.for('error')] = error;
29+
next(error);
30+
};
31+
32+
module.exports = {
33+
expressMiddleware,
34+
expressErrorMiddleware,
35+
};

foo.js

Lines changed: 0 additions & 20 deletions
This file was deleted.

index.js

Lines changed: 7 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
1-
// https://gist.github.com/tothandras/bc06910063015b8891ff0fbb909c68c2
2-
// https://github.com/trentm/node-bunyan#levels
3-
// https://news.ycombinator.com/item?id=14209168
4-
// https://tools.ietf.org/html/rfc5424
5-
61
const _ = require('lodash');
72
const pino = require('pino');
8-
const onFinished = require('on-finished');
9-
const onHeaders = require('on-headers');
103
const multistream = require('pino-multi-stream').multistream;
114
const serializers = require('./serializers');
5+
const { expressMiddleware, expressErrorMiddleware } = require('./express');
126

137
const levels = {
148
silent: Infinity,
@@ -20,9 +14,6 @@ const levels = {
2014
trace: 10,
2115
};
2216

23-
// options.disableFields = ['error.stack'];
24-
// options.enableFields = ['req.protocol'];
25-
2617
const maxLevelWrite = function(data) {
2718
let dest;
2819
let stream;
@@ -48,79 +39,14 @@ const maxLevelWrite = function(data) {
4839
}
4940
};
5041

51-
const expressMiddleware = function(req, response, next) {
52-
this.debug({ req, ackId: req.ackId }, 'Request accepted');
53-
req._startAt = process.hrtime();
54-
onHeaders(response, () => {
55-
response._startAt = process.hrtime();
56-
const diffFromSeconds = (response._startAt[0] - req._startAt[0]) * 1e3;
57-
const diffFromNanoseconds = (response._startAt[1] - req._startAt[1]) * 1e-6;
58-
const ms = diffFromSeconds + diffFromNanoseconds;
59-
response.time = ms.toFixed(3);
60-
});
61-
onFinished(response, (err, res) => {
62-
const error = res[Symbol.for('error')];
63-
if (error) {
64-
this.error({ error, req, res, ackId: req.ackId }, 'Error handler at the end of app');
65-
} else if (res.out) {
66-
this.debug({ req, res, ackId: req.ackId }, `Standard output [${res.statusCode}]`);
67-
} else {
68-
this.info({ req, res, ackId: req.ackId }, `Standard output [${res.statusCode}]`);
69-
}
70-
});
71-
next();
72-
};
73-
74-
const expressErrorMiddleware = (error, req, res, next) => {
75-
res[Symbol.for('error')] = error;
76-
next(error);
77-
};
78-
7942
const defaultLogger = (options = {}) => {
8043
const pretty = pino.pretty();
8144
pretty.pipe(process.stdout);
8245
const prettyErr = pino.pretty();
8346
prettyErr.pipe(process.stderr);
8447

85-
if (options.disableFields) {
86-
_.forEach(serializers, (value, key) => {
87-
const matcher = new RegExp(`^${key}.(.*)`);
88-
const affectedFields = [];
89-
options.disableFields.forEach(field => {
90-
field.replace(matcher, (match, p1) => {
91-
affectedFields.push(p1);
92-
});
93-
});
94-
95-
if (affectedFields.length > 0) {
96-
const newSerializer = obj => {
97-
return _.omit(value(obj), affectedFields);
98-
};
99-
serializers[key] = newSerializer;
100-
}
101-
});
102-
}
103-
104-
if (options.enableFields) {
105-
_.forEach(serializers, (value, key) => {
106-
const matcher = new RegExp(`^${key}.(.*)`);
107-
const affectedFields = [];
108-
options.enableFields.forEach(field => {
109-
field.replace(matcher, (match, p1) => {
110-
affectedFields.push(p1);
111-
});
112-
});
113-
114-
if (affectedFields.length > 0) {
115-
const newSerializer = obj => {
116-
const newFields = _.pick(obj, affectedFields);
117-
const originalResult = value(obj);
118-
return _.assign(originalResult, newFields);
119-
};
120-
serializers[key] = newSerializer;
121-
}
122-
});
123-
}
48+
serializers.disablePaths(options.disableFields);
49+
serializers.enablePaths(options.enableFields);
12450

12551
const isProduction = process.env.NODE_ENV === 'production';
12652
const isTesting = process.env.NODE_ENV === 'test';
@@ -152,7 +78,10 @@ const defaultLogger = (options = {}) => {
15278
streams = options.streams;
15379
}
15480

155-
const logger = pino({ level: defaultLevel, timestamp: false, base: {}, serializers }, multistream(streams));
81+
const logger = pino(
82+
{ level: defaultLevel, timestamp: false, base: {}, serializers: serializers.serializers },
83+
multistream(streams)
84+
);
15685
logger.warning = logger.warn;
15786

15887
// Add maxLevel support to pino-multi-stream

serializers.js

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
const _ = require('lodash');
22

3-
/**
4-
* Gets full URL from express Req object
5-
* @param {Req} req
6-
* @return {!string}
7-
*/
8-
const fullUrlFromReq = req => (req.get ? `${req.protocol}://${req.get('host')}${req.originalUrl}` : '');
9-
10-
module.exports = {
3+
const serializers = {
114
error(obj) {
125
return {
136
message: _.get(obj, 'message'),
@@ -45,7 +38,7 @@ module.exports = {
4538
return {
4639
body,
4740
query,
48-
url: fullUrlFromReq(obj),
41+
url: obj.originalUrl || obj.url,
4942
method: _.get(obj, 'method'),
5043
};
5144
},
@@ -56,3 +49,55 @@ module.exports = {
5649
};
5750
},
5851
};
52+
53+
const disablePaths = paths => {
54+
if (!paths || paths.length <= 0) {
55+
return;
56+
}
57+
_.forEach(serializers, (value, key) => {
58+
const matcher = new RegExp(`^${key}.(.*)`);
59+
const affectedFields = [];
60+
paths.forEach(field => {
61+
field.replace(matcher, (match, p1) => {
62+
affectedFields.push(p1);
63+
});
64+
});
65+
66+
if (affectedFields.length > 0) {
67+
const newSerializer = obj => {
68+
return _.omit(value(obj), affectedFields);
69+
};
70+
serializers[key] = newSerializer;
71+
}
72+
});
73+
};
74+
75+
const enablePaths = paths => {
76+
if (!paths || paths.length <= 0) {
77+
return;
78+
}
79+
_.forEach(serializers, (value, key) => {
80+
const matcher = new RegExp(`^${key}.(.*)`);
81+
const affectedFields = [];
82+
paths.forEach(field => {
83+
field.replace(matcher, (match, p1) => {
84+
affectedFields.push(p1);
85+
});
86+
});
87+
88+
if (affectedFields.length > 0) {
89+
const newSerializer = obj => {
90+
const newFields = _.pick(obj, affectedFields);
91+
const originalResult = value(obj);
92+
return _.assign(originalResult, newFields);
93+
};
94+
serializers[key] = newSerializer;
95+
}
96+
});
97+
};
98+
99+
module.exports = {
100+
serializers,
101+
enablePaths,
102+
disablePaths,
103+
};

0 commit comments

Comments
 (0)