Logging is an essential part of application. According to Twelve-Factor App methodology it’s the XI factor of application development. A proper logging gives you a valuable insite of your application. In Node.js application, it is always suggested to use logging libraryrather than just using console.log in productione environment. Log4js, Bunyan , and Winston are the best logging libraries among other libraries. I am not going to discuss here which is the best logging library among them.
In this tutorials I will cover the Winston logging library with Morgan logger middleware. Winston is most popular, simple and versatile logging library for NodeJs application. You can easily customize it, format the log messages and send them any internal or cloud log management tools.
Logging with Winston
Let’s create a custom nodejs app with Winston logging library. Create a blank package.json
file that automatically accepts all the defaults without prompting.
mkdir ~/myapp cd ~/myapp npm init -y |
Install Winston
npm install winston --save |
Now, create a config
folder file with logger.js
file which contains the winston configuraiton.
mkdir ~/myapp/config touch ~/myapp/config/logger.js |
Let’s talk about a little bit about Winston logging library. It supports
- Pre-defined levels as well as custom error levels
- Multiple transports as well as custom transport
- Handle exceptions
- Different log formats as well as custom formats
Transport
Transport is a concept that stores or output the logs into file, console, stream or cloud platform over http. It’s loaded as module while creating an instance of a winston logger. You can use multiple transports at different log level at the same time. What it means actually you can output to console and send to http at the same time. Built-in available transports are Console, File, Http and Stream. There are also some aditional community transports available.
Logging level
Logging level is part of transport which indicate the minimum logging priority that transport will listen out for. Logong level is denoted the by integer number and it follows npm
logging levels.
- 0: error
- 1: warn
- 2: info
- 3: verbose
- 4: debug
- 5: silly
For example, you configure transport with setting logging level to debug: 4.
This means, it will log levels
- 4 (debug)
- 3 (verbose)
- 2 (info)
- 1 (warn)
- 0 (error)
If you set the logging level to info then the logs will show only info messages as well as less than or equal to this level, You wont see the debug or verbose messages. This helps to avoid clutter in the logs and prevents too much info being shown in the logs in a production environment.
Next, define a logger with a very basic winston configurations.
//~/myapp/config/logging.js // Import modules const winston = require('winston'); // Logger configuration const logConfiguration = { exitOnError: false, transports: [ new winston.transports.Console({ level: 'info' }), new winston.transports.File({ level: 'error', filename: `./logs/app.log` }) ] }; // Create the logger const logger = winston.createLogger(logConfiguration); // Export the logger module.exports = logger; |
Let me explain what properties I configured here. In logConfiguration
object, I initiated two transports: console with level: info and file with level: error. File tranporter will log only errors into log/app.log
file at application root. Console transporter will log upto info or any logs less than info level. I also set exitOnError
to false. Means it will not exit after logging an uncaughtException.
Now, I am going to use the logger in my app.js. //~/myapp/app.js const logger = require('./config/logger'); logger.info("hello world!"); |
It print hello world!
in console as below. It doesn’t add any log into app.log file because of log level.
{"message":"hello world!","level":"info"} |
Now, we print another log with error level.
logger.error("error occured"); |
This time, you see following message in console as well into app.log file.
{"message":"hello world!","level":"error"} |
Custom log formats
Another important advantage of Winston is customizing log message format as well as creating your own custom format. By default it supports JSON formatting, coloring log output, and the ability to change with formats. You can add different message formats to diferent transports. Let’s start with an example that adds a timestamp, label, colorizes the level and message, lowercase message content and prints using the template [level] [timestamp-label]: message
.
// Import modules const { createLogger, format, transports } = require('winston'); const { combine, timestamp, label, colorize, printf } = format; const customFormat = printf(({ level, message, label, timestamp }) => { return `$[${level}] [${timestamp}-${label}] ${message.toLowerCase()}`; }); // Logger configuration const logConfiguration = { format: combine( label({ label: 'Custom APP log!' }), timestamp(), colorize({ all: true }), customFormat ), transports: [ new transports.Console({ level: 'info' }) ], exitOnError: false, }; // Create the logger const logger = createLogger(logConfiguration); |
Now if you try again logger.info("HELLO WORLD")
, you will see following message into console.
More details on format: See Winston Format
Send log message to Cloud platform
There are multiple cloud platforms with paid and limited free subscribtions where you can send log messages. I used couple of them, but for this tutorials I chose Papertrail.
// import modules const winston = require('winston'); // Install papertail: npm install winston-papertrail --save // Requiring `winston-papertrail` will expose require('winston-papertrail').Papertrail; const winstonPapertrail = new winston.transports.Papertrail({ level: 'info', colorize: true, host: 'logs.papertrailapp.com', port: 12345 }); // Logger configuration const logConfiguration = { exitOnError: false, transports: [winstonPapertrail] }; // Create the logger const logger = winston.createLogger(logConfiguration); // Export the logger module.exports = logger; |
I almost covered everything about Winston as a proper logger. One more thing I want to add using winston with morgan.
Why Morgan?
Morgan is HTTP request middleware logger for Node.js. It basically simplifies the process of request logging. Most of the time it’s used with Winston to consolidate HTTP request data logs. Morgan supports a handful of pre-defined logged formats with well-know names/structures: combined, common, dev, short, tiny as well as custom format. Beside the format, it also accepts following properties as into its optiont object.
- immediate: set it to true in order to log request even server crashes.
- skip: skip log outputs based on some custom logic
- stream: Output stream for writing log lines. Defaults to
process.stdout
Let’s look at an example of Morgan with Winston logger into an express based Node.js application. By default, it outputs to the console, so let’s define a stream function into Winston so that we can transfer morgan’s output into the Winston log files.
//~/myapp/config/logging.js ... logger.stream = { write: function(message, encoding) { logger.info(message); }, }; // Export the logger module.exports = logger; |
In app.js (Example based on express framework)
const express = require('express'); const bodyParser = require('body-parser'); const app = express(); const morgan = require('morgan'); const logger = require('./config/logger'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(morgan('combined', { immediate: true, stream: logger.stream })); ... |
Morgan supports token. To know more details token and logged format: Morgan NPM Package documentation
In this tutotial, I mostly focused on Winston logger with Morgan HTTP middleware for simplicity. I mentioned before that there are more better logger libraries availabe based on different features. Choice one that best fits into your application. Happy Hacking.
clean article, if we have to send it to AWS ?