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
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
| Type | Purpose | Example |
|---|---|---|
| Error | Critical failures | Database connection lost |
| Warning | Potential issues | Deprecated API usage |
| Info | Normal operations | User logged in |
| Debug | Development details | Variable values |
| Trace | Very detailed flow | Function 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()]
});
| Level | When to use |
|---|---|
| error | Critical failures, unavailable services |
| warn | Deprecated usage, near-threshold resources |
| info | Server lifecycle, user operations |
| http | Request/response logs |
| debug | Variables, branch details, diagnostics |
| silly | Full 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&A1Why 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?
Explanation: Logging exists to observe, diagnose, and improve reliability.
2
Which log level is most critical?
Explanation:
error indicates critical failures.3
Why prefer JSON logs in production?
Explanation: JSON format works best with log pipelines and search tools.
4
What does log rotation prevent?
Explanation: Rotation caps file size/count to protect disk space.
5
Which should never be logged?
Explanation: Sensitive data must be masked or excluded.
6
Best default production log level?
Explanation:
info is a practical baseline for production observability.7
What enables root-cause diagnostics?
Explanation: Stack traces show call paths and failure points.
8
Why include request IDs in logs?
Explanation: Correlation IDs connect events across services.
9
Which library is widely used for Node logging?
Explanation:
winston is a popular structured logging library.10
Unhandled rejections should be:
Explanation: Always log unhandled rejections to avoid silent failures.