Skip to main content

Winston Logger - Full tutorial with a sample Nodejs application

· 8 min read
Sai Deepesh

Winston Logger is one of the most popular logging libraries for Node.js. It is designed to be a simple and universal logging library supporting multiple modes of transport. A transport is essentially a storage device for the logs.

Cover Image

Each logger can have multiple modes of transport configured at different levels. For example, one may want error logs stored in a database, but all logs output to the console or a local file.

Some of the features of Winston logger are:

  • Logging Levels
  • Transports
  • Formats
  • Profiling

What are logging levels in Winston logger?‚Äč

Logging levels in winston follow the severity ordering specified by RFC5424: severity of all levels is assumed to be numerically ascending from most important to least important.

Each level is given a specific integer priority. The higher the priority, the more important the message is considered, and the lower the corresponding integer priority. For example, as specified in RFC5424 the syslog levels are prioritized from 0 to 7 (highest to lowest).

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

Similarly, npm logging levels are prioritized from 0 to 6 (highest to lowest):

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

What are Transports in Winston logger?‚Äč

A transport is a storage device or output mechanism for our logs. Each Winston logger can have multiple modes of transport configured at different levels.

Winston comes with three core modes of transport: console, file, and HTTP. The transports must be created and added to the loggers.

Here's how we initialize different transports:

  • **Console Transport:**
    logger.add(new winston.transports.Console(options));
  • File Transport:
    logger.add(new winston.transports.File(options));
  • HTTP Transport:
    logger.add(new winston.transports.Http(options));

Formats in Winston logger‚Äč

