DEV Community

Atlas Whoff
Atlas Whoff

Posted on

Structured Logging: From console.log to Production-Ready Observability

Structured Logging: From console.log to Production-Ready Observability

console.log is fine for development. In production, you need logs you can search, filter, and alert on.

Why Structured Logging

// Unstructured — hard to query
console.log('User 123 placed order 456 for $29.99 at 2024-01-15T10:30:00Z');

// Structured — queryable as JSON
logger.info('order.created', {
  userId: '123',
  orderId: '456',
  amount: 2999,
  currency: 'usd',
});
Enter fullscreen mode Exit fullscreen mode

Pino: Fast Structured Logger for Node.js

npm install pino pino-pretty
Enter fullscreen mode Exit fullscreen mode
import pino from 'pino';

export const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  ...(process.env.NODE_ENV === 'development' ? {
    transport: { target: 'pino-pretty', options: { colorize: true } }
  } : {}),
  // Add base fields to every log
  base: {
    service: 'api',
    version: process.env.npm_package_version,
    env: process.env.NODE_ENV,
  },
  // Redact sensitive fields
  redact: ['req.headers.authorization', 'body.password', 'body.cardNumber'],
});
Enter fullscreen mode Exit fullscreen mode

Request Context Logging

import { AsyncLocalStorage } from 'async_hooks';

const requestContext = new AsyncLocalStorage<{ requestId: string; userId?: string }>();

// Middleware: set context for entire request lifecycle
app.use((req, res, next) => {
  const requestId = crypto.randomUUID();
  requestContext.run({ requestId }, next);
});

// Logger with context
function log(level: 'info' | 'warn' | 'error', message: string, data?: object) {
  const ctx = requestContext.getStore();
  logger[level]({ ...ctx, ...data }, message);
}

// Any log from within a request automatically includes requestId
log('info', 'order.created', { orderId: '123' });
// → { requestId: 'uuid', userId: '456', orderId: '123', message: 'order.created' }
Enter fullscreen mode Exit fullscreen mode

Log Levels

logger.trace('Entering function');  // verbose debugging
logger.debug('Query executed', { sql, duration });  // development
logger.info('User logged in', { userId });   // normal operations
logger.warn('Rate limit approaching', { current, limit }); // might be a problem
logger.error('Payment failed', { error, userId, orderId }); // needs attention
logger.fatal('Database unreachable'); // service is down
Enter fullscreen mode Exit fullscreen mode

Sending to a Log Aggregator

// Send to Loki (Grafana's log storage)
const logger = pino(
  { level: 'info' },
  pino.transport({
    target: 'pino-loki',
    options: {
      host: process.env.LOKI_URL,
      labels: { app: 'api', env: process.env.NODE_ENV },
    },
  })
);
Enter fullscreen mode Exit fullscreen mode

Structured logging with Pino, request correlation IDs, and log aggregation are configured in the AI SaaS Starter Kit.

Top comments (0)