DEV Community

Cover image for Winston Logger - Full tutorial with a sample Nodejs application
Ankit Anand ✨ for SigNoz

Posted on • Originally published at signoz.io

Winston Logger - Full tutorial with a sample Nodejs application

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.

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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' });
Enter fullscreen mode Exit fullscreen mode

Prerequisites

Getting Started with Winston Logger

Create a node project in the current directory:

mkdir winston-nodejs-example
cd winston-nodejs-example 
Enter fullscreen mode Exit fullscreen mode

Initialize an npm project:

npm init -y
Enter fullscreen mode Exit fullscreen mode

Install express and winston packages:

npm i winston express
Enter fullscreen mode Exit fullscreen mode

Create an entry file called index.js file:

touch index.js
Enter fullscreen mode Exit fullscreen mode

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}`);
});
Enter fullscreen mode Exit fullscreen mode

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

node index.js
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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

/node_modules
/index.js
/logger.js
/package-lock.json
/package.json
Enter fullscreen mode Exit fullscreen mode

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}`);
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}"
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Running the app

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

docker compose up --build
Enter fullscreen mode Exit fullscreen mode

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

Top comments (8)

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

I like it. I did not know about SigNoz, though. I'll check it out. Personally I like Seq. Something Seq has that SigNoz may not is dashboards. You can run Seq in Docker too.

Collapse
 
ankit01oss profile image
Ankit Anand ✨

Hi Jose,

Can you elaborate more about the use case of dashboards? Would love to improve our experience.

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

Basically this feature.

Thread Thread
 
ankit01oss profile image
Ankit Anand ✨

Thanks for sharing, I will check it out.

Collapse
 
pavanbelagatti profile image
Pavan Belagatti

Interesting article. Nicely written to understand how logs can be collected and analysed through SigNoz

Collapse
 
ankit01oss profile image
Ankit Anand ✨

Thanks Pavan!

Collapse
 
pranay01 profile image
Pranay Prateek

Nice article

Collapse
 
slidenerd profile image
slidenerd

go with pino or roarr , i dont think winston is in fashion anymore