Winston: A Better Way To Log

Image for post
Image for post
Photo by Tim Wright on Unsplash

One of the first things any JavaScript developer learns is Console.log(). This is a useful function which accepts one or more objects and outputs their string representation to the developer console. It is often used during the development process to help know what value certain variables have during an application’s execution. This form of “log” is most useful when trying to determine when and why an error occurred (debugging). While good for most beginner projects, it may not be practical to rely on such logging for enterprise solutions. For instance, let’s say you maintain a site that accepts payments and a user reports an issue with a payment made days, or even weeks, ago. Having access to a detailed log of that user’s actions at the time of making the payment would make the debugging process much easier. It’s for this reason that most developers require logging to more than just the console. Some common storage options for logs are files and databases, with some even choosing to log to emails. While you might choose to implement a custom solution, Winston offers developers the ability to log to various storage locations with just a few lines of code. So, who is Winston?

Not a “who”, but a “what”. Winston is a JavaScript logging library that makes logging to various, persistent, storage locations, like databases or files, much simpler. Here’s how to use it to log to a file:

First thing’s first, you’ll want to install both the winston and winston-daily-rotate-file (we’ll use this later on) packages from NPM.

Now that you have the necessary packages installed (and imported/required in your project), you can create your own custom format. Using Winston, we have the ability to define specific formats for how we want each line in our log file to be displayed.

Image for post
Image for post

We’re doing a couple of things here. Using the combine() function, we can fuze multiple custom formats into one. The first format we are applying is color. Using colorize(), we give different log levels (‘info’, ‘debug’, ‘error’, etc.) different colors. This makes it easier to differentiate error logs from other, non-catastrophic, logs. Timestamp() gives us access to a string which contains the current date and time. This is useful information when determining when a certain issue occurred. The Align() function adds a \t delimiter (tab) to the beginning of each message, which ensures the logs are aligned properly, while the printf() function allows us to define a structure for our log messages. In this case, we would output the current timestamp, followed by the log level and the specific message.

Now that our custom format is defined, we can create our logger.

Image for post
Image for post

Here, we use the add() function to define a new logger with a category of “customLogger” (this could be anything). For format, we pass in the custom format we created earlier. We also provide two transports (read: storage locations). The first is a winston-daily-file-rotate instance. Using this package, we can specify a condition for our log files to rotate. In this case, we are saying that we want our logs to be written to a new log file every day. We could have specified for the logs to be written to a new log file every hour, or after the current file exceeds a specific size, etc. Most of the time, however, having one log file for each day is sufficient. Log file rotation ensures that we do not have all logs being written to a single file. This is important as it allows for logs to be easily-separated by date. It’s also much safer than storing all logs in a single file. Mistakes happen, and a log file might be accidentally deleted on any given Monday. Our second transport is the Console itself (read: Console.log()), meaning that the custom-formatted logs will be displayed there too.

We can now proceed to use the defined logger to log messages to both a file and the Console. To do so, we simply create a new variable and “get” the logger we just created, using the category (you can get and use the defined logger in any file in your project, as long as the Winston package is imported in that file).

Image for post
Image for post

Now, the only thing left to do is to start logging!

Image for post
Image for post

Here, we use logger.info(), since we defined our log transports to store all “info” level logs. Alternatively, we could have also used logger.log(), and specify both the level and the message. For example:

Here’s how our custom logs would look:

Image for post
Image for post

For a more complete example, I’ve implemented this same solution here, using Loopback4. Thanks for reading!

Interested in Computer Science, education and world domination.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store