Logging Levels and Strategies
Proper logging strategies are essential for debugging and monitoring applications. Learn to implement tiered logging, structured logs, and effective log management.
Challenge
Section titled “Challenge”Implement a comprehensive logging strategy that captures appropriate information at each level while maintaining performance and security.
Explanation
Section titled “Explanation”Log Levels
Section titled “Log Levels”- ERROR: System errors requiring attention
- WARN: Potentially problematic situations
- INFO: General informational messages
- DEBUG: Detailed diagnostic information
- TRACE: Very detailed debugging information
Logging Strategies
Section titled “Logging Strategies”- Centralized Logging: Aggregate logs from all services
- Structured Logging: Machine-parseable log format
- Contextual Logging: Include request context
- Asynchronous Logging: Non-blocking log operations
Code Examples
Section titled “Code Examples”1. Custom Logger with Multiple Levels
Section titled “1. Custom Logger with Multiple Levels”const fs = require('fs');const path = require('path');
class Logger { constructor(options = {}) { this.level = options.level || 'info'; this.logFile = options.logFile || 'app.log'; this.maxSize = options.maxSize || 10 * 1024 * 1024; // 10MB this.maxFiles = options.maxFiles || 5;
this.levels = { error: 0, warn: 1, info: 2, debug: 3, trace: 4, }; }
shouldLog(level) { return this.levels[level] <= this.levels[this.level]; }
format(level, message, meta) { const timestamp = new Date().toISOString(); return JSON.stringify({ timestamp, level, message, ...meta, }); }
write(entry) { // Console output console.log(entry);
// File output with rotation this.writeToFile(entry); }
writeToFile(entry) { try { const stats = fs.statSync(this.logFile); if (stats.size > this.maxSize) { this.rotateFile(); } } catch (e) { // File doesn't exist yet }
fs.appendFileSync(this.logFile, entry + '\n'); }
rotateFile() { const ext = path.extname(this.logFile); const name = path.basename(this.logFile, ext); const dir = path.dirname(this.logFile); const rotated = path.join(dir, `${name}.${Date.now()}${ext}`);
fs.renameSync(this.logFile, rotated); }
error(message, meta = {}) { if (this.shouldLog('error')) { this.write(this.format('ERROR', message, meta)); } }
warn(message, meta = {}) { if (this.shouldLog('warn')) { this.write(this.format('WARN', message, meta)); } }
info(message, meta = {}) { if (this.shouldLog('info')) { this.write(this.format('INFO', message, meta)); } }
debug(message, meta = {}) { if (this.shouldLog('debug')) { this.write(this.format('DEBUG', message, meta)); } }
trace(message, meta = {}) { if (this.shouldLog('trace')) { this.write(this.format('TRACE', message, meta)); } }}
module.exports = new Logger({ level: 'info' });2. Contextual Logging Middleware
Section titled “2. Contextual Logging Middleware”const uuid = require('uuid');
class RequestLogger { constructor(logger) { this.logger = logger; }
middleware() { return (req, res, next) => { // Generate unique request ID req.id = req.headers['x-request-id'] || uuid.v4();
// Create context for this request req.logger = this.createContextLogger(req.id);
// Log request req.logger.info('Incoming request', { method: req.method, path: req.path, query: req.query, ip: req.ip, });
// Log response const originalJson = res.json; res.json = function(data) { req.logger.info('Response sent', { statusCode: res.statusCode, responseTime: Date.now() - req.startTime, }); return originalJson.call(this, data); };
req.startTime = Date.now(); next(); }; }
createContextLogger(requestId) { return { info: (msg, meta) => this.logger.info(msg, { requestId, ...meta }), error: (msg, meta) => this.logger.error(msg, { requestId, ...meta }), warn: (msg, meta) => this.logger.warn(msg, { requestId, ...meta }), debug: (msg, meta) => this.logger.debug(msg, { requestId, ...meta }), }; }}
// Usageconst requestLogger = new RequestLogger(logger);app.use(requestLogger.middleware());3. Environment-Based Logging
Section titled “3. Environment-Based Logging”const logger = { development: { level: 'debug', format: 'pretty', transports: ['console', 'file'], }, production: { level: 'info', format: 'json', transports: ['file', 'sentry'], redactFields: ['password', 'token', 'apiKey'], }, test: { level: 'error', format: 'json', transports: ['memory'], silent: process.env.CI === 'true', },};
function getLogConfig() { const env = process.env.NODE_ENV || 'development'; return logger[env] || logger.development;}
// Redact sensitive informationfunction redactSensitive(data, redactFields) { const redacted = { ...data }; redactFields.forEach(field => { if (redacted[field]) { redacted[field] = '***REDACTED***'; } }); return redacted;}4. Performance Logging
Section titled “4. Performance Logging”class PerformanceLogger { constructor(logger, threshold = 1000) { this.logger = logger; this.threshold = threshold; // milliseconds }
middleware() { return (req, res, next) => { const startTime = process.hrtime.bigint();
res.on('finish', () => { const endTime = process.hrtime.bigint(); const duration = Number(endTime - startTime) / 1000000; // Convert to ms
if (duration > this.threshold) { this.logger.warn('Slow endpoint', { method: req.method, path: req.path, duration: `${duration.toFixed(2)}ms`, threshold: `${this.threshold}ms`, }); } else { this.logger.debug('Endpoint performance', { method: req.method, path: req.path, duration: `${duration.toFixed(2)}ms`, }); } });
next(); }; }
measureAsync(name, fn) { return async (...args) => { const start = process.hrtime.bigint(); try { const result = await fn(...args); const duration = Number(process.hrtime.bigint() - start) / 1000000;
this.logger.debug(`${name} executed`, { duration: `${duration.toFixed(2)}ms` }); return result; } catch (error) { const duration = Number(process.hrtime.bigint() - start) / 1000000; this.logger.error(`${name} failed`, { duration: `${duration.toFixed(2)}ms`, error: error.message, }); throw error; } }; }}
// Usageconst perfLogger = new PerformanceLogger(logger, 500);app.use(perfLogger.middleware());
const measuredDatabaseQuery = perfLogger.measureAsync( 'User.findById', (userId) => database.users.findById(userId));5. Error Logging with Stack Traces
Section titled “5. Error Logging with Stack Traces”class ErrorLogger { constructor(logger) { this.logger = logger; }
logError(error, context = {}) { const errorInfo = { message: error.message, type: error.constructor.name, stack: error.stack, code: error.code, statusCode: error.statusCode || 500, ...context, };
this.logger.error('Error occurred', errorInfo);
// Additional error tracking if (error.statusCode >= 500) { // Critical error - notify team this.notifyTeam(errorInfo); } }
middleware() { return (err, req, res, next) => { this.logError(err, { requestId: req.id, method: req.method, path: req.path, userAgent: req.get('user-agent'), ip: req.ip, });
const statusCode = err.statusCode || 500; res.status(statusCode).json({ error: { message: err.message, code: err.code, ...(process.env.NODE_ENV === 'development' && { stack: err.stack }), }, }); }; }
notifyTeam(errorInfo) { // Send to Slack, email, etc. console.log('Notifying team about critical error...'); }}
// Usageapp.use((err, req, res, next) => { new ErrorLogger(logger).middleware()(err, req, res, next);});6. Log Aggregation
Section titled “6. Log Aggregation”const winston = require('winston');const WinstonCloudWatch = require('winston-cloudwatch');
const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.json(), transports: [ new winston.transports.Console({ format: winston.format.simple(), }), new WinstonCloudWatch({ awsRegion: 'us-east-1', logGroupName: `/app/${process.env.NODE_ENV}`, logStreamName: `${process.env.HOSTNAME}-${Date.now()}`, messageFormatter: ({ level, message, meta }) => `[${level}] ${message} ${JSON.stringify(meta)}`, }), ], exceptionHandlers: [ new winston.transports.File({ filename: 'exceptions.log' }), ],});
module.exports = logger;Best Practices
Section titled “Best Practices”-
Appropriate Log Levels
- Use ERROR for system failures
- Use WARN for performance issues
- Use INFO for important events
- Use DEBUG during development
-
Structured Logging
- Always use JSON format
- Include context information
- Add timestamps and IDs
- Maintain consistent schema
-
Performance
- Don’t log synchronously
- Use log levels to filter verbosity
- Rotate log files regularly
- Archive old logs
-
Security
- Never log passwords or tokens
- Redact PII
- Control log access
- Secure log storage
-
Maintenance
- Document logging standards
- Review log queries regularly
- Monitor log storage
- Set retention policies
Real-World Applications
Section titled “Real-World Applications”- Production Monitoring: Track application health
- Debugging: Find issues quickly
- Auditing: Track user actions
- Performance Analysis: Identify bottlenecks
- Compliance: Maintain audit trails