winston
is the most popular logging solution for Node.js. In fact, when
measured in public npm
downloads winston
is so popular that it has more
usage than the top four comparable logging solutions combined. For nearly
the last three years the winston
project has undergone a complete rewrite
for a few reasons:
winston
internals with Node.js objectMode
streams.winston
itself.winston
into several smaller packages: winston-transport
,
logform
, and triple-beam
.Why don’t you take it for a spin?
npm i winston@3
# or if `yarn` is more your fancy
yarn add winston@3
winston@3
APIIf you’re familiar with winston
the default v3
API will look pretty familiar to you:
const { transports, format, createLogger } = require('winston');
const logger = createLogger({
level: 'info',
format: format.combine(
format.timestamp(),
format.colorize(),
format.printf(info => `[${info.timestamp}] ${info.level}: ${info.message}`)
),
transports: [
//
// - Write to all logs with level `info` and below to `combined.log`
// - Write all logs error (and below) to `error.log`.
//
new transports.File({ filename: 'error.log', level: 'error' }),
new transports.File({ filename: 'combined.log' })
]
});
logger.info('Hello again distributed logs');
And that’s because the core logging semantics are the same. There are however, a few key differences:
createLogger
instead of new Logger
this change allowed for a major
performance increase due to how prototype functions are optimized.winston
core itself and allow for userland (i.e.
user-defined formats to be shared just like userland transports are
already. We’ll discuss formats in-depth below.Logger
and Transport
instances are now Node.js objectMode
streams.finish
event instead to know when all logs have been written
and the process is safe to exit.There is an extensive upgrade guide on how to migrate to
winston@3
. Particular care was taken to ensure backwards compatibility with existing transports. There are also a long list of full featured examples.
You shouldn’t be worried about your favorite Transport not working with
winston@3
– the winston-transport
API was designed with this in mind.
Any winston@2
transport should get seamlessly wrapped with a compatibility
stream that will tell you to politely nudge the author:
SomeTransport is a legacy winston transport. Consider upgrading:
- Upgrade docs: https://github.com/winstonjs/winston/blob/master/UPGRADE-3.0.md
Are you a transport author? We’d love your input! There’s a great discussion
going on about how to seamlessly support both winston@2
and winston@3
with
low maintenance overhead.
“~Life~ Open Source is a series of natural and spontaneous changes.”
To understand why formats were the right decision for winston@3
we must look
backwards. The essence of the winston@2.x
API is summed up by common.log
which will forever exist in winston-compat as a compatibility layer for
transport authors. It began innocent enough – a shared utility function that
accepts options
and returns a formatted string
representing the log
message.
Then came a series of natural and spontaneous changes over the course of many years – and it grew. And grew. And grew. And created an unending list of feature requests for log formatting (see just a few).
As this pattern became more evident so did the cost. This made the design goals clear for formats:
winston
itself.This focus on log formatting features is not by accident. What winston
has
shown is that reading logs is an immensely personal experience. By putting the
formatting features in userland we support that demand with less burden.
What is a format? Let’s start with an example that adds a timestamp, colorizes
the level and message, aligns message content with \t
and prints using the
template [timestamp] [level]: [message]
.
const { format } = require('winston');
const customFormat = format.combine(
format.timestamp(), // Adds info.timestamp
format.colorize({ all: true }), // Colorizes { level, message } on the info
format.align(), // Prepends message with `\t`
format.printf(info => `[${info.timestamp}] ${info.level}: ${info.message}`)
);
Each one of these format
methods resemble a Node.js TransformStream
but are
specifically designed to be synchronous since they do not have to handle
backpressure (i.e. a fast reader and a slow writer – such as reading from file
and writing to an HTTPS socket).
There is too much to go into about Formats in a single blog post, but there’s a lot more in the Format documentation and in logform where they are implemented.
npm
packages – oh my!The key to the winston@3
API is ES6 Symbols – the API itself would have not
been possible without them. Why? The property value for the LEVEL
symbol is a
copy of the original level
value that should be considered immutable to
formats.
Without an ES6 Symbol we’d still have the same need to accomplish API goals.
That means instead of [LEVEL]
in this example info
object:
{ [LEVEL]: 'info', level: 'info', message: 'Look at my logs, my logs are amazing.' }
we would have to pick a plain property name, say _level
:
{ _level: 'info', level: 'info', message: 'Look at my logs, my logs are amazing.' }
You might think that a _level
property vs. a LEVEL
symbol are more or less
the same. They are except for one important difference: Symbols are excluded
from JSON
by definition. This is critical because so many log files are
serialized to JSON and including these internal properties would be
unacceptable.
This seemingly small nuance of the API turned out to be critically important to
the winston@3
release timeline. To understand why one must understand what
Node.js LTS is and what it can mean to authors of popular npm
packages such
as winston
.
Since Node.js began the LTS program it’s been the goal of winston
to support
all LTS releases of Node (including maintenance LTS). Why support such old LTS
releases of Node.js? The underlying motivation is to make winston
as reliable
for large teams & enterprises as Node.js itself.
This LTS goal is a core reason why this release took three years to ship. Let’s look at a recent release chart for Node.js LTS:
As you can see node@4
entered “end of life” in April 2018. Until then it was
in the active support matrix for winston
releases. Since ES6 features
(including Symbols) were only available in node@6
this meant that even if API
development and re-write progress was ahead of schedule (it wasn’t) that
version wouldn’t be able to use it.
Why not polyfill the ES6 Symbol API? That’s definitely a possibility to consider, but given the size and scope of the rewrite itself it didn’t seem necessary to rush ourselves. Having seen how delicate these API decisions can impact such a large ecosystem of packages (almost 1000 results on npm).
Looking to get involved in an actively maintained open source project? Then
look no further - we’d love to have you join us on winston
!
0x
and even more test coverage).You can find us on Gitter in winston
.
And with that – happy logging!
The author would like to extend a very special thanks to all of the contributors for the
winston@3
release. In particular: David Hyde, Chris Alderson, and Jarrett Cruger.