Logs em Node.js para produção

    Logs são o principal instrumento de diagnóstico em produção. Em Node.js, console.log está longe do ideal para produção — use Winston para logs estruturados em JSON com níveis de severidade, contexto por requisição e integração nativa com Docker. Com logs bem estruturados, você encontra erros em segundos em vez de minutos.

    Configurar Winston para produção

    Winston é o logger mais popular para Node.js. Configure para emitir JSON em produção e formato legível em desenvolvimento:

    typescript
    // src/logger.ts
    import winston from 'winston'
    
    const isProduction = process.env.NODE_ENV === 'production'
    
    export const logger = winston.createLogger({
      level: process.env.LOG_LEVEL || (isProduction ? 'info' : 'debug'),
      format: isProduction
        ? winston.format.combine(
            winston.format.timestamp(),
            winston.format.errors({ stack: true }),
            winston.format.json()              // JSON em produção
          )
        : winston.format.combine(
            winston.format.colorize(),
            winston.format.simple()            // legível em desenvolvimento
          ),
      transports: [
        new winston.transports.Console(),      // sempre stdout/stderr
      ],
      // Capturar exceções e rejections não tratadas
      exceptionHandlers: [new winston.transports.Console()],
      rejectionHandlers: [new winston.transports.Console()],
    })
    
    // Uso:
    logger.info('Servidor iniciado', { port: 3000 })
    logger.error('Falha ao conectar', { error: err.message, stack: err.stack })
    Dica
    Em Docker, sempre use transports.Console() (stdout) — não grave logs em arquivo dentro do container. O Docker coleta stdout/stderr e permite acesso via docker logs. Gravar em arquivo dentro do container mistura o ciclo de vida dos logs com o do container.

    Logs por requisição HTTP com Express

    Adicione contexto de request_id, método, rota e duração em cada log de requisição:

    typescript
    // src/middleware/request-logger.ts
    import { Request, Response, NextFunction } from 'express'
    import { randomUUID } from 'crypto'
    import { logger } from '../logger'
    
    export function requestLogger(req: Request, res: Response, next: NextFunction) {
      const requestId = randomUUID()
      const start = Date.now()
    
      // Adicionar request_id ao request para uso em logs subsequentes
      req.requestId = requestId
    
      res.on('finish', () => {
        const duration = Date.now() - start
    
        logger.info('HTTP request', {
          request_id: requestId,
          method: req.method,
          path: req.path,
          status: res.statusCode,
          duration_ms: duration,
          user_agent: req.headers['user-agent'],
          ip: req.ip,
        })
      })
    
      next()
    }
    
    // app.ts
    app.use(requestLogger)
    
    // Output em produção (JSON):
    // {"level":"info","message":"HTTP request","method":"GET","path":"/users","status":200,"duration_ms":23}

    Capturar erros não tratados

    Exceções e rejections não capturadas são os erros mais difíceis de debugar em produção — certifique-se de logá-los antes do processo encerrar:

    typescript
    // src/index.ts
    import { logger } from './logger'
    
    // Exceções síncronas não capturadas
    process.on('uncaughtException', (error: Error) => {
      logger.error('Uncaught exception — processo será encerrado', {
        error: error.message,
        stack: error.stack,
      })
      // Dar tempo para o logger fazer flush antes de encerrar
      setTimeout(() => process.exit(1), 1000)
    })
    
    // Promises rejeitadas sem .catch()
    process.on('unhandledRejection', (reason: unknown) => {
      logger.error('Unhandled promise rejection', {
        reason: reason instanceof Error ? reason.message : String(reason),
        stack: reason instanceof Error ? reason.stack : undefined,
      })
    })
    
    // Sinal de encerramento gracioso (SIGTERM do Docker stop)
    process.on('SIGTERM', () => {
      logger.info('SIGTERM recebido — encerrando servidor...')
      server.close(() => {
        logger.info('Servidor encerrado com sucesso')
        process.exit(0)
      })
    })

    Filtrar e buscar logs no Docker

    Comandos para trabalhar com logs de containers em produção:

    bash
    # Ver últimas 100 linhas
    docker compose logs app --tail=100
    
    # Seguir em tempo real
    docker compose logs app -f
    
    # Filtrar apenas erros (logs em JSON)
    docker compose logs app 2>&1 | grep '"level":"error"'
    
    # Buscar por request_id específico
    docker compose logs app 2>&1 | grep '"request_id":"abc-123"'
    
    # Filtrar por período (requer Docker 20.10+)
    docker logs --since="2026-06-07T10:00:00" --until="2026-06-07T11:00:00" nome_container
    
    # Parsear logs JSON com jq (apt install jq)
    docker compose logs app 2>&1 | grep '^{' | jq 'select(.level == "error") | {time: .timestamp, msg: .message, path: .path}'
    Dica
    jq é indispensável para trabalhar com logs JSON no terminal. Instale com apt install jq. Permite filtrar, selecionar campos e formatar a saída de forma muito mais eficiente que grep.

    Evitar os erros mais comuns de logging

    Práticas que causam problemas em produção e como evitá-las:

    typescript
    // ❌ NÃO faça em produção:
    
    // 1. Logar objetos sensíveis
    logger.info('Usuário autenticado', { user, password }) // expõe senha
    
    // 2. console.log em vez de logger
    console.log('debug:', req.body) // sem nível, sem JSON, sem timestamp
    
    // 3. Strings de erro sem stack trace
    logger.error('Erro: ' + err.message) // perde o stack trace
    
    // 4. Logar a cada tick/iteração
    setInterval(() => logger.debug('tick'), 100) // ~35k logs/hora
    
    // ✅ FAÇA:
    
    // 1. Sanitizar antes de logar
    const { password, token, ...safeUser } = user
    logger.info('Usuário autenticado', { user: safeUser })
    
    // 2. Sempre usar o logger configurado
    logger.debug('Request body', { body: req.body })
    
    // 3. Passar o objeto Error completo
    logger.error('Falha na operação', { error: err })
    // Winston extrai message e stack automaticamente com format.errors()
    
    // 4. Logar apenas eventos significativos
    // A cada N iterações, ou apenas quando o estado muda

    $ runstack deploy --plan starter

    Não quer configurar manualmente?

    Não quer configurar manualmente? Implante o VPS em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.

    Perguntas frequentes