`n

Logging Strategy Node.js - Complete Guide

Published: September 25, 2024 | Reading time: 20 minutes

Logging Strategy Overview

Effective logging is crucial for debugging, monitoring, and maintaining applications:

Logging Benefits
# Logging Benefits
- Debugging and troubleshooting
- Performance monitoring
- Security auditing
- Business analytics
- Compliance requirements
- Error tracking
- User behavior analysis

Logging Libraries

Popular Node.js Logging Libraries

Logging Libraries
# Logging Libraries

# 1. Winston Logger
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
    new winston.transports.Console({
      format: winston.format.simple()
    })
  ]
});

logger.info('Application started');
logger.error('An error occurred', { error: 'Something went wrong' });

# 2. Pino Logger
const pino = require('pino');

const logger = pino({
  level: 'info',
  transport: {
    target: 'pino-pretty',
    options: {
      colorize: true
    }
  }
});

logger.info('Application started');
logger.error({ error: 'Something went wrong' }, 'An error occurred');

# 3. Bunyan Logger
const bunyan = require('bunyan');

const logger = bunyan.createLogger({
  name: 'myapp',
  level: 'info',
  streams: [
    {
      level: 'info',
      stream: process.stdout
    },
    {
      level: 'error',
      path: 'error.log'
    }
  ]
});

logger.info('Application started');
logger.error({ error: 'Something went wrong' }, 'An error occurred');

# 4. Console Logger
class ConsoleLogger {
  constructor(level = 'info') {
    this.level = level;
    this.levels = {
      error: 0,
      warn: 1,
      info: 2,
      debug: 3
    };
  }
  
  log(level, message, meta = {}) {
    if (this.levels[level] <= this.levels[this.level]) {
      const timestamp = new Date().toISOString();
      console.log(`[${timestamp}] ${level.toUpperCase()}: ${message}`, meta);
    }
  }
  
  error(message, meta) {
    this.log('error', message, meta);
  }
  
  warn(message, meta) {
    this.log('warn', message, meta);
  }
  
  info(message, meta) {
    this.log('info', message, meta);
  }
  
  debug(message, meta) {
    this.log('debug', message, meta);
  }
}

const logger = new ConsoleLogger('info');
logger.info('Application started');
logger.error('An error occurred', { error: 'Something went wrong' });

# 5. Custom Logger
class CustomLogger {
  constructor(options = {}) {
    this.level = options.level || 'info';
    this.format = options.format || 'json';
    this.transports = options.transports || ['console'];
    this.levels = {
      error: 0,
      warn: 1,
      info: 2,
      debug: 3
    };
  }
  
  log(level, message, meta = {}) {
    if (this.levels[level] <= this.levels[this.level]) {
      const logEntry = {
        timestamp: new Date().toISOString(),
        level,
        message,
        ...meta
      };
      
      if (this.format === 'json') {
        console.log(JSON.stringify(logEntry));
      } else {
        console.log(`[${logEntry.timestamp}] ${level.toUpperCase()}: ${message}`, meta);
      }
    }
  }
  
  error(message, meta) {
    this.log('error', message, meta);
  }
  
  warn(message, meta) {
    this.log('warn', message, meta);
  }
  
  info(message, meta) {
    this.log('info', message, meta);
  }
  
  debug(message, meta) {
    this.log('debug', message, meta);
  }
}

const logger = new CustomLogger({ level: 'info', format: 'json' });
logger.info('Application started');
logger.error('An error occurred', { error: 'Something went wrong' });

# 6. Structured Logging
class StructuredLogger {
  constructor(options = {}) {
    this.level = options.level || 'info';
    this.service = options.service || 'myapp';
    this.version = options.version || '1.0.0';
    this.environment = options.environment || 'development';
  }
  
  createLogEntry(level, message, meta = {}) {
    return {
      timestamp: new Date().toISOString(),
      level,
      message,
      service: this.service,
      version: this.version,
      environment: this.environment,
      ...meta
    };
  }
  
  log(level, message, meta = {}) {
    const logEntry = this.createLogEntry(level, message, meta);
    console.log(JSON.stringify(logEntry));
  }
  
  error(message, meta) {
    this.log('error', message, meta);
  }
  
  warn(message, meta) {
    this.log('warn', message, meta);
  }
  
  info(message, meta) {
    this.log('info', message, meta);
  }
  
  debug(message, meta) {
    this.log('debug', message, meta);
  }
}

const logger = new StructuredLogger({
  service: 'myapp',
  version: '1.0.0',
  environment: 'production'
});

logger.info('Application started');
logger.error('An error occurred', { error: 'Something went wrong' });

# 7. Logging with Context
class ContextLogger {
  constructor(baseLogger, context = {}) {
    this.baseLogger = baseLogger;
    this.context = context;
  }
  
  log(level, message, meta = {}) {
    const enrichedMeta = { ...this.context, ...meta };
    this.baseLogger.log(level, message, enrichedMeta);
  }
  
  error(message, meta) {
    this.log('error', message, meta);
  }
  
  warn(message, meta) {
    this.log('warn', message, meta);
  }
  
  info(message, meta) {
    this.log('info', message, meta);
  }
  
  debug(message, meta) {
    this.log('debug', message, meta);
  }
  
  child(context) {
    return new ContextLogger(this.baseLogger, { ...this.context, ...context });
  }
}

const baseLogger = new StructuredLogger();
const logger = new ContextLogger(baseLogger, { userId: '123', requestId: 'req-456' });

logger.info('User action performed');
logger.error('Database error', { table: 'users', operation: 'insert' });

# 8. Logging with Performance Metrics
class PerformanceLogger {
  constructor(baseLogger) {
    this.baseLogger = baseLogger;
  }
  
  startTimer(label) {
    const start = process.hrtime.bigint();
    return {
      end: () => {
        const end = process.hrtime.bigint();
        const duration = Number(end - start) / 1000000; // Convert to milliseconds
        this.baseLogger.info(`Timer ${label} completed`, { duration, label });
        return duration;
      }
    };
  }
  
  measureAsync(label, fn) {
    const timer = this.startTimer(label);
    return fn().finally(() => timer.end());
  }
  
  measureSync(label, fn) {
    const timer = this.startTimer(label);
    try {
      const result = fn();
      timer.end();
      return result;
    } catch (error) {
      timer.end();
      throw error;
    }
  }
}

const baseLogger = new StructuredLogger();
const perfLogger = new PerformanceLogger(baseLogger);

// Measure async operation
await perfLogger.measureAsync('database-query', async () => {
  await database.query('SELECT * FROM users');
});

// Measure sync operation
const result = perfLogger.measureSync('data-processing', () => {
  return processData(largeDataset);
});

# 9. Logging with Error Tracking
class ErrorLogger {
  constructor(baseLogger) {
    this.baseLogger = baseLogger;
  }
  
  logError(error, context = {}) {
    const errorInfo = {
      name: error.name,
      message: error.message,
      stack: error.stack,
      ...context
    };
    
    this.baseLogger.error('Error occurred', errorInfo);
  }
  
  logUnhandledError(error, context = {}) {
    const errorInfo = {
      name: error.name,
      message: error.message,
      stack: error.stack,
      type: 'unhandled',
      ...context
    };
    
    this.baseLogger.error('Unhandled error occurred', errorInfo);
  }
  
  logPromiseRejection(reason, promise) {
    const errorInfo = {
      reason: reason.toString(),
      promise: promise.toString(),
      type: 'unhandledRejection'
    };
    
    this.baseLogger.error('Unhandled promise rejection', errorInfo);
  }
}

const baseLogger = new StructuredLogger();
const errorLogger = new ErrorLogger(baseLogger);

// Log caught error
try {
  throw new Error('Something went wrong');
} catch (error) {
  errorLogger.logError(error, { userId: '123', action: 'user-login' });
}

# 10. Logging with Security
class SecurityLogger {
  constructor(baseLogger) {
    this.baseLogger = baseLogger;
  }
  
  logSecurityEvent(event, details = {}) {
    const securityInfo = {
      event,
      timestamp: new Date().toISOString(),
      ...details
    };
    
    this.baseLogger.warn('Security event', securityInfo);
  }
  
  logLoginAttempt(userId, success, details = {}) {
    this.logSecurityEvent('login_attempt', {
      userId,
      success,
      ...details
    });
  }
  
  logAccessDenied(userId, resource, details = {}) {
    this.logSecurityEvent('access_denied', {
      userId,
      resource,
      ...details
    });
  }
  
  logSuspiciousActivity(activity, details = {}) {
    this.logSecurityEvent('suspicious_activity', {
      activity,
      ...details
    });
  }
}

const baseLogger = new StructuredLogger();
const securityLogger = new SecurityLogger(baseLogger);

securityLogger.logLoginAttempt('user123', true, { ip: '192.168.1.1' });
securityLogger.logAccessDenied('user123', '/admin', { ip: '192.168.1.1' });
securityLogger.logSuspiciousActivity('multiple_failed_logins', { userId: 'user123' });

Log Levels and Strategies

Log Level Management

Log Levels

  • ERROR: System errors
  • WARN: Warning conditions
  • INFO: General information
  • DEBUG: Debug information
  • TRACE: Detailed tracing
  • FATAL: Fatal errors
  • CUSTOM: Application-specific

Logging Strategies

  • Structured logging
  • Context enrichment
  • Performance metrics
  • Error tracking
  • Security logging
  • Audit trails
  • Business metrics

Production Logging

Production Logging Setup

Production Logging
# Production Logging

# 1. Production Winston Setup
const winston = require('winston');
const { Logtail } = require('@logtail/node');

const logtail = new Logtail(process.env.LOGTAIL_TOKEN);

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ 
      filename: 'error.log', 
      level: 'error',
      maxsize: 5242880, // 5MB
      maxFiles: 5
    }),
    new winston.transports.File({ 
      filename: 'combined.log',
      maxsize: 5242880, // 5MB
      maxFiles: 5
    }),
    new winston.transports.Console({
      format: winston.format.simple()
    })
  ]
});

// Add Logtail transport for production
if (process.env.NODE_ENV === 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.json()
  }));
}

# 2. Production Pino Setup
const pino = require('pino');

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  transport: process.env.NODE_ENV === 'production' ? {
    target: 'pino/file',
    options: { destination: 1 } // stdout
  } : {
    target: 'pino-pretty',
    options: {
      colorize: true
    }
  }
});

# 3. Log Rotation
const winston = require('winston');
require('winston-daily-rotate-file');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.DailyRotateFile({
      filename: 'logs/application-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      maxSize: '20m',
      maxFiles: '14d'
    }),
    new winston.transports.DailyRotateFile({
      filename: 'logs/error-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      level: 'error',
      maxSize: '20m',
      maxFiles: '14d'
    })
  ]
});

# 4. Log Aggregation
const winston = require('winston');
const { LogstashTransport } = require('winston-logstash');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.Console(),
    new LogstashTransport({
      host: process.env.LOGSTASH_HOST,
      port: process.env.LOGSTASH_PORT,
      node_name: 'myapp',
      level: 'info'
    })
  ]
});

# 5. Log Monitoring
class LogMonitor {
  constructor(logger) {
    this.logger = logger;
    this.errorCount = 0;
    this.warningCount = 0;
    this.lastReset = Date.now();
  }
  
  log(level, message, meta) {
    if (level === 'error') {
      this.errorCount++;
    } else if (level === 'warn') {
      this.warningCount++;
    }
    
    this.logger.log(level, message, meta);
    
    // Check for alert conditions
    this.checkAlerts();
  }
  
  checkAlerts() {
    const now = Date.now();
    const timeSinceReset = now - this.lastReset;
    
    // Reset counters every hour
    if (timeSinceReset > 3600000) {
      this.errorCount = 0;
      this.warningCount = 0;
      this.lastReset = now;
    }
    
    // Alert if too many errors
    if (this.errorCount > 100) {
      this.logger.error('High error rate detected', {
        errorCount: this.errorCount,
        timeWindow: '1 hour'
      });
    }
  }
  
  getStats() {
    return {
      errorCount: this.errorCount,
      warningCount: this.warningCount,
      timeSinceReset: Date.now() - this.lastReset
    };
  }
}

const baseLogger = new StructuredLogger();
const logMonitor = new LogMonitor(baseLogger);

# 6. Log Sampling
class SampledLogger {
  constructor(baseLogger, sampleRate = 1.0) {
    this.baseLogger = baseLogger;
    this.sampleRate = sampleRate;
  }
  
  shouldLog() {
    return Math.random() < this.sampleRate;
  }
  
  log(level, message, meta) {
    if (this.shouldLog()) {
      this.baseLogger.log(level, message, meta);
    }
  }
  
  error(message, meta) {
    this.log('error', message, meta);
  }
  
  warn(message, meta) {
    this.log('warn', message, meta);
  }
  
  info(message, meta) {
    this.log('info', message, meta);
  }
  
  debug(message, meta) {
    this.log('debug', message, meta);
  }
}

const baseLogger = new StructuredLogger();
const sampledLogger = new SampledLogger(baseLogger, 0.1); // 10% sampling

# 7. Log Filtering
class FilteredLogger {
  constructor(baseLogger, filters = []) {
    this.baseLogger = baseLogger;
    this.filters = filters;
  }
  
  shouldLog(level, message, meta) {
    return this.filters.every(filter => filter(level, message, meta));
  }
  
  log(level, message, meta) {
    if (this.shouldLog(level, message, meta)) {
      this.baseLogger.log(level, message, meta);
    }
  }
  
  error(message, meta) {
    this.log('error', message, meta);
  }
  
  warn(message, meta) {
    this.log('warn', message, meta);
  }
  
  info(message, meta) {
    this.log('info', message, meta);
  }
  
  debug(message, meta) {
    this.log('debug', message, meta);
  }
}

const baseLogger = new StructuredLogger();
const filteredLogger = new FilteredLogger(baseLogger, [
  // Filter out sensitive data
  (level, message, meta) => {
    if (meta.password) {
      meta.password = '[REDACTED]';
    }
    return true;
  },
  // Filter out debug logs in production
  (level, message, meta) => {
    if (process.env.NODE_ENV === 'production' && level === 'debug') {
      return false;
    }
    return true;
  }
]);

# 8. Log Correlation
class CorrelationLogger {
  constructor(baseLogger) {
    this.baseLogger = baseLogger;
    this.correlationId = null;
  }
  
  setCorrelationId(id) {
    this.correlationId = id;
  }
  
  log(level, message, meta = {}) {
    const enrichedMeta = {
      correlationId: this.correlationId,
      ...meta
    };
    
    this.baseLogger.log(level, message, enrichedMeta);
  }
  
  error(message, meta) {
    this.log('error', message, meta);
  }
  
  warn(message, meta) {
    this.log('warn', message, meta);
  }
  
  info(message, meta) {
    this.log('info', message, meta);
  }
  
  debug(message, meta) {
    this.log('debug', message, meta);
  }
}

const baseLogger = new StructuredLogger();
const correlationLogger = new CorrelationLogger(baseLogger);

// Set correlation ID for request
correlationLogger.setCorrelationId('req-123');
correlationLogger.info('Request started');
correlationLogger.info('Processing user data');
correlationLogger.info('Request completed');

# 9. Log Metrics
class MetricsLogger {
  constructor(baseLogger) {
    this.baseLogger = baseLogger;
    this.metrics = {
      logCount: 0,
      errorCount: 0,
      warningCount: 0,
      infoCount: 0,
      debugCount: 0
    };
  }
  
  log(level, message, meta) {
    this.metrics.logCount++;
    this.metrics[`${level}Count`]++;
    
    this.baseLogger.log(level, message, meta);
  }
  
  error(message, meta) {
    this.log('error', message, meta);
  }
  
  warn(message, meta) {
    this.log('warn', message, meta);
  }
  
  info(message, meta) {
    this.log('info', message, meta);
  }
  
  debug(message, meta) {
    this.log('debug', message, meta);
  }
  
  getMetrics() {
    return { ...this.metrics };
  }
  
  resetMetrics() {
    this.metrics = {
      logCount: 0,
      errorCount: 0,
      warningCount: 0,
      infoCount: 0,
      debugCount: 0
    };
  }
}

const baseLogger = new StructuredLogger();
const metricsLogger = new MetricsLogger(baseLogger);

# 10. Log Health Check
class LogHealthCheck {
  constructor(logger) {
    this.logger = logger;
    this.lastLogTime = Date.now();
    this.logInterval = 60000; // 1 minute
  }
  
  start() {
    setInterval(() => {
      const now = Date.now();
      const timeSinceLastLog = now - this.lastLogTime;
      
      if (timeSinceLastLog > this.logInterval) {
        this.logger.warn('No logs received recently', {
          timeSinceLastLog,
          logInterval: this.logInterval
        });
      }
    }, this.logInterval);
  }
  
  updateLastLogTime() {
    this.lastLogTime = Date.now();
  }
}

const baseLogger = new StructuredLogger();
const logHealthCheck = new LogHealthCheck(baseLogger);
logHealthCheck.start();

Summary

Node.js logging strategy involves several key components:

  • Logging Libraries: Winston, Pino, Bunyan, and custom loggers
  • Log Levels: ERROR, WARN, INFO, DEBUG, and custom levels
  • Production Logging: Log rotation, aggregation, and monitoring
  • Advanced Features: Correlation, filtering, sampling, and metrics

Need More Help?

Struggling with logging strategy implementation or need help setting up production logging? Our Node.js experts can help you implement effective logging systems.

Get Logging Help