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