Hi guys, long time no see...
Time to learn about another awesome concept: logging
What does Wikipedia say about logging?
In computing, logging is the act of keeping a log of events that occur in a computer system, such as problems, errors, or just information on current operations. A message or log entry is recorded for each such event. These log messages can then be used to monitor and understand the operation of the system, to debug problems, or during an audit. (Logging (computing))
So going by the definition, logging simply means keeping a log of events that occur in a computer system. This can be used to monitor and understand the operation of the system, to debug problems, or during an audit. Many types of logs can be generated by a system, but we will be focusing on Server Logs in this article.
What Are Server Logs?
Server Logs are logs that are generated by a server. These logs are used to monitor and understand the server's operation. Typical examples of server logs are:
- Page Requests Logs
- Error Logs
- Access Logs
- Information Logs, etc.
Logs help in understanding the behavior of the server, how many requests are being made to the server, what kind of requests are being made, what kind of errors are being generated, etc. Understanding the patterns in the logs can help optimize the server's performance and debug issues.
Winston is one such tool that can be used to generate logs in a Node.js application. Its various modifications can be used to generate logs in different formats and at different levels.
Installing Winston
To install Winston, run the following command:
npm install winston
Using Winston
Let's create the simplest logger using Winston:
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.Console()
]
});
// now we can use the logger to log messages as we want
logger.info('Hello, Winston!');
logger.error('An error occurred!');
logger.warn('Warning: This is a warning!');
Output:
Understanding the components of the Winston Logger
We have created a simple logger using Winston. Let's understand the components of the logger:
-
Levels - Levels are used to specify the severity of the log. Winston is pre-configured with the following levels:
- error: Severity 0 - This is an error message.
- warn: Severity 1 - This is a warning message.
- info: Severity 2 - This is an informational message.
- http: Severity 3 - This is an HTTP log.
- verbose: Severity 4 - This is a verbose log.
- debug: Severity 5 - This is a debug message.
- silly: Severity 6 - This is a silly message.
The above example of the logger is configured to log messages at the
info
,warn
, anderror
levels. We can also create custom levels for the Winston logger.We can use the syslog levels as well. The syslog levels can be used from
winston.config.syslog.levels
. The default levels are fromwinston
.config.npm.levels.import winston from 'winston'; const customLevels = { levels: { error: 0, warn: 1, info: 2, }, colors: { error: 'red', warn: 'yellow', info: 'green', } }; // add colors to the custom levels winston.addColors(customLevels.colors); // define a logger with custom levels const logger = winston.createLogger({ levels: customLevels.levels, format: winston.format.combine( winston.format.colorize(), winston.format.simple() ), transports: [ new winston.transports.Console() ] }); // now we can use the logger to log messages as we want logger.error('Hello, Winston!'); logger.warn('An error occurred!'); logger.info('Warning: This is a warning!');
Output:
Here, we have defined custom levels for the logger and added colors to the custom levels. We have to inform Winston about the custom levels and colors using the
addColors
method. -
Formats - Formats are used to specify the format of the log message. Winston is pre-configured with the following formats:
-
JSON
- This format logs the message in JSON format. -
simple
- This format logs the message in a simple format. -
colorize
- This format logs the message in color. -
printf
- This format logs the message in a custom format. -
timestamp
- This format logs the message with a timestamp. -
combine
- This format combines multiple formats.
We can also create custom formats for the Winston logger.
import winston from 'winston'; // define a logger with custom format const logger = winston.createLogger({ level: 'info', format: winston.format.combine( winston.format.colorize(), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss', }), winston.format.printf(({ level, message, timestamp }) => { return `${timestamp} [${level}]: ${message}`; }) ), transports: [ new winston.transports.Console() ] }); // now we can use the logger to log messages as we want logger.info('Hello, Winston!'); logger.error('An error occurred!'); logger.warn('Warning: This is a warning!');
Output:
Here, we have defined a custom format for the logger. The custom format logs the message in the format
[timestamp] [level]: message
-
-
Transports - Transports are used to specify the destination of the log message. Winston is pre-configured with the following transports:
-
Console
- This transport logs the message to the console. Mostly used for development purposes. -
File
- This transport logs the message to a file. We can specify the filename, the maximum size of the file, and the maximum number of files. -
HTTP
- This transport logs the message to another server using HTTP. Mostly used for remote logging services. -
Stream
- This transport logs the message to a NodeJS writable stream.
-
Daily Rotate File - Daily Rotate File is an important way to prevent the accumulation of unnecessary old log files that are of no use in a live server and can be replaced automatically, ensuring the latest log files are available always and the redundant old files are removed/rotated to save the space on the live server
Let's install the Daily Rotate File package:
npm install winston-daily-rotate-file
import winston from 'winston';
import 'winston-daily-rotate-file';
const fileFormat = winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss',
}),
winston.format.printf(({ timestamp, level, message }) => {
return `${timestamp} [${level}]: ${message}`;
})
);
const combinedFileTransport = new winston.transports.DailyRotateFile({
filename: '%DATE%_combined.log',
datePattern: 'YYYY-MM-DD-HH',
maxSize: '2m',
dirname: './logs/combined',
maxFiles: '14d',
format: fileFormat
});
Here, we have defined a Daily Rotate File transport for the logger. The Daily Rotate File transport logs the message to a file with the filename combined.log
and rotates the file daily.
The maximum size of the file is 2m
i.e. 2MB and the maximum number of days the files will be kept is 14d
, where d is days. The %DATE%
is a placeholder that will be replaced with the current date in the format YYYY-MM-DD-HH
.
Designing a production-ready logger using Winston, Morgan and Daily Rotate File
Let's design a production-ready logger using Winston, Morgan and Daily Rotate File:
// winston.config.js
import winston from 'winston';
import 'winston-daily-rotate-file';
const fileFormat = winston.format.combine(
winston.format.colorize(),
winston.format.uncolorize(),
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss',
}),
winston.format.prettyPrint({
depth: 5
}),
winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`),
);
const consoleFormat = winston.format.combine(
winston.format.colorize(),
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss',
}),
winston.format.prettyPrint({
depth: 5
}),
winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`),
);
const consoleTransport = new winston.transports.Console({
format: consoleFormat,
});
const combinedFileTransport = new winston.transports.DailyRotateFile({
filename: '%DATE%_combined.log',
format: fileFormat, //format means - how the log should be formatted
datePattern: 'YYYY-MM-DD-HH',
maxSize: '2m',
dirname: './logs/combined',
maxFiles: '14d',
});
const errorFileTransport = new winston.transports.DailyRotateFile({
filename: '%DATE%_error.log',
level: 'error',
format: fileFormat, //format means - how the log should be formatted
datePattern: 'YYYY-MM-DD-HH',
maxSize: '2m',
dirname: './logs/errors',
maxFiles: '14d',
});
const httpTransport = new winston.transports.Http({
format: winston.format.json(),
host: 'localhost',
port: 4000,
path: '/logs',
ssl: false,
batch: true,
batchCount: 10,
batchInterval: 10000,
});
const logger = winston.createLogger({
levels: winston.config.syslog.levels,
transports: [
errorFileTransport,
combinedFileTransport,
consoleTransport,
httpTransport,
],
});
export default logger;
Here, we have defined a logger using the Winston library with the following functionalities:
- Console Transport - Logs the message to the console.
- Daily Rotate File Transport - Logs the message to a file and rotates the file daily.
- HTTP Transport - Logs the message to another server using HTTP.
- Error File Transport - Logs the error message to a file and rotates the file daily.
- Combined File Transport - Logs the message to a file and rotates the file daily.
- Syslog Levels - We have used the syslog levels for the logger.
// index.js
import express from 'express';
import morgan from 'morgan';
import logger from './config/winston.config.js';
const app = express();
app.use(express.json());
app.use(morgan('dev', {
stream: {
write: message => {
logger.info(message);
}
}
}));
app.get('/', (_req, res) => {
return res.status(200).json({ message: 'Hello World' });
});
function handleFatalError(err) {
logger.error(err);
process.exit(1);
}
app.listen(process.env.PORT || 3000, () => {
logger.info('Server is running on http://localhost:' + (process.env.PORT || 3000) + '/');
process.on('unhandledRejection', handleFatalError);
process.on('SIGTERM', handleFatalError);
});
To further enhance the functionality of our logger, we have integrated it with the Morgan library. Morgan is a middleware that logs the HTTP requests to the server. We have used the dev
format of Morgan to log the HTTP requests to the server.
The stream
option of Morgan is used to specify the destination of the log message. We have used the Winston logger to log the message to the console.
Output:
-
Files created by the Daily Rotate File Transport:
-
Logs generated in the console:
-
Logs generated by the HTTP Transport:
The following logs were generated by our logger's HTTP Transport. The logs were sent to another server running on
localhost:4000/logs
. The requests were made in batches as specified in the configuration. -
Logs generated by the Error File Transport:
-
Logs generated by the Combined File Transport:
Conclusion
In this article, we have learned about the concept of logging and how to create a logger using Winston in a Node.js application. We have also learned about the various components of the Winston logger, such as levels, formats, and transports.
Try out the Winston logger setup in your Node.js applications and let me know your thoughts in the comments. If you have any questions or suggestions, feel free to ask.
So... that's it for today.
I would like to thank you 🫡🎉 for reading here and appreciate your patience. It would mean the world to me if you give feedback/suggestions/recommendations below.
See you in the next article, which will be published on......
PS:
I typically write these articles in the TIL form, sharing the things I learn during my daily work or afterward. I aim to post once or twice a week with all the things I have learned in the past week.
Top comments (4)
Beautiful one I love to use logging alot, Infact I integrated logging to my dataDisk project making it easy to know the occurence of events while processing data. Thank you, this was a great psot.
Great article. Any further insights on how to make the most of your log files, any tool to read those files and setup alerts, etc.?
Thanks for commenting 🙏 and to answer your questions
Implement strategies like structured logging to ensure that dynamic information from requests does not affect the log files' structure. This maintains a consistent format, making it easier to understand and automate the analysis of log files.
There are several production-ready log analysis tools, such as the ELK stack (Elasticsearch, Logstash, Kibana) and Prometheus & Grafana. While I am still learning about these tools in depth, they are highly recommended for their robust capabilities. You can find other good articles on them online.
server.notify()
orprocess.on('uncaughtException',...)
orserver.on('error')
?