Logging in JavaScript Applications: Best Practices on Node.js

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Logging in JavaScript Applications: Best Practices in Practice



Logging is not just about writing messages to the console. In a production environment, properly configured logging becomes the main tool for monitoring, debugging, and auditing. Without it, you are working blind. In this article, we will break down the best logging practices for JavaScript applications on Node.js, from choosing a library to configuring file rotation. All code examples are ready for use.



1. Why is console.log Bad?



During development, we all use console.log. It's fast and convenient. But in production, this approach is dangerous:



  • No logging levels. You won't be able to filter errors from informational messages.
  • Synchronous blocking. The standard console.log can slow down the application under a high volume of requests.
  • Lack of structure. JSON logs cannot be parsed without additional workarounds.
  • No rotation. The log file will grow indefinitely until it fills the disk.


Instead, use professional libraries. The two most popular in Node.js are Winston and Pino.



2. Choosing a Library: Winston vs Pino



Both libraries solve the same problem but have different priorities.



Winston — Versatile and Flexible

Winston is the "Swiss Army knife" of logging. It supports many transports (files, console, HTTP, databases) and is easy to configure.



const winston = require('winston');

const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [ new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' }), ],});

// In development, add console outputif (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: winston.format.simple(), }));}

logger.info('Server started on port 3000');logger.error('Database connection error', { db: 'postgres' });


Pino — Speed First

Pino positions itself as the fastest logger for Node.js. It is 5-6x faster than Winston thanks to the use of sonic-boom (asynchronous writing). Ideal for high-load projects.



const pino = require('pino');

const logger = pino({ level: process.env.LOG_LEVEL || 'info', transport: { target: 'pino/file', options: { destination: './app.log' }, },});

logger.info({ user: 'admin' }, 'User logged into the system');logger.error({ err: new Error('Timeout') }, 'Request exceeded the limit');


Tip: For microservices and high-traffic APIs, choose Pino. For complex configurations with multiple transports, choose Winston.



3. Logging Levels: Don't Write Everything to info



Use the standard level scale (in increasing order of importance):



  • trace / debug — for development only. Detailed debugging.
  • info — normal application operation: startup, successful requests, business events.
  • warn — potential issues: slow requests, low memory, deprecated methods.
  • error — exceptions, failures, crashes. Should always be in a separate file or monitoring system.
  • fatal — critical errors after which the application cannot continue working.


Configure the LOG_LEVEL environment variable to change the filtering level without restarting the code:



// Example with Winstonconst logger = winston.createLogger({  level: process.env.LOG_LEVEL || 'info',  // ...});

// In

Blogs

Book Recommendations