A Complete Guide to Winston Logging in Node.js | Better Stack Community (2024)

Winston is the most popular logginglibrary for Node.js. It aims to make logging more flexible and extensible bydecoupling different aspects such as log levels, formatting, and storage so thateach API is independent and many combinations are supported. It also usesNode.js streams to minimize theperformance impact of implementing logging in your application.

In this tutorial, we will explain how to install, set up, and use theWinston logger in a Node.js application.We'll go through all the options it provides and show how to customize them invarious ways. Finally, we'll describe how to use it in conjunction withMorgan middleware for logging incomingrequests in Express server.

Prerequisites

Before proceeding with the rest of this article, ensure that you have a recentversion of Node.js and npm installedlocally on your machine. This article also assumes that you are familiar withthe basic concepts of logging in Node.js.

The final step in this tutorial discusses how to configure Winston to send logsin a centralized platform. This step is optional but recommended so that you cancollect logs from multiple servers and consolidate the data in one place foreasy access. You'll need to sign up for a freeLogtail account if that's something you'reinterested in learning about.

Also, note that all the provided examples in this article are accurate forWinston 3.x. We will also endeavor to keep this article up to date formajor releases of the framework in the future.

Side note: Get a Node.js logs dashboard

Save hours of sifting through Node.js logs. Centralize withBetter Stackand start visualizing your log data in minutes.

See the Node.js demo dashboard live.

Getting started with Winston

Winston is available as an npm package,so you can install it through the command below.

Copied!

npm install winston

Next, import it into your Node.js application:

Copied!

const winston = require('winston');

Although Winston provides a default logger that you can use by calling a levelmethod on the winston module, it the recommended way to use it is to create acustom logger through the createLogger() method:

Copied!

const winston = require('winston');const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [new winston.transports.Console()],});

Afterwards, you can start logging through methods on the logger object:

Copied!

logger.info('Info message');logger.error('Error message');logger.warn('Warning message');

This yields the following output:

Output

{"level":"info","message":"Info message"}{"level":"error","message":"Error message"}{"level":"warn","message":"Warning message"}

In subsequent sections, we'll examine all the properties that you can use tocustomize your logger so that you will end up with an optimal configurationfor your Node.js application.

Log levels in Winston

Winston supports six log levels by default. It followsthe order specified by theRFC5424 document. Eachlevel is given an integer priority with the most severe being the lowest numberand the least one being the highest.

Copied!

{ error: 0, warn: 1, info: 2, http: 3, verbose: 4, debug: 5, silly: 6}

The six log levels above each correspond to a method on the logger:

Copied!

logger.error('error');logger.warn('warn');logger.info('info');logger.verbose('verbose');logger.debug('debug');logger.silly('silly');

You can also pass a string representing the logging level to the log() method:

Copied!

logger.log('error', 'error message');logger.log('info', 'info message');

The level property on the logger determines which log messages will beemitted to the configured transports (discussed later). For example, since thelevel property was set to info in the previous section, only log entrieswith a minimum severity of info (or maximum integer priority of 2) will bewritten while all others are suppressed. This means that only the info,warn, and error messages will produce output with the current configuration.

To cause the other levels to produce output, you'll need to change the value oflevel property to the desired minimum. The reason for this configuration is sothat you'll be able to run your application in production at one level (sayinfo) and your development/testing/staging environments can be set to a lesssevere level like debug or silly, causing more information to be emitted.

The accepted best practice for setting a log level is to use an environmentalvariable. This is done to avoid modifying the application code when the loglevel needs to be changed.

Copied!

const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.json(), transports: [new winston.transports.Console()],});

With this change in place, the application will log at the info level if theLOG_LEVEL variable is not set in the environment.

Customizing log levels in Winston

Winston allows you to readily customize the log levels to your liking. Thedefault log levels are defined in winston.config.npm.levels, but you can alsouse the Syslog levelsthrough winston.config.syslog.levels:

Copied!

const logger = winston.createLogger({ levels: winston.config.syslog.levels, level: process.env.LOG_LEVEL || 'info', format: winston.format.cli(), transports: [new winston.transports.Console()],});

The syslog levels are shown below:

Copied!

