Dec 3rd, 2021 - written by Kimserey with .
Winston logger is one of the most popular logger for node application. In today’s post we will look at how we can configure and use Winston.
We start by installing Winston:
1
npm install winston
We can then import the createLogger
function which is the main function used to create the logger and the transports
and format
namespaces containing the functions needed to configure the logger.
1
import { createLogger, transports, format } from "winston";
The simplest logger we can start with would be a console logger with a colorized simple output. To configure that we have to specify:
1
2
3
4
5
6
7
8
9
10
11
12
const logger = createLogger({
transports: [new transports.Console()],
format: format.combine(
format.colorize(),
format.timestamp(),
format.printf(({ timestamp, level, message }) => {
return `[${timestamp}] ${level}: ${message}`;
})
),
});
logger.info("Hello world!");
We used createLogger
to create a logger and passed in the Console
transport and a simple format taking the timestamp
, level
and message
.
1
[2021-11-21T09:08:40.844Z] info: Hello world!
We can see that logging Hello world!
then yield the expected log format.
Additionally we can specify extra metadata to be used in the format:
1
2
3
4
5
6
7
8
9
10
11
12
13
const logger = createLogger({
transports: [new transports.Console()],
format: format.combine(
format.colorize(),
format.timestamp(),
format.printf(({ timestamp, level, message, service }) => {
return `[${timestamp}] ${service} ${level}: ${message}`;
})
),
defaultMeta: {
service: "WinstonExample",
},
});
Here we added default metadata service
which we used in the printf
format and that will print our service in the logs:
1
[2021-11-21T09:13:14.545Z] WinstonExample info: Hello world!
There are several core transports build-in, we already seen Console
, the other available are File
, Http
and Stream
.
File
can be used to save logs into a file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const logger = createLogger({
transports: [
new transports.File({
dirname: "logs",
filename: "winston_example.log",
}),
],
format: format.combine(
format.timestamp(),
format.printf(({ timestamp, level, message, service }) => {
return `[${timestamp}] ${service} ${level}: ${message}`;
})
),
defaultMeta: {
service: "WinstonExample",
},
});
Here the logs will be written to a file under logs/winston_example.log
. The logs will be appended, multiple options are available to specify the max size, rotate the file and zip the file.
Http
is used to ship logs to a defined http endpoint:
1
2
3
4
5
6
7
8
9
10
11
12
const logger = createLogger({
transports: [new transports.Http()],
format: format.combine(
format.timestamp(),
format.printf(({ timestamp, level, message, service }) => {
return `[${timestamp}] ${service} ${level}: ${message}`;
})
),
defaultMeta: {
service: "WinstonExample",
},
});
Stream
is the most flexible transport as it accepts a Node stream. Using it, we are able to implement any transport. For example here, we are putting the logs into a stream file just to demonstrate the usage:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { createLogger, transports, format } from "winston";
import { createWriteStream } from "fs";
const logger = createLogger({
transports: [
new transports.Stream({
stream: createWriteStream("hello.txt"),
}),
],
format: format.combine(
format.timestamp(),
format.printf(({ timestamp, level, message, service }) => {
return `[${timestamp}] ${service} ${level}: ${message}`;
})
),
defaultMeta: {
service: "WinstonExample",
},
});
And there are many more community driven transports available.
We saw earlier that format
namespace exposes the different format functions which can be used to generate a format for the logger. The main formats we used in this post were:
combine
colorize
timestamp
printf
json
combine
is a format building block which allow us to combine multiple formats. For example if we want to have a colorized human readable formatted string with timestamp, we can combine the following:
1
2
3
4
5
6
7
format.combine(
format.colorize(),
format.timestamp(),
format.printf(({ timestamp, level, message }) => {
return `[${timestamp}] ${level}: ${message}`;
})
);
We can see that colorize
will apply color to the level so that error would appear red, and timestamp
will make available the current timestamp in metadata. Then all the metadata can be extracted in printf
to construct a nice log.
string
formatted log are great for console debugging but when shipping logs to server application, json format is preferable as it enables querying from tools. To ship as json we can use the format json
.
1
format.combine(format.timestamp(), format.json());
We removed colorize
as we do not want the color unicode to be added to the level or the message.
Another interesting format is splat
which enables string interpolation:
1
2
3
4
5
6
7
format.combine(
format.colorize(),
format.splat(),
format.printf(({ timestamp, level, message }) => {
return `[${timestamp}] ${level}: ${message}`;
})
);
We can then use %d
for digits and %s
for strings:
1
2
const userId = "123";
logger.info("Hello world! userId: %s", userId);
And the message will be interpolated:
1
[2021-11-21T10:06:31.477Z] WinstonExample info: Hello world! userId: 123
Lastly we can provide extra metadata which are not part of the message on each logs. This is useful to provide request ID or correlation ID.
1
logger.info("Hello world!", { correlationId: "123", userId: "456 " });
By default those will be captured in the log information but we can specifically place them into a property of the log using the metadata
format:
1
2
3
4
5
6
7
8
9
format.combine(
format.colorize(),
format.splat(),
format.metadata(),
format.timestamp(),
format.printf(({ timestamp, level, message, metadata }) => {
return `[${timestamp}] ${level}: ${message}. ${JSON.stringify(metadata)}`;
})
);
All the metadata are being capture into { metadata: {...} }
. Take note that timestamp
is registered after metadata
- the order matters otherwise it will be captured within metadata
.
So far we’ve seen how to define logs for console and how to define logs for files. We saw that the colorize
format was only useful for the console transport and json
was only useful for file or http transports. Therefore what we want to achieve is to have specific formats per transports.
Each transport have a format
attribute which can be used to be combined with the format
defined at the root option of the logger.
To achieve what we describe above we can then do the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const logger = createLogger({
transports: [
new transports.Console({
format: format.combine(
format.colorize(),
format.printf(({ timestamp, level, message, metadata }) => {
return `[${timestamp}] ${level}: ${message}. ${JSON.stringify(
metadata
)}`;
})
),
}),
new transports.File({
dirname: "logs",
filename: "winston_example.log",
format: format.combine(format.json()),
}),
],
format: format.combine(format.metadata(), format.timestamp()),
});
logger.info("Hello world!", { correlationId: "123", userId: "456 " });
We define a global format which gather metadata and add a timestamp. And we then define two transports one for console which colorize the log and print the log in a defined format. And we define another file transport which output the log in json.
We end up with the following log printed in console:
1
[2021-11-21T10:23:23.380Z] info: Hello world!. {"correlationId":"123","userId":"456 "}
while having the following in file:
1
2
3
4
5
6
{
"level": "info",
"message": "Hello world!",
"metadata": { "correlationId": "123", "userId": "456 " },
"timestamp": "2021-11-21T10:23:23.380Z"
}
And that concludes today’s post on Winston logger!
Today we looked at Winston logger. We started by looking at how we could install it then looked at a very simple example to get started quickly. We then moved on to look in more details at the transports available to ship the logs and we also took a look at the different format functions available to transform the logs into either human readable logs or json logs. We completed the post by looking at how we could define sepcific format for different transports. I hope you liked this post and I’ll see you on the next one!