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.
npm i winston
npm i winston-daily-rotate-file
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.
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.
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).
Now, the only thing left to do is to start logging!
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:
this.logger.log('info', 'this is the example!');
Here’s how our custom logs would look:
For a more complete example, I’ve implemented this same solution here, using Loopback4. Thanks for reading!