{ emerg: 0, alert: 1, crit: 2, error: 3, warning: 4, notice: 5, info: 6, debug: 7}

Afterwards, you can log using the methods that correspond to each defined level:

Copied!

winston.emerg("Emergency");winston.crit("Critical");winston.warning("Warning");

If you prefer to change the levels to a completely custom system, you'll need tocreate an object and assign a number priority to each one starting from the mostsevere to the least. Afterwards, assign that object to the levels property inthe configuration object passed to the createLogger() method.

Copied!

const logLevels = { fatal: 0, error: 1, warn: 2, info: 3, debug: 4, trace: 5,};const logger = winston.createLogger({ levels: logLevels, level: process.env.LOG_LEVEL || 'info', format: winston.format.json(), transports: [new winston.transports.Console()],});

You can now use methods on the logger that correspond to your custom loglevels as shown below:

Copied!

logger.fatal('fatal!');logger.trace('trace!');

Formatting your log messages

Winston outputs its logs in JSON format by default, but it also supports otherformats which are accessible on the winston.format object. For example, ifyou're creating a CLI application, you may want to switch this to the cliformat which will print a color coded output:

Copied!

const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.cli(), transports: [new winston.transports.Console()],});
A Complete Guide to Winston Logging in Node.js | Better Stack Community (1)
A Complete Guide to Winston Logging in Node.js | Better Stack Community (2)

The formats available in winston.format are defined in thelogform module. This module provides theability to customize the format used by Winston to your heart's content. You cancreate a completely custom format or modify an existing one to add newproperties.

Here's an example that adds a timestamp field to the each log entry:

Copied!

const winston = require('winston');const { combine, timestamp, json } = winston.format;const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: combine(timestamp(), json()), transports: [new winston.transports.Console()],});

When you use a level method on the logger, you'll see a datetime valueformatted as new Date().toISOString().

Copied!

logger.info('Info message')

Output

{"level":"info","message":"Info message","timestamp":"2022-01-25T15:50:09.641Z"}

You can change the format of this datetime value by passing an object totimestamp() as shown below. The string value of the format property belowmust be one acceptable by thefecha module.

Copied!

timestamp({ format: 'YYYY-MM-DD hh:mm:ss.SSS A', // 2022-01-25 03:23:10.350 PM})

You can also create a entirely different format as shown below:

Copied!

const winston = require('winston');const { combine, timestamp, printf, colorize, align } = winston.format;const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: combine( colorize({ all: true }), timestamp({ format: 'YYYY-MM-DD hh:mm:ss.SSS A', }), align(), printf((info) => `[${info.timestamp}] ${info.level}: ${info.message}`) ), transports: [new winston.transports.Console()],});logger.info('Info message');logger.error('Error message');logger.warn('Warning message');

The combine() method merges multiple formats into one, while colorize()assigns colors to the different log levels so that each level is easilyidentifiable. The timestamp() method outputs a datatime value that correspondsto the time that the message was emitted. The align() method aligns the logmessages, while printf() defines a custom structure for the message. In thiscase, it outputs the timestamp and log level followed by the message.

Output

[2022-01-27 06:37:27.653 AM] info: Info message[2022-01-27 06:37:27.656 AM] error: Error message[2022-01-27 06:37:27.656 AM] warn: Warning message

While you can format your log entries in any way you wish, the recommendedpractice for server applications is to stick with a structured logging format(like JSON) so that your logs can be easily machine readable for filtering andgathering insights.

Configuring transports in Winston

Transports in Winston refer to the storage location for your log entries.Winston provides great flexibility in choosing where you want your log entriesto be outputted to. The following transport options are available in Winston bydefault:

  • Console:output logs to the Node.js console.
  • File:store log messages to one or more files.
  • HTTP:stream logs to an HTTP endpoint.
  • Stream:output logs to any Node.js stream.

Thus far, we've demonstrated the defaultConsole transportto output log messages to the Node.js console. Let's look at how we can storelogs in a file next.

Copied!

const winston = require('winston');const { combine, timestamp, json } = winston.format;const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: combine(timestamp(), json()), transports: [ new winston.transports.File({ filename: 'combined.log', }), ],});logger.info('Info message');logger.error('Error message');logger.warn('Warning message');

