Use Winston Logger in Web Applications

By Raman Kumar

Updated on Dec 03, 2024

In this tutorial, we'll learn use Winston Logger in web applications.

Winston is a versatile and highly customizable logging library for Node.js, widely used in web applications for its flexibility and ability to handle various log formats, transports, and levels. In this tutorial, we will explore how to use Winston in a web application, from basic setup to advanced features like custom log formats, file-based logging, and error handling.

Why Logging Is Important

Logging is essential for:

  • Debugging issues during development.
  • Monitoring application behavior in production.
  • Auditing user actions and system events.
  • Maintaining historical records for post-incident analysis.

Winston excels as a logger due to its support for multiple transports (where logs are saved), log levels, and extensibility.

Key Concepts in Winston

  • Transports: Winston can log to multiple destinations like the console, files, HTTP endpoints, and more.
  • Log Levels: Winston uses predefined severity levels (error, warn, info, http, verbose, debug, and silly) or custom levels.
  • Formats: Winston supports formatting log messages using the format module, enabling timestamped, JSON-formatted, or colorized logs.
  • Error Handling: Winston integrates well with Node.js for logging errors, making it easy to troubleshoot application issues.

Use Winston Logger in Web Applications

Step 1: Install Winston

To begin, install Winston via npm in your Node.js project:

npm install winston

Step 2: Setting Up Winston in a Web Application

Create a logging utility module to centralize Winston configuration.

Create a logger.js file and following code:

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

// Define custom log format
const customFormat = format.combine(
  format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
  format.printf(({ timestamp, level, message, stack }) => {
    return stack
      ? `[${timestamp}] ${level}: ${message}\nStack: ${stack}`
      : `[${timestamp}] ${level}: ${message}`;
  })
);

// Create logger instance
const logger = createLogger({
  level: 'info', // Default log level
  format: customFormat,
  transports: [
    new transports.Console({ level: 'debug', format: format.colorize() }),
    new transports.File({ filename: 'logs/app.log', level: 'info' }),
    new transports.File({ filename: 'logs/error.log', level: 'error' }),
  ],
  exceptionHandlers: [
    new transports.File({ filename: 'logs/exceptions.log' })
  ],
  rejectionHandlers: [
    new transports.File({ filename: 'logs/rejections.log' })
  ]
});

module.exports = logger;

Step 3: Using the Logger in an Express Application

Integrate the logger into an Express.js application for capturing requests, responses, and errors.

Create a app.js file and following code:

const express = require('express');
const morgan = require('morgan'); // HTTP request logging
const logger = require('./logger'); // Import logger module

const app = express();
const PORT = 3000;

// Middleware for HTTP logging using morgan and Winston
app.use(
  morgan('combined', {
    stream: {
      write: (message) => logger.http(message.trim()), // Use 'http' log level
    },
  })
);

// Sample route
app.get('/', (req, res) => {
  logger.info('Home route accessed');
  res.send('Welcome to Winston Logger tutorial!');
});

// Middleware for handling errors
app.use((err, req, res, next) => {
  logger.error(err.message, { stack: err.stack });
  res.status(500).send('Something went wrong!');
});

// Start server
app.listen(PORT, () => {
  logger.info(`Server running on http://localhost:${PORT}`);
});

Step 4: Advanced Features

Logging Metadata

Metadata provides additional context, like user IDs or request IDs, to log entries.

logger.info('User logged in', { userId: '12345' });

Dynamic Log Levels

Set log levels dynamically based on the environment.

logger.level = process.env.LOG_LEVEL || 'info';

Conditional Logging

Avoid verbose logs in production:

if (process.env.NODE_ENV !== 'production') {
  logger.debug('This is a debug message');
}

Step 5: Rotating Logs with winston-daily-rotate-file

To prevent log files from growing indefinitely, use the winston-daily-rotate-file package.

Install the Package

npm install winston-daily-rotate-file

Update logger.js

const DailyRotateFile = require('winston-daily-rotate-file');

logger.add(
  new DailyRotateFile({
    filename: 'logs/application-%DATE%.log',
    datePattern: 'YYYY-MM-DD',
    maxSize: '20m',
    maxFiles: '14d'
  })
);

Step 6: Testing and Validating Logs

Run the application and observe log outputs in the console and log files. Test different scenarios:

  • Normal operations (successful requests).
  • HTTP errors (e.g., 404).
  • Uncaught exceptions or promise rejections.

Best Practices

1. Use Appropriate Log Levels:

  • error: Critical errors.
  • warn: Non-critical warnings.
  • info: General information.
  • debug: Detailed debugging information.

2. Protect Sensitive Data: Avoid logging sensitive user information like passwords or personal details.

3. Centralized Logging: Use external services like ELK Stack (Elasticsearch, Logstash, Kibana) or Grafana Loki for centralized log management.

4. Monitor Logs: Continuously monitor logs for error patterns or unusual activity using automated tools.

Conclusion

Winston is a robust logging library that simplifies managing logs in Node.js applications. By understanding its key concepts and using best practices, you can effectively capture and analyze logs to ensure your application's reliability and performance.

With this production-ready setup, you are well-equipped to handle logging needs for any web application.

Checkout our instant dedicated servers and Instant KVM VPS plans.