DEV Community

Cover image for Getting started with Winston Logger; A beginner's guide
Ugbem Job
Ugbem Job

Posted on

Getting started with Winston Logger; A beginner's guide

Logging Definition

  • Logging is the process of recording application actions, activities and state to a secondary interface.

  • Logging is the process of recording application activities into log files. Data saved in a log file are called logs and are usually identified with the .log extension (some people use other extension preferences)

In this article, you will discover how to use - Winston to log your application activities into files instead of logging to the console.

Why Do you need to Log Data

Why do we need to Log application activities you may ask;
Well, logging;

  • Helps us know when something or an issue occurs with our app, especially when it's in production mode.
  • Helps monitor and Keep track of your system activities.
  • Helps persist data so you can view this data for analysis later

Let’s begin with our Winston Logger

In this tutorial, we will need an ExpressJS application running on our machine and one prerequisite to use express is to have Node installed on your machine.

If you do not want to go through the entire process, you can clone this repository for the complete code.

Let's Dive In

  • Open up your terminal on your desktop or preferred folder location.

I have a special folder where I keep all tutorial related files so I’ll open it up on my terminal.

Within your folder dir, create a new folder, I'll call mine winston-tut and initialize node with either yarn or npm (I’ll be using yarn).

mkdir winston-tut
cd winston-tut
yarn init -y
Enter fullscreen mode Exit fullscreen mode

Open it up with your preferred code editor ( I’ll be using code-insiders ).

code-insider ./
Enter fullscreen mode Exit fullscreen mode

After that, we’ll have to install express, winston and dotenv

 yarn add express winston dotenv
Enter fullscreen mode Exit fullscreen mode

Also, we’ll need to install - nodemon as a dev dependency, so as to be able to restart our server automatically in dev mode.

yarn add -D nodemon
Enter fullscreen mode Exit fullscreen mode

Also, you will have to modify your package.json file to be able to use ECMAScript 6 modulejs.

Ensure your node is version 14 or higher. You can check for the version of your node on your terminal with the command node -v or node --version. You can download the current node here.

  • Open your package.json and simply add the following;
 “type”:”module”,
 “scripts”:{
   “start:dev”:nodemon app”,
   “start”:”node app”
   },
Enter fullscreen mode Exit fullscreen mode
  • Create a new file from your terminal in your working dir with touch app.js where you'll spin up your express server.
  • Add the following code to your app.js
import Express from "express";
const app = Express();
const port = process.env.PORT || 3000;
app.listen(port, () => {
    console.log(`App running on port ${ port }`);
})
Enter fullscreen mode Exit fullscreen mode

Run yarn start:dev to start the server in dev mode.

  • Create another file winston.js. This is where we'll write our code for the logger.
  import winston from 'winston'
  const { transports, format, createLogger } = winston
  const { combine, printf } = format
Enter fullscreen mode Exit fullscreen mode

For your reference

  • Winston requires at least one transport to create a log. A transport is where the log is saved. Read more on transports
  • This allows flexibility when writing your own transports in case you wish to include a default format with your transport. Read more on format
  • Since we want our logger to be in a readable human format, we'll have to do some custom winston configuration
// ..
// ..
const customLog = printf(({ level, message }) => {
    return `Level:[${ level }] LogTime: [${ logTime }] Message:-[${ message }]`
})

const logger = new createLogger({
    format: combine(customLog), transports: [
       new transports.File({
            level: 'info',
            dirname: 'logs',
            json: true,
            handleExceptions: true,
            filename: `combined.log`
        })
    ], exitOnError: false
})

export default logger
Enter fullscreen mode Exit fullscreen mode

The Logger configuration above logs to a file. We’ll add a transport array to the log configuration object.

Later in this guide, we’ll see how we can log error and other log levels to a file and to the console too.

  • Back to our app.js, let's import our logger
import logger from "./winston.js"
//...
// ...

app.listen(port, () => {
    logger.log('info', `App running on port ${ port }`);
})
Enter fullscreen mode Exit fullscreen mode

From the code above example:

  • Every time the server starts or restarts, Winston will record a log to the combined.log file.

  • Now let's log error level into it's own file for readability and also do some personalization in terms of logging with date and timestamps.

    • Back to our winston.js file we'll write a custom logic.
// ...
// ...

// Create a log time
const logTime = new Date().toLocaleDateString()
const customLog = printf(({ level, message }) => {
    return `Level:[${ level }] LogTime: [${ logTime }] Message:-[${ message }]`
})