The snippet above configures the logger to output all emitted log messages toa file named combined.log. When you run the snippet above, this file will becreated with the following contents:

Copied!

{"level":"info","message":"Info message","timestamp":"2022-01-26T09:38:17.747Z"}{"level":"error","message":"Error message","timestamp":"2022-01-26T09:38:17.748Z"}{"level":"warn","message":"Warning message","timestamp":"2022-01-26T09:38:17.749Z"}

In a production application, it may not be ideal to log every single messageinto a single file as that will make filtering critical issues harder since itwill be mixed together with inconsequential messages. A potential solution wouldbe to use two File transports, one that logs all messages to a combined.logfile, and another that logs messages with a minimum severity of error to aseparate file.

Copied!

const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: combine(timestamp(), json()), transports: [ new winston.transports.File({ filename: 'combined.log', }), new winston.transports.File({ filename: 'app-error.log', level: 'error', }), ],});

With this change in place, all your log entries will still be outputted to thecombined.log file, but a separate app-error.log will also be created and itwill contain only error messages. Note that the level property on theFile() transport signifies the minimum severity that should be logged to thefile. If you change it from error to warn, it means that any message with aminimum severity of warn will be logged to the app-error.log file (warnand error levels in this case).

A common need that Winston does not enable by default is the ability to log eachlevel into different files so that only info messages go to an app-info.logfile, debug messages into an app-debug.log file, and so on (seethis GitHub issue). To getaround this, use a custom format on the transport to filter the messages bylevel. This is possible on any transport (not just File), since they allinherit fromwinston-transport.

Copied!

const errorFilter = winston.format((info, opts) => { return info.level === 'error' ? info : false;});const infoFilter = winston.format((info, opts) => { return info.level === 'info' ? info : false;});const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: combine(timestamp(), json()), transports: [ new winston.transports.File({ filename: 'combined.log', }), new winston.transports.File({ filename: 'app-error.log', level: 'error', format: combine(errorFilter(), timestamp(), json()), }), new winston.transports.File({ filename: 'app-info.log', level: 'info', format: combine(infoFilter(), timestamp(), json()), }), ],});

The above code logs only error messages in the app-error.log file and infomessages to the app-info.log file. What happens is that the custom functions(infoFilter() and errorFilter()) checks the level of a log entry and returnsfalse if it doesn't match the specified level which causes the entry to beomitted from the transport. You can customize this further or create otherfilters as you see fit.

Log rotation in Winston

Logging into files can quickly get out of hand if you keep logging to the samefile as it can get extremely large and become cumbersome to manage. This iswhere the concept of log rotationcan come in handy. The main purpose of log rotation is to restrict the size ofyour log files and create new ones based on some predefined criteria. Forexample, you can create a new log file every day and automatically delete thoseolder than a time period (say 30 days).

Winston provides thewinston-daily-rotate-file modulefor this purpose. It is a transport that logs to a rotating file that isconfigurable based on date or file size, while older logs can be auto deletedbased on count or elapsed days. Go ahead and install it through npm as shownbelow:

Copied!

npm install winston-daily-rotate-file

Once installed, it may be used to replace the default File transport as shownbelow:

Copied!

const winston = require('winston');require('winston-daily-rotate-file');const { combine, timestamp, json } = winston.format;const fileRotateTransport = new winston.transports.DailyRotateFile({ filename: 'combined-%DATE%.log', datePattern: 'YYYY-MM-DD', maxFiles: '14d',});const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: combine(timestamp(), json()), transports: [fileRotateTransport],});

The datePattern property controls how often the file should be rotated (everyday), and the maxFiles property ensures that log files that are older than 14days are automatically deleted. You can also listen for the following events ona rotating file transport if you want to perform some action on cue:

Copied!

// fired when a log file is createdfileRotateTransport.on('new', (filename) => {});// fired when a log file is rotatedfileRotateTransport.on('rotate', (oldFilename, newFilename) => {});// fired when a log file is archivedfileRotateTransport.on('archive', (zipFilename) => {});// fired when a log file is deletedfileRotateTransport.on('logRemoved', (removedFilename) => {});

