// Structured logging with Winston (Node.js)
const winston = require('winston');
const { v4: uuidv4 } = require('uuid');
// Create logger
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()
),
defaultMeta: {
service: process.env.SERVICE_NAME || 'web-app',
environment: process.env.NODE_ENV || 'development',
version: process.env.APP_VERSION || '1.0.0',
},
transports: [
new winston.transports.Console(),
// File transport for production
...(process.env.NODE_ENV === 'production'
? [
new winston.transports.File({
filename: '/var/log/app/error.log',
level: 'error',
maxsize: 50 * 1024 * 1024, // 50MB
maxFiles: 5,
}),
new winston.transports.File({
filename: '/var/log/app/combined.log',
maxsize: 100 * 1024 * 1024,
maxFiles: 10,
}),
]
: []),
],
});
// Sensitive field redaction
const SENSITIVE_FIELDS = ['password', 'token', 'secret', 'authorization', 'cookie'];
function redactSensitive(obj) {
if (!obj || typeof obj !== 'object') return obj;
const redacted = { ...obj };
for (const key of Object.keys(redacted)) {
if (SENSITIVE_FIELDS.some(f => key.toLowerCase().includes(f))) {
redacted[key] = '[REDACTED]';
}
}
return redacted;
}
// Request logging middleware (Express)
function requestLogger(req, res, next) {
// Generate correlation ID
req.correlationId = req.headers['x-correlation-id'] || uuidv4();
res.setHeader('x-correlation-id', req.correlationId);
const startTime = process.hrtime.bigint();
// Log request
logger.info('Incoming request', {
correlationId: req.correlationId,
method: req.method,
path: req.path,
query: req.query,
userAgent: req.headers['user-agent'],
ip: req.ip,
userId: req.user?.id,
});
// Log response on finish
res.on('finish', () => {
const duration = Number(process.hrtime.bigint() - startTime) / 1e6;
const logData = {
correlationId: req.correlationId,
method: req.method,
path: req.path,
statusCode: res.statusCode,
duration: Math.round(duration * 100) / 100,
contentLength: res.getHeader('content-length'),
userId: req.user?.id,
};
if (res.statusCode >= 500) {
logger.error('Request failed', logData);
} else if (res.statusCode >= 400) {
logger.warn('Client error', logData);
} else {
logger.info('Request completed', logData);
}
});
next();
}
// Child logger with context
function createChildLogger(context) {
return logger.child(context);
}
// Usage in services
class OrderService {
constructor() {
this.logger = createChildLogger({ module: 'OrderService' });
}
async createOrder(userId, items) {
const orderLogger = this.logger.child({
userId,
action: 'createOrder',
itemCount: items.length,
});
orderLogger.info('Creating order');
try {
const order = await db.orders.create({ userId, items });
orderLogger.info('Order created successfully', {
orderId: order.id,
total: order.total,
});
return order;
} catch (error) {
orderLogger.error('Failed to create order', {
error: error.message,
stack: error.stack,
});
throw error;
}
}
}
module.exports = { logger, requestLogger, createChildLogger };