Formats in winston can be accessed from winston.format. They are implemented in [logform](https://github.com/winstonjs/logform), a separate module from winston. This allows flexibility when writing your transports in case you wish to include a default format with your transport.

In modern versions of node template strings are very performant and are the recommended way for doing most end-user formatting.

Profiling with Winston logger‚Äč

In addition to logging messages and metadata, winston also has a simple profiling mechanism implemented for any logger.

All profile messages are set to the 'info' level by default, and both message and metadata are optional. For individual profile messages, you can override the default log level by supplying a metadata object with a level property:

logger.profile('test', { level: 'debug' });

Prerequisites‚Äč

Getting Started with Winston Logger‚Äč

Create a node project in the current directory:

mkdir winston-nodejs-example
cd winston-nodejs-example

Initialize an npm project:

npm init -y

Install express and winston packages:

npm i winston express

Create an entry file called index.js file:

touch index.js

Create a basic hello world express app:

const express = require("express");
const PORT = process.env.PORT || "5555";
const app = express();

app.use(express.json())

app.get("/", (req, res) => {
res.json({ method: req.method, message: "Hello World", ...req.body });
});

app.get('/404', (req, res) => {
res.sendStatus(404);
})

app.get("/user", (req, res, next) => {
try {
throw new Error("Invalid user");
} catch (error) {
res.status(500).send("Error!");
}
});

app.listen(parseInt(PORT, 10), () => {
console.log(`Listening on http://localhost:${PORT}`);
});

Run the server with the below command and hit http://localhost:5555:

node index.js

If done correctly, the console should show Listening on http://localhost:5555

Basic nodejs express app
Basic nodejs express app

Now, in the current directory, create a logger.js file in which we will be configuring the winston logger:

const {createLogger, format, transports} = require("winston");

const logger = createLogger({
level: "debug",
format: format.json(),
transports: [new transports.Console()],
});

module.exports = logger;

At this point, the project structure should look like this:

/node_modules
/index.js
/logger.js
/package-lock.json
/package.json

Import the logger and use it wherever required. The final index.js after using the logger looks something like this:

const express = require("express");
const logger = require("./logger");
const PORT = process.env.PORT || "5555";
const app = express();

app.use(express.json())

app.get("/", (req, res) => {
logger.log("debug", "Hello, World!"); //debug level as first param
logger.debug("The is the home '/' route.");
// using debug method directly
res.json({ method: req.method, message: "Hello World", ...req.body });
});

app.get('/404', (req, res) => {
logger.error("404 error"); //error method
logger.debug("The is the 404 route.");
res.sendStatus(404);
})

app.get("/user", (req, res) => {
try {
throw new Error("Invalid user");
} catch (error) {
logger.error("Auth Error: invalid user");
logger.debug("The is the user route.");
res.status(500).send("Error!");
}
});

app.listen(parseInt(PORT, 10), () => {
console.log(`Listening on http://localhost:${PORT}`);
});

Logs will be captured depending on the route we hit.

Home route

nodejs express app home route
Nodejs Express app home route

Home route logs

winston logs for home route
winston logs for home route

User error route

Nodejs express error route
Nodejs express error route

User error logs

User Error logs
User error logs

404 route

Nodejs express app 404 route
Nodejs express app 404 route

404 route logs

404 route logs
404 route logs

In a production environment, you will need a log management tool to store and manage your logs efficiently. In this tutorial, we will use SigNoz - an open source APM and observability tool for logs collected by Winston logging library.

Log management in SigNoz‚Äč

SigNoz is full-stack open source Application Performance Monitoring tool that you can use for monitoring logs, metrics, and traces. Having all the important telemetry signals under a single dashboard leads to less operational overhead. Users can also access telemetry data with richer context by correlating these signals.

SigNoz uses a columnar database - ClickHouse, for storing logs efficiently. Big companies like Uber and Cloudflare have shifted from Elasticsearch to ClickHouse for storing their log data.

Sending logs to SigNoz deployed on Docker‚Äč

We will dockerize our nodejs application and run the application in Docker. We will be using the console transport for winston. If SigNoz is running on the same host, it will automatically start collecting logs of all the docker containers.

Installing and running the SigNoz app‚Äč

SigNoz can be installed on macOS or Linux computers in just three steps by using a simple install script.

The install script automatically installs Docker Engine on Linux. However, on macOS, you must manually install Docker Engine before running the install script.

git clone -b main https://github.com/SigNoz/signoz.git
cd signoz/deploy/
./install.sh

You can visit the documentation for instructions on how to install SigNoz using Docker Swarm and Helm Charts.

Deployment Docs

Dockerising the Node app‚Äč

Create a docker-compose.yaml file and paste the following code:

version: "3.9"

services:
app:
container_name: app
image: app
restart: always
build:
context: .
dockerfile: Dockerfile
target: base
ports:
- "${PORT}:${PORT}"

Create a Dockerfile (no file extension needed) and paste the following code:

FROM node:alpine as base

WORKDIR /winston-nodejs-example # current project name

COPY package.json ./

RUN rm -rf node_modules && npm i

COPY . .

CMD ["node", "index.js"]

Before we can deploy our app on a Docker container, we need to set up the environment variable we will need to run the app. Create a file named .env in the root directory of your folder.

Since we defined the port as a variable in the docker-compose.yml file, we need to set the port in the .env file:

PORT=5555

Running the app‚Äč

Finally, we can deploy the Node app on a Docker container. To do so, use Docker Compose:

docker compose up --build

Once the build is successfully run, you should be able to see the following logs on the console.

Nodejs Winston logs on console
Nodejs Winston logs on console

Observing the logs on SigNoz‚Äč

Now, hit the different routes we’ve hit earlier to check the logs i.e /, /404, /user and we should be able to watch the logs in SigNoz as follows.

winston logs with SigNoz
winston logs with SigNoz

winston logs with SigNoz
winston logs with SigNoz

winston logs with SigNoz
winston logs with SigNoz

If SigNoz is installed on a different host, you can collect logs by following these instructions.

Conclusion‚Äč

Logs play an essential part in a developer’s workflow and are critical to debugging applications. Winston is a simple logging library and makes the logging process more flexible and extensible. Once the logs are generated, you can collect them with SigNoz.

SigNoz uses OpenTelemetry to collect logs. With OpenTelemetry, you can also correlate your logs with other telemetry signals like metrics and traces. Having contextual information in your logs can help you debug applications faster. You can get an overview of logs management in SigNoz from the logs documentation.


Related Posts

SigNoz - a lightweight open source ELK alternative

Morgan Logger - Tutorial on how to use in an Express application