Custom transports in Winston

Winston supports the ability to create your own transports or utilize onemade by the community. A customtransport may be used to store your logs in a database, log management tool, orsome other location. Here are some custom transports that you might want tocheck out:

Winston supports the addition of metadata to log messages. You can add defaultmetadata to all log entries, or specific metadata to individual logs. Let'sstart with the former which can be added to a logger instance through thedefaultMeta property:

Copied!

const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', defaultMeta: { service: 'admin-service', }, format: winston.format.json(), transports: [new winston.transports.Console()],});logger.info('Info message');logger.error('Error message');

When you log any message using the logger above, the contents of thedefaultMeta object will be injected into each entry:

Output

{"level":"info","message":"Info message","service":"admin-service"}{"level":"error","message":"Error message","service":"admin-service"}

This default metadata can be used to differentiate log entries by service orother criteria when logging to the same location from different services.

Another way to add metadata to your logs is by creating a child logger throughthe child method. This is useful if you want to add certain metadata thatshould be added to all log entries in a certain scope. For example, if you add arequestId to your logs entries, you can search your logs and find the all theentries that pertain to a specific request.

Copied!

const childLogger = logger.child({ requestId: 'f9ed4675f1c53513c61a3b3b4e25b4c0' });childLogger.info('Info message');childLogger.info('Error message');

Output

{"level":"info","message":"Info message","requestId":"f9ed4675f1c53513c61a3b3b4e25b4c0","service":"admin-service"}{"level":"error","message":"Error message","requestId":"f9ed4675f1c53513c61a3b3b4e25b4c0","service":"admin-service"}

A third way to add metadata to your logs is to pass an object to the levelmethod at its call site:

Copied!

const childLogger = logger.child({ requestId: 'f9ed4675f1c53513c61a3b3b4e25b4c0' });childLogger.info('File uploaded successfully', { file: 'something.png', type: 'image/png', userId: 'jdn33d8h2',});

Output

{"file":"something.png","level":"info","message":"File uploaded successfully","requestId":"f9ed4675f1c53513c61a3b3b4e25b4c0","service":"admin-service","type":"image/png","userId":"jdn33d8h2"}

Logging errors in Winston

One of the most surprising behaviors of Winston for newcomers to the library isin its way of handling errors. Logging an instance of the Error object resultsin an empty message:

Copied!

const winston = require("winston");const { combine, timestamp, json } = winston.format;const logger = winston.createLogger({ level: "info", format: combine(timestamp(), json()), transports: [new winston.transports.Console()],});logger.error(new Error("an error"));

Output

{"level":"error","timestamp":"2022-07-03T19:58:26.516Z"}

Notice how the message property is omitted, and other properties of theError (like its name and stack) are also not included in the output. Thiscan result in a nightmare situation where errors in production are not recordedleading to lost time when troubleshooting.

Fortunately, you can fix this issue by importing and specifying the errorsformat as shown below:

Copied!

const winston = require("winston");const { combine, timestamp, json, errors } = winston.format;const logger = winston.createLogger({ level: "info", format: combine(errors({ stack: true }), timestamp(), json()), transports: [new winston.transports.Console()],});logger.error(new Error("an error"));

You will now get the proper output:

Output

{"level":"error","message":"an error","stack":"Error: an error\n at Object.<anonymous> (/home/ayo/dev/betterstack/betterstack-community/demo/snippets/main.js:9:14)\n at Module._compile (node:internal/modules/cjs/loader:1105:14)\n at Module._extensions..js (node:internal/modules/cjs/loader:1159:10)\n at Module.load (node:internal/modules/cjs/loader:981:32)\n at Module._load (node:internal/modules/cjs/loader:827:12)\n at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)\n at node:internal/main/run_main_module:17:47","timestamp":"2022-07-03T20:11:23.303Z"}

Handling uncaught exceptions and uncaught promise rejections

Winston provides the ability to automatically catch and log uncaught exceptionsand uncaught promise rejections on a logger. You'll need to specify thetransport where these events should be emitted to through theexceptionHandlers and rejectionHandlers properties respectively:

Copied!

