Node.js Error Logging: Complete Theory & Practice Guide

Complete theory and practice guide for robust error logging in Node.js applications.

Console Logs Winston Production Best Practices

Table of Contents

  1. Theory: Why Error Logging Matters
  2. Basic Console Logging
  3. File-Based Logging
  4. Structured Logging with Winston
  5. Log Levels Explained
  6. Error Stack Traces
  7. Best Practices
  8. Interview Q&A + MCQ
  9. Contextual Learning Links

1. Theory: Why Error Logging Matters

What is Error Logging?

Error logging is the systematic recording of errors, warnings, and informational events that occur during your application's execution.

Why It's Critical

  • Debugging production issues when no debugger is attached
  • Maintaining an audit trail of what happened and when
  • Monitoring recurring issues and performance anomalies
  • Meeting compliance requirements in regulated domains
  • Providing better context for user support incidents

Types of Logs

TypePurposeExample
ErrorCritical failuresDatabase connection lost
WarningPotential issuesDeprecated API usage
InfoNormal operationsUser logged in
DebugDevelopment detailsVariable values
TraceVery detailed flowFunction entry/exit

2. Basic Console Logging

// app.js - Simple console logging
try {
  const result = JSON.parse('invalid json');
} catch (error) {
  console.error('❌ Error occurred:', error.message);
  console.error('Stack trace:', error.stack);
}

console.log('ℹ️ Info: Server started on port 3000');
console.warn('⚠️ Warning: Deprecated method used');
console.error('🔥 Error: Failed to connect to database');
console.debug('🐛 Debug: User object:', { id: 123, name: 'John' });

const log = {
  info: (msg, data) => console.log(`[${new Date().toISOString()}] INFO: ${msg}`, data || ''),
  error: (msg, error) => console.error(`[${new Date().toISOString()}] ERROR: ${msg}`, error.stack),
  warn: (msg) => console.warn(`[${new Date().toISOString()}] WARN: ${msg}`)
};

log.info('User authenticated', { userId: 42 });
log.error('Database query failed', new Error('Connection timeout'));

Example Output

[2024-01-15T10:30:00.000Z] INFO: User authenticated { userId: 42 }
[2024-01-15T10:30:01.000Z] ERROR: Database query failed Error: Connection timeout

3. File-Based Logging

const fs = require('fs');
const path = require('path');

class FileLogger {
  constructor(logDir = './logs') {
    this.logDir = logDir;
    if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
  }

  getLogFileName() {
    const date = new Date();
    const y = date.getFullYear();
    const m = String(date.getMonth() + 1).padStart(2, '0');
    const d = String(date.getDate()).padStart(2, '0');
    return path.join(this.logDir, `app-${y}-${m}-${d}.log`);
  }

  writeLog(level, message, meta = null) {
    const timestamp = new Date().toISOString();
    const logEntry = { timestamp, level, message, ...(meta && { meta }) };
    fs.appendFileSync(this.getLogFileName(), JSON.stringify(logEntry) + '\n');
    console[level === 'error' ? 'error' : 'log'](`[${timestamp}] ${level.toUpperCase()}: ${message}`);
  }

  info(message, meta) { this.writeLog('info', message, meta); }
  warn(message, meta) { this.writeLog('warn', message, meta); }
  error(message, error) { this.writeLog('error', message, { message: error.message, stack: error.stack }); }
}

Generated Log File Example

{"timestamp":"2024-01-15T10:30:00.000Z","level":"info","message":"Application started","meta":{"version":"1.0.0"}}
{"timestamp":"2024-01-15T10:30:01.000Z","level":"error","message":"Critical error","meta":{"message":"Database connection failed","stack":"Error: Database connection failed..."}}

4. Structured Logging with Winston

Installation

npm install winston

Basic Winston Setup

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

Advanced Winston with Pretty Formatting

const winston = require('winston');
const { combine, timestamp, printf, colorize, align, json } = winston.format;
const consoleFormat = printf(({ level, message, timestamp, ...meta }) => {
  const metaStr = Object.keys(meta).length ? JSON.stringify(meta) : '';
  return `${timestamp} [${level}]: ${message} ${metaStr}`.trim();
});

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: combine(timestamp(), align()),
  transports: [
    new winston.transports.Console({ format: combine(colorize({ all: true }), consoleFormat) }),
    new winston.transports.File({ filename: 'logs/app.log', format: combine(timestamp(), json()), maxsize: 5242880, maxFiles: 5 }),
    new winston.transports.File({ filename: 'logs/error.log', level: 'error', format: combine(timestamp(), json()) })
  ]
});

5. Log Levels Explained

const logger = winston.createLogger({
  levels: { error: 0, warn: 1, info: 2, http: 3, verbose: 4, debug: 5, silly: 6 },
  level: 'debug',
  transports: [new winston.transports.Console()]
});
LevelWhen to use
errorCritical failures, unavailable services
warnDeprecated usage, near-threshold resources
infoServer lifecycle, user operations
httpRequest/response logs
debugVariables, branch details, diagnostics
sillyFull verbose traces and dumps

6. Error Stack Traces

