Structured logging with ELK stack integration

Ryan Nakamura Feb 2026
2 tabs
// 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 };
2 files · javascript, xml Explain with highlit

Structured logging outputs JSON-formatted log entries for machine parsing. Each log line includes timestamp, level, message, and contextual fields like request_id, user_id, and service. Structured logs enable powerful queries in Elasticsearch through Kibana. Log levels (debug, info, warn, error) filter noise in different environments. Correlation IDs trace requests across microservices. The ELK stack (Elasticsearch, Logstash, Kibana) or EFK (Elasticsearch, Fluentd, Kibana) centralizes logs. Fluentd or Filebeat ships logs from containers. Log rotation prevents disk exhaustion. Sensitive data like passwords and tokens must be redacted. Structured logging transforms debugging from grepping text files to querying indexed, searchable data.