const winston = require('winston');const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.json(), transports: [new winston.transports.Console()], exceptionHandlers: [ new winston.transports.File({ filename: 'exception.log' }), ], rejectionHandlers: [ new winston.transports.File({ filename: 'rejections.log' }), ],});

The logger above is configured to log uncaught exceptions to anexception.log file, while uncaught promise rejections are placed in arejections.log file. You can try this out by throwing an error somewhere inyour code without catching it.

Copied!

throw new Error('An uncaught error');

You'll notice that the entire stack trace is included in the log entry for theexception, along with the date and message.

Copied!

cat exception.log

Output

{"date":"Mon Jun 06 2022 14:00:03 GMT+0100 (West Africa Standard Time)","error":{},"exception":true,"level":"error","message":"uncaughtException: An uncaught error\nError: An uncaught error\n at Object.<anonymous> (/home/ayo/dev/betterstack/betterstack-community/demo/snippets/main.js:15:7)\n at Module._compile (node:internal/modules/cjs/loader:1105:14)\n at Module._extensions..js (node:internal/modules/cjs/loader:1159:10)\n at Module.load (node:internal/modules/cjs/loader:981:32)\n at Module._load (node:internal/modules/cjs/loader:827:12)\n at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)\n at node:internal/main/run_main_module:17:47","os":{"loadavg":[1.75,0.82,0.95],"uptime":271655.58},"process":{"argv":[". . ."],"cwd":"/home/ayo/dev/betterstack/betterstack-community/demo/snippets","execPath":"/home/ayo/.volta/tools/image/node/18.1.0/bin/node","gid":1000,"memoryUsage":{"arrayBuffers":110487,"external":465350,"heapTotal":11141120,"heapUsed":7620128,"rss":47464448},"pid":421995,"uid":1000,"version":"v18.1.0"},"stack":". . .","trace":[. . .]}

Winston will also cause the process to exit with a non-zero status code once itlogs an uncaught exception. You can change this by setting the exitOnErrorproperty on the logger to false as shown below:

Copied!

const winston = require('winston');const logger = winston.createLogger({ exitOnError: false });// orlogger.exitOnError = false;

Note that the accepted best practice is to exit immediately after an uncaughterror is detected as the program will be in an undefined state, so the aboveconfiguration is not recommended. Instead, you should let your program crash andset up a Node.js process manager (such as PM2) to restart itimmediately while setting up some alerting mechanism to notify you of theproblem (see section onCentralizing Logs below).

Profiling your Node.js code with Winston

Winston also provides basic profiling capabilities on any logger through theprofile() method. You can use it to collect some basic performance data inyour application hotspots in the absence of specialized tools.

Copied!

// start a timerlogger.profile('test');setTimeout(() => { // End the timer and log the duration logger.profile('test');}, 1000);

The code above produces the following output:

Output

{"durationMs":1001,"level":"info","message":"test"}

You can also use the startTimer() method on a logger instance to create anew timer and store a reference to it in a variable. Then use the done()method on the timer to halt it and log the duration:

Copied!

// start a timerconst profiler = logger.startTimer();setTimeout(() => { // End the timer and log the duration profiler.done({ message: 'Logging message' });}, 1000);

Output

{"durationMs":1001,"level":"info","message":"Logging message"}

The durationMs property contains the timers' duration in milliseconds. Also,log entries produced by the Winston profiler are set to the info level bydefault, but you can change this by setting the level property in the argumentto profiler.done() or logger.profile():

Copied!

profiler.done({ message: 'Logging message', level: 'debug' });// orlogger.profile('test', { level: 'debug' });

Working with multiple loggers in Winston

A large application will often have multiple loggers with different settings forlogging in different areas of the application. This is exposed in Winstonthrough winston.loggers:

loggers.js

Copied!

const winston = require('winston');winston.loggers.add('serviceALogger', { level: process.env.LOG_LEVEL || 'info', defaultMeta: { service: 'service-a', }, format: winston.format.logstash(), transports: [ new winston.transports.File({ filename: 'service-a.log', }), ],});winston.loggers.add('serviceBLogger', { level: process.env.LOG_LEVEL || 'info', defaultMeta: { service: 'service-b', }, format: winston.format.json(), transports: [new winston.transports.Console()],});

