Skip to content

Commit fa86131

Browse files
committed
feat: Drop winston and simpplify the logger
Drop winston in favour of creating a simple logger that allows extracting error object properties (including message) while keeping the log message. BREAKING CHANGE: The logger do not allow using winston's API anymore as we dropped winston
1 parent 69b7a11 commit fa86131

12 files changed

Lines changed: 877 additions & 313 deletions

File tree

.circleci/config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ jobs:
3131
- run:
3232
name: Lint
3333
command: npm run lint
34+
- run:
35+
name: Test
36+
command: npm run test
3437
- persist_to_workspace:
3538
root: ~/repo
3639
paths:

.eslintrc.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@
55
"promise",
66
"security"
77
],
8+
"env": {
9+
"mocha": true
10+
},
811
"rules": {
912
"array-bracket-newline": [2, "consistent"],
1013
"array-element-newline": [2, "consistent"],
1114
"arrow-body-style": [2, "as-needed"],
1215
"capitalized-comments": 2,
1316
"comma-dangle": 2,
1417
"dot-location": [2, "property"],
18+
"no-console": 0,
1519
"no-empty-function": 2,
1620
"no-magic-numbers": 0,
1721
"no-tabs": [2, {"allowIndentationTabs": true}],

README.md

Lines changed: 13 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,8 @@
77
npm install --save @5app/logger
88
```
99

10-
Then, generate a logger in your app and use it to log messages.
11-
12-
```javascript
13-
const {logger} = require('@5app/logger');
14-
15-
logger.info('An email was sent', {
16-
email: 'customer@5app.com',
17-
template: 'template1',
18-
});
19-
20-
logger.error(new Error('Unknown playlist 123'));
21-
```
22-
23-
Alternatively, you can create a new instance of logger where you can specify the metadata, logging level, and whether you want simple logs or not:
24-
2510
```javascript
26-
const {getLogger} = require('@5app/logger');
27-
28-
const logger = getLogger({
29-
simple: process.env.NODE_ENV === 'development',
30-
metadata: {
31-
tag: process.env.TAG, // release tag, e.g. docker container tag
32-
},
33-
});
11+
const logger = require('@5app/logger');
3412

3513
logger.info('An email was sent', {
3614
email: 'customer@5app.com',
@@ -42,57 +20,21 @@ logger.error(new Error('Unknown playlist 123'));
4220

4321
## Options
4422

45-
The logger generator accepts the following options:
23+
The logger can optionally be customised using the following environment variables:
4624

47-
### simple
25+
- `LOGS_FORMAT`: if set to `json`, the logger will log messages in json format instead of pretty messages (default behaviour).
26+
- `LOGS_LEVEL`: minimum logging level, by default it will be `debug`. Accepted values are `'debug'`, `'info'`, `'warn'`, and `'error'`
27+
- `TAG`: release tag (e.g. docker image tag) to be added to the log messages
4828

49-
This boolean value specifies whether we should log pretty dev-friendly messages instead of JSON or not.
50-
By default, simple will be false.
29+
## Logging levels
5130

52-
Example:
53-
```javascript
54-
const {getLogger} = require('@5app/logger');
31+
Logging levels are (from lower to higher priority): `'debug'`, `'info'`, `'warn'`, and `'error'`.
32+
The logger provides the logging functions with the following signatures: `logger.<level>(message, objectOrError)`
5533

56-
// This logger will log dev-friendly messages
57-
const logger = getLogger({
58-
simple: true,
59-
});
34+
Here is an example of how the logger can be used:
6035
```
61-
62-
The default logger uses the environment variable `LOGS_FORMAT` to determine if the logs are going to be generated in json (`json`) or simple console logs (any other value other than `json`).
63-
64-
### metadata
65-
66-
Metadata is an object containing service metadata like the release tag or the A/B testing experiment we are running.
67-
This object will be added to each log entry when using the JSON mode.
68-
69-
Example:
70-
```javascript
71-
const {getLogger} = require('@5app/logger');
72-
73-
// This logger will add details about the current release and A/B experiment to every log line
74-
const logger = getLogger({
75-
metadata: {
76-
release: '1.2.3',
77-
experiment: 12345,
78-
},
79-
});
80-
```
81-
82-
### level
83-
84-
You can specify a minimum logging level using the `level` parameter or using the `LOGS_LEVEL` environment variable.
85-
By default, the logging level will be [debug (npm)](https://github.com/winstonjs/winston#logging-levels).
86-
87-
Example:
88-
```javascript
89-
const {getLogger} = require('@5app/logger');
90-
91-
// This logger will add details about the current release and A/B experiment to every log line
92-
const logger = getLogger({
93-
level: 'warn',
94-
});
95-
96-
logger.info('this message will not be logged');
97-
logger.warn('you will see this message');
36+
logger.error('An error happened', new ApiError('The api call failed', 404)); // will log the message, the error message, the stack trace, and the statusCode error property
37+
logger.warn('Be warned', {a: 1, b: Date.now(), c: 'some string'});
38+
logger.info('An event happened', {a: 1, b: Date.now(), c: 'some string'});
39+
logger.debug('A minor operation', {a: 1, b: Date.now(), c: 'some string'});
9840
```

getLogger.js

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

index.js

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
const getLogger = require('./getLogger');
1+
const simple = require('./outputs/simple');
2+
const json = require('./outputs/json');
3+
const levels = require('./levels');
4+
const priority = require('./priority');
25

3-
const {
4-
LOGS_FORMAT,
5-
LOGS_LEVEL,
6-
TAG
7-
} = process.env;
6+
const {LOGS_FORMAT, LOGS_LEVEL} = process.env;
87

9-
const logger = getLogger({
10-
simple: LOGS_FORMAT !== 'json',
11-
level: LOGS_LEVEL || 'debug',
12-
metadata: {
13-
tag: TAG
14-
}
8+
const logger = LOGS_FORMAT === 'json' ? json : simple;
9+
const minimumPriority = priority(LOGS_LEVEL || 'debug');
10+
11+
// eslint-disable-next-line no-empty-function
12+
const noLog = () => {};
13+
14+
const methods = {};
15+
16+
Object.keys(levels).forEach(level => {
17+
methods[level] = priority(level) >= minimumPriority ? (message, context) => logger(level, message, context) : noLog;
1518
});
1619

17-
module.exports = {
18-
logger,
19-
getLogger
20-
};
20+
module.exports = methods;

levels.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const chalk = require('chalk');
2+
3+
const levels = {
4+
error: {priority: 4, color: chalk.red},
5+
warn: {priority: 3, color: chalk.yellow},
6+
info: {priority: 2, color: chalk.green},
7+
debug: {priority: 1, color: chalk.grey, colorAll: true}
8+
};
9+
10+
module.exports = levels;

outputs/json.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const priority = require('../priority');
2+
3+
const {TAG: tag} = process.env;
4+
5+
function formatError(data) {
6+
if (data instanceof Error) {
7+
return {
8+
error: data.message,
9+
stacktrace: data.stack ? data.stack.split('\n').slice(1, 3)
10+
.map(line => line.trim()) : undefined,
11+
statusCode: data.statusCode
12+
};
13+
}
14+
15+
return data;
16+
}
17+
18+
function log(level, message, context = {}) {
19+
let contextObject = context;
20+
21+
if (priority(level) >= priority('error')) {
22+
contextObject = formatError(context);
23+
}
24+
25+
const payload = JSON.stringify({
26+
level,
27+
message,
28+
tag,
29+
timestamp: new Date().toISOString(),
30+
...contextObject
31+
});
32+
33+
console.log(payload);
34+
}
35+
36+
module.exports = log;

outputs/simple.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const util = require('util');
2+
const levels = require('../levels');
3+
4+
// Precalculate each level logging options
5+
const prefixes = Object.keys(levels).reduce((map, level) => {
6+
map[level] = levels[level].color(`${level}:`);
7+
return map;
8+
}, {});
9+
10+
// Simple logging function
11+
function log(level, message, context = '') {
12+
const prefix = prefixes[level] || '';
13+
const {colorAll, color} = levels[level];
14+
15+
if (colorAll) {
16+
console.log(color(util.format(`${level}: ${message}`, context)));
17+
}
18+
else {
19+
console.log(`${prefix} ${message}`, context);
20+
}
21+
}
22+
23+
module.exports = log;

0 commit comments

Comments
 (0)