const logger = winston.createLogger({
  level: 'debug',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [new winston.transports.Console()]
});

class DatabaseError extends Error {
  constructor(message, query) {
    super(message);
    this.name = 'DatabaseError';
    this.query = query;
    Error.captureStackTrace(this, DatabaseError);
  }
}

Expected Output

2024-01-15T10:30:00.000Z [error]: Operation failed at level2: Connection pool exhausted
DatabaseError: Connection pool exhausted
    at level3 (/app/stack-trace-demo.js:35:9)
    at level2 (/app/stack-trace-demo.js:41:5)

7. Best Practices

Complete Production-Ready Logger

const winston = require('winston');

class ProductionLogger {
  constructor(serviceName) {
    this.serviceName = serviceName;
    return this.setupLogger();
  }
  setupLogger() {
    const maskSensitiveData = winston.format((info) => {
      const sensitiveFields = ['password', 'credit_card', 'ssn', 'token', 'api_key'];
      sensitiveFields.forEach(field => {
        const regex = new RegExp(`"${field}":"[^"]*"`, 'gi');
        if (typeof info.message === 'string') info.message = info.message.replace(regex, `"${field}":"[REDACTED]"`);
      });
      return info;
    });
    return winston.createLogger({
      level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
      format: winston.format.combine(
        maskSensitiveData(),
        winston.format.timestamp(),
        winston.format.errors({ stack: true }),
        winston.format.json()
      ),
      defaultMeta: { service: this.serviceName, environment: process.env.NODE_ENV || 'development' },
      transports: [
        new winston.transports.File({ filename: 'logs/error.log', level: 'error', maxsize: 10485760, maxFiles: 5 }),
        new winston.transports.File({ filename: 'logs/combined.log', maxsize: 10485760, maxFiles: 5 })
      ]
    });
  }
}

Key Best Practices Summary

  • Never log sensitive data (passwords, tokens, PII)
  • Use appropriate log levels based on severity
  • Include context: request IDs, user IDs, operation names
  • Prefer JSON structured logs for aggregation/search
  • Rotate log files to avoid disk exhaustion
  • Log uncaught exceptions and unhandled rejections
  • Always include timestamps (ISO format)
  • Use metadata objects instead of string concatenation
  • Tune verbosity by environment (dev vs prod)
  • Use trace/request IDs for distributed diagnostics

10 Interview Questions + 10 MCQs

Interview Pattern 10 Q&A
1Why is logging essential in production?easy
Answer: It provides visibility when debugging production issues without an attached debugger.
2Difference between error and warning logs?easy
Answer: Errors are failures requiring action; warnings indicate potential issues.
3Why use structured JSON logs?medium
Answer: They are machine-parseable and easier to search/aggregate in log tools.
4What does log rotation solve?medium
Answer: It prevents unbounded log file growth and disk exhaustion.
5When should you log stack traces?medium
Answer: For exceptions and failures where root-cause analysis is needed.
6Why avoid logging sensitive fields?easy
Answer: To prevent secrets/PII exposure in logs and downstream systems.
7How do request IDs help?medium
Answer: They correlate logs across services for a single request path.
8What should be the default log level in production?hard
Answer: Usually info (or higher) with debug logs disabled by default.
9Why log uncaught exceptions and unhandled rejections?hard
Answer: They reveal critical crashes and async failures that might otherwise be silent.
10Why use different formats for console vs file?hard
Answer: Human-readable console output and machine-readable file/JSON output serve different consumers.

10 Error Logging MCQs

1

Primary purpose of error logging?

AReduce file size
BDiagnose and monitor issues
CDisable errors
DHide failures
Explanation: Logging exists to observe, diagnose, and improve reliability.
2

Which log level is most critical?

Ainfo
Bwarn
Cerror
Ddebug
Explanation: error indicates critical failures.
3

Why prefer JSON logs in production?

AFaster fonts
BMachine parsing/aggregation
CLess metadata
DNo timestamps needed
Explanation: JSON format works best with log pipelines and search tools.
4

What does log rotation prevent?

ASyntax errors
BDisk usage growth
CHTTP timeouts
DMemory leaks
Explanation: Rotation caps file size/count to protect disk space.
5

Which should never be logged?

ARequest ID
BPassword/token
CStatus code
DTimestamp
Explanation: Sensitive data must be masked or excluded.
6

Best default production log level?

Asilly
Bdebug
Cinfo
Dtrace only
Explanation: info is a practical baseline for production observability.
7

What enables root-cause diagnostics?

ARemoving stack traces
BError stack traces
CNo timestamps
DMinified logs
Explanation: Stack traces show call paths and failure points.
8

Why include request IDs in logs?

AReduce CPU usage
BTrace requests end-to-end
CHide user IDs
DAvoid log files
Explanation: Correlation IDs connect events across services.
9

Which library is widely used for Node logging?

Aaxios
Bwinston
Cchalk
Dlodash
Explanation: winston is a popular structured logging library.
10

Unhandled rejections should be:

AIgnored silently
BLogged with context
CConverted to info logs
DRemoved from code
Explanation: Always log unhandled rejections to avoid silent failures.