The serviceALogger shown above logs to a service-a.log file using thebuilt-in Logstash format, while serviceBLogger logs to the console using theJSON format. Once you've configured the loggers for each service, you can accessthem in any file using the winston.loggers.get() provided that you import theconfiguration preferably in the entry file of the application:

main.js

Copied!

require('./loggers.js');const winston = require('winston');const serviceALogger = winston.loggers.get('serviceALogger');const serviceBLogger = winston.loggers.get('serviceBLogger');serviceALogger.error('logging to a file');serviceBLogger.warn('logging to the console');

Copied!

node main.js

Output

{"level":"warn","message":"logging to the console","service":"service-b"}

Copied!

cat service-a.log

Output

{"@fields":{"level":"error","service":"service-a"},"@message":"logging to a file"}

Logging in an Express application using Winston and Morgan

Morgan is an HTTP request loggermiddleware for Express that automatically logs thedetails of incoming requests to the server (such as the remote IP Address,request method, HTTP version, response status, user agent, etc.), and generatethe logs in the specified format. The main advantage of using Morgan is that itsaves you the trouble of writing a custom middleware for this purpose.

Here's a simple program demonstrating Winston and Morgan being used together inan Express application. When you connect Morgan with a Winston logger, all yourlogging is formatted the same way and goes to the same place.

server.js

Copied!