// Custom date for logging files with date of occurance
const date = new Date()
const newdate = `${ date.getDate() }-${ date.getMonth() }-${ date.getFullYear() }`

const options = {
    info: {
        level: 'info',
        dirname: 'logs/combibned',
        json: true,
        handleExceptions: true,
        datePattern: 'YYYY-MM-DD-HH',
        filename: `combined-${ newdate }.log`,
    },
    error: {
        level: 'error',
        dirname: 'logs/error',
        json: true,
        handleExceptions: true,
        filename: `error-${ newdate }.log`,
    },
    console: {
        level: 'debug',
        json: false,
        handleExceptions: true,
        colorize: true,
    },
}

const logger = new createLogger({
    format: combine(customLog), transports: [
        new transports.File(options.info),
        new transports.File(options.error),
        new transports.Console(options.console)
    ], exitOnError: false
})

Enter fullscreen mode Exit fullscreen mode
  • Back to our app.js, let's import our logger
import logger from "./winston.js"
//...
// ...

logger.error("This is an error log")
logger.warn("This is a warn log")
logger.debug("This is logged to the Console only ")

app.listen(port, () => {
    logger.log('info', `App running on port ${ port }`);
})
Enter fullscreen mode Exit fullscreen mode

Logging to database

  • With winston, it is very easy to log application activities to a database. > I will be logging into a mongo database in this section. I will write on how to do so in other databases soon. > > ### Let's begin

We'll need to install a dependency winston-mongo

yarn add winston-mongo
Enter fullscreen mode Exit fullscreen mode
  • Back to our winston.js file we'll just add few lines of code to our existing logic.
import ("winston-mongodb");

// ..
// ..
// ..

const options = {
dbinfo: {
    level: "info",
    collection: "deliveryLog",
    db: process.env.MONGO_URI,
    options: { useNewUrlParser: true, useUnifiedTopology: true },
    maxsize: 52428800, // 50MB
  },

// ..
// ..

}

const logger = new createLogger({
    format: combine(customLog), transports: [
      // ..
      // ..
      new transports.MongoDB(options.dbinfo),
    ], exitOnError: false
})

Enter fullscreen mode Exit fullscreen mode

And that's all for logging with winston. You can visit winston's github repo for more.

You can view complete code here.

Finally

  • Logging is the best approach to adopt for your production application. Also there are other standard (premium) logging tools out there.

  • Always remember logging is best to be in a readable human format as it helps debugging easier.

You might ask when to log, I'll say it's best to log when your app starts and it's best to log into a seperate database when your app hits production.

Some of the standard logging instances include:

  • Logging when there is an error or the app encounters unexpected exceptions.
  • Logging when a system event takes place.
  • Logging request and responses

section.io

Top comments (8)

Collapse
 
liranfar profile image
Liran Farage

Thanks for sharing it!
Do you know how to flush winston logs proactively in v3? It seems that only ending the logger does it.

Collapse
 
jobizil profile image
Ugbem Job • Edited

Flushing proactively do you mean before the "process.exit()" in your callback runs?

If so, kindly check this out if it might help; stackoverflow.com/questions/187717...

Collapse
 
liranfar profile image
Liran Farage

Just flushing... Whenever I want, not necessarily before exit.

Thread Thread
 
jobizil profile image
Ugbem Job

Okay, I get you. Usually I use cron jobs to flush server logs after 7 days and error logs after 31 days ( I know this might not be be the most effective way during production).

Thread Thread
 
liranfar profile image
Liran Farage

How you do that? What type of method/function you call in order to flush the logs? That's what I couldn't find expect of ending the logger and waiting for a 'finish' event

Thread Thread
 
liranfar profile image
Liran Farage

Just to be clear, flushing the logs to the disk from the memory, something that usually happens behind the scene within the logger components.

Thread Thread
 
jobizil profile image
Ugbem Job • Edited

I have tried checking for this, but I could not get it. Incase you find a solution kindly post it here too, so we'll all learn.

Collapse
 
lexnwimue profile image
lex Nwimue P

Here's my code:

const reportsLogger = createLogger({
  format: format.combine(format.timestamp()),
  transports: [
    new transports.File({
      filename: "logs/users-error.log",
      level: "error",
    }),

    new transports.MongoDB(mongoOptions),
  ],
});
Enter fullscreen mode Exit fullscreen mode

And I keep getting this error:

TypeError: transports.MongoDB is not a constructor
Enter fullscreen mode Exit fullscreen mode