const winston = require('winston');const express = require('express');const morgan = require('morgan');const axios = require('axios');const app = express();const { combine, timestamp, json } = winston.format;const logger = winston.createLogger({ level: 'http', format: combine( timestamp({ format: 'YYYY-MM-DD hh:mm:ss.SSS A', }), json() ), transports: [new winston.transports.Console()],});const morganMiddleware = morgan( ':method :url :status :res[content-length] - :response-time ms', { stream: { // Configure Morgan to use our custom logger with the http severity write: (message) => logger.http(message.trim()), }, });app.use(morganMiddleware);app.get('/crypto', async (req, res) => { try { const response = await axios.get( 'https://api2.binance.com/api/v3/ticker/24hr' ); const tickerPrice = response.data; res.json(tickerPrice); } catch (err) { logger.error(err); res.status(500).send('Internal server error'); }});app.listen('5000', () => { console.log('Server is running on port 5000');});

The morgan() middleware function takes two arguments: a string describing theformat of the log message and the configuration options for the logger. Theformat is composed up ofindividual tokens, which can becombined in any order. You can also use apredefined formatinstead. In the second argument, the stream property is set to log theprovided message using our custom logger at the http severity level. Thisreflects the severity that all Morgan events will be logged at.

Before you execute this code, install all the required dependencies first:

Copied!

npm install winston express morgan axios

Afterward, start the server and send requests to the /crypto route through thecommands below:

Copied!

node server.js

Copied!

curl http://localhost:5000/crypto

You should observe the following output:

Output

{"level":"http","message":"GET /crypto 200 1054176 - 3553.998 ms","timestamp":"2022-06-06 03:51:56.336 PM"}{"level":"http","message":"GET /crypto 200 1054224 - 2745.956 ms","timestamp":"2022-06-06 03:52:00.530 PM"}{"level":"http","message":"GET /crypto 200 1054214 - 3605.577 ms","timestamp":"2022-06-06 03:52:10.774 PM"}

Notice that the all the request metadata outputted by Morgan is placed as astring in the message property which makes it harder to search and filter.Let's configure Morgan such that the message string will be a stringified JSONobject.

server.js

Copied!

. . .const morganMiddleware = morgan( function (tokens, req, res) { return JSON.stringify({ method: tokens.method(req, res), url: tokens.url(req, res), status: Number.parseFloat(tokens.status(req, res)), content_length: tokens.res(req, res, 'content-length'), response_time: Number.parseFloat(tokens['response-time'](req, res)), }); }, { stream: { // Configure Morgan to use our custom logger with the http severity write: (message) => { const data = JSON.parse(message); logger.http(`incoming-request`, data); }, }, });. . .

The string argument to the morgan() function has been changed to a functionthat returns a stringified JSON object. In the write() function, the JSONstring is parsed and the resulting object is passed as metadata to thelogger.http() method. This ensures that each metric is produced as a separateproperty in the log entry.

Restart the server and send requests to the /crypto route once again. You'llobserve the following output:

Output

{"content_length":"1054555","level":"http","message":"incoming-request","method":"GET","response_time":2974.763,"status":200,"timestamp":"2022-06-06 08:46:41.267 PM","url":"/crypto"}

Centralizing your logs in the cloud

We already discussed logging to files in a previous section of this tutorial.It's a great way to persist your log entries for later examination, but may beinsufficient for distributed applications running on multiple servers sincelooking at the logs of a single server may not longer enough to locate anddiagnose problems.

A widely employed solution in such cases is to collect the logs from individualservers and centralize them one place so that its easy to get a complete pictureon any issue. There are several solutions for aggregating logs such as the opensourceElasticsearch, Logstash, and Kibana (ELK) stackbut a reliable setup can be convoluted especially if you're opting for aself-hosted solution.

The simplest, and often more cost-effective way to centralized logs is to use acloud-based service. Most services offer log filtering, alerting, and an optionfor unlimited log storage which could help with spotting long-term trends. Inthis section, we will briefly discuss how to send Node.js application logs toone such service (Logtail) when using theWinston logger. We'll demonstrate this process using the server.js examplefrom the previous section.

The first step involves creating or logging in to yourLogtail account, then find the Sourcesoption in the left-hand menu and click the Connect source button on theright.

A Complete Guide to Winston Logging in Node.js | Better Stack Community (3)
A Complete Guide to Winston Logging in Node.js | Better Stack Community (4)

Give your source a name and select the Node.js platform, then click theCreate source button.

A Complete Guide to Winston Logging in Node.js | Better Stack Community (5)
A Complete Guide to Winston Logging in Node.js | Better Stack Community (6)

Once your Logtail source is created, copy the Source token to yourclipboard.

A Complete Guide to Winston Logging in Node.js | Better Stack Community (7)
A Complete Guide to Winston Logging in Node.js | Better Stack Community (8)

Return to your terminal and install the@logtail/node and@logtail/winston packages inyour Node.js project:

Copied!

npm install @logtail/node @logtail/winston

Afterward, make the following changes to your server.js file:

server.js

Copied!

. . .const axios = require('axios');

const { Logtail } = require('@logtail/node');

const { LogtailTransport } = require('@logtail/winston');

const logtail = new Logtail('<your_source_token>');

const { combine, timestamp, json } = winston.format;const app = express();const logger = winston.createLogger({ level: 'http', format: combine( timestamp({ format: 'YYYY-MM-DD hh:mm:ss.SSS A', }), json() ),

transports: [new winston.transports.Console(), new LogtailTransport(logtail)],

});. . .

The Logtail class is imported and initialized with your source token (replacethe <your_source_token> placeholder). In a real application, you should use anenvironmental variable to control the source token value. The LogtailTransportclass is a Winston compatible transport that transmits Winston logs to Logtailwhen initialized in the transports array as above.

After saving the server.js file above, restart your server and send a fewrequests to the /crypto route. Your logs will continue to appear in theconsole as before, but it will also sync to Logtail.com in realtime. Head overto your browser and click the Live tail link under your source name, or youcan click the Live tail menu entry on the left and filter the logs using thedropdown on the top left:

A Complete Guide to Winston Logging in Node.js | Better Stack Community (9)
A Complete Guide to Winston Logging in Node.js | Better Stack Community (10)
A Complete Guide to Winston Logging in Node.js | Better Stack Community (11)
A Complete Guide to Winston Logging in Node.js | Better Stack Community (12)

You should observe that your Winston-generated application logs are comingthrough as expected. You can click a log entry to expand its properties:

A Complete Guide to Winston Logging in Node.js | Better Stack Community (13)
A Complete Guide to Winston Logging in Node.js | Better Stack Community (14)

From this point onwards, all your application logs from any server orenvironment will be centralized in Logtail. You can easily apply filters to findthe information you need or set up alerts to notify you whenever your logs matchcertain conditions. For more information on integrating Logtail in yourapplication, explore the full documentation.

Conclusion

In this article, we've examined the Winston logging package for Node.jsapplications. It provides everything necessary in a loggingframework, such as structured (JSON) logging, coloredoutput, log levels, and the ability to log to several locations. It also has asimple API and is easy to customize which makes it a suitable solution for anytype of project.

We hope this article has helped you learn about everything that you can do withWinston, and how it may be used to develop a good logging system in yourapplication. Don't forget to check out it'sofficial documentation pages to learnmore.

Thanks for reading, and happy coding!

A Complete Guide to Winston Logging in Node.js | Better Stack Community (15)

Article by

Ayooluwa Isaiah

Ayo is the Head of Content at Better Stack. His passion is simplifying and communicating complex technical ideas effectively. His work was featured on several esteemed publications including LWN.net, Digital Ocean, and CSS-Tricks. When he’s not writing or coding, he loves to travel, bike, and play tennis.

Got an article suggestion?Let us know

Next article

11 Best Practices for Logging in Node.jsIf you want to improve visibility into your Node.js application, the best place to start is by implementing logging best practices.→

A Complete Guide to Winston Logging in Node.js | Better Stack Community (16)

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

A Complete Guide to Winston Logging in Node.js | Better Stack Community (2024)
Top Articles
Require Re-Register Multi Factor Authentication (MFA)
Reminder: Life insurance coverage decreases at 70
Walgreens Harry Edgemoor
El Paso Pet Craigslist
PontiacMadeDDG family: mother, father and siblings
Rabbits Foot Osrs
Martha's Vineyard Ferry Schedules 2024
Owatc Canvas
Best Cheap Action Camera
Tlc Africa Deaths 2021
Vichatter Gifs
Detroit Lions 50 50
Dusk
Bros Movie Wiki
California Department of Public Health
Bjork & Zhulkie Funeral Home Obituaries
Craigslist Deming
使用 RHEL 8 时的注意事项 | Red Hat Product Documentation
24 Hour Drive Thru Car Wash Near Me
Craigslist Free Stuff Merced Ca
12 Top-Rated Things to Do in Muskegon, MI
67-72 Chevy Truck Parts Craigslist
Aol News Weather Entertainment Local Lifestyle
Employee Health Upmc
Mybiglots Net Associates
The best brunch spots in Berlin
Yale College Confidential 2027
1636 Pokemon Fire Red U Squirrels Download
Vivification Harry Potter
How rich were the McCallisters in 'Home Alone'? Family's income unveiled
Kaiser Infozone
Craigslist Free Stuff San Gabriel Valley
Here’s how you can get a foot detox at home!
Hermann Memorial Urgent Care Near Me
New York Rangers Hfboards
Hotels Near New Life Plastic Surgery
2008 Chevrolet Corvette for sale - Houston, TX - craigslist
KITCHENAID Tilt-Head Stand Mixer Set 4.8L (Blue) + Balmuda The Pot (White) 5KSM175PSEIC | 31.33% Off | Central Online
Aliciabibs
Ishow Speed Dick Leak
Maxpreps Field Hockey
Tiny Pains When Giving Blood Nyt Crossword
Main Street Station Coshocton Menu
Compare Plans and Pricing - MEGA
The best bagels in NYC, according to a New Yorker
Former Employees
Tricare Dermatologists Near Me
Leland Nc Craigslist
BCLJ July 19 2019 HTML Shawn Day Andrea Day Butler Pa Divorce
Matt Brickman Wikipedia
2294141287
Tyrone Unblocked Games Bitlife
Latest Posts
Article information

Author: Kerri Lueilwitz

Last Updated:

Views: 6309

Rating: 4.7 / 5 (67 voted)

Reviews: 90% of readers found this page helpful

Author information

Name: Kerri Lueilwitz

Birthday: 1992-10-31

Address: Suite 878 3699 Chantelle Roads, Colebury, NC 68599

Phone: +6111989609516

Job: Chief Farming Manager

Hobby: Mycology, Stone skipping, Dowsing, Whittling, Taxidermy, Sand art, Roller skating

Introduction: My name is Kerri Lueilwitz, I am a courageous, gentle, quaint, thankful, outstanding, brave, vast person who loves writing and wants to share my knowledge and understanding with you.