Diretrizes de Desenvolvimento
13. Monitoramento e Logging

13. Monitoramento e Logging

O monitoramento eficaz e o logging detalhado são fundamentais para manter a saúde, performance e segurança de nossas aplicações. Esta seção fornece diretrizes abrangentes para implementar estratégias robustas de logging e monitoramento em nossos projetos Node.js e Laravel.

13.1. Estratégias de logging

Um sistema de logging bem projetado é crucial para o diagnóstico de problemas, análise de performance e auditoria de segurança.

13.1.1. Níveis de Log

Utilize os seguintes níveis de log consistentemente:

  1. ERROR: Erros críticos que impedem o funcionamento da aplicação.
  2. WARN: Situações anômalas que não impedem o funcionamento, mas requerem atenção.
  3. INFO: Informações gerais sobre o fluxo da aplicação.
  4. DEBUG: Informações detalhadas úteis para debugging.
  5. TRACE: Informações muito detalhadas, geralmente usadas apenas em desenvolvimento.

13.1.2. Implementação em Node.js

Utilize o pacote winston para logging estruturado:

const winston = require('winston');
 
const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  defaultMeta: { service: 'user-service' },
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
  ],
});
 
// Exemplo de uso
logger.info('User logged in', { userId: user.id });
logger.error('Database connection failed', { error: err.message });

13.1.3. Implementação em Laravel

Utilize o sistema de logging nativo do Laravel, configurado em config/logging.php:

use Illuminate\Support\Facades\Log;
 
// Exemplo de uso
Log::info('User logged in', ['userId' => $user->id]);
Log::error('Database connection failed', ['error' => $e->getMessage()]);

Configure canais de log múltiplos para separar diferentes tipos de logs:

'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['single', 'slack'],
    ],
    'single' => [
        'driver' => 'single',
        'path' => storage_path('logs/laravel.log'),
        'level' => 'debug',
    ],
    'slack' => [
        'driver' => 'slack',
        'url' => env('LOG_SLACK_WEBHOOK_URL'),
        'username' => 'Laravel Log',
        'emoji' => ':boom:',
        'level' => 'critical',
    ],
],

13.1.4. Boas Práticas de Logging

  1. Estruture seus logs: Use formatos como JSON para facilitar a análise.
  2. Inclua contexto: Adicione informações relevantes como IDs de usuário, IDs de transação, etc.
  3. Evite informações sensíveis: Nunca logue senhas ou tokens de acesso.
  4. Use log sampling: Para logs de alto volume, considere amostrar para reduzir o ruído.
  5. Correlação de logs: Use IDs de correlação para rastrear requisições através de múltiplos serviços.

13.2. Ferramentas de monitoramento

Utilizamos uma combinação de ferramentas para garantir uma observabilidade completa de nossas aplicações.

13.2.1. Sentry

Sentry é nossa principal ferramenta para rastreamento de erros e performance.

Para Node.js:

const Sentry = require("@sentry/node");
 
Sentry.init({
  dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
  tracesSampleRate: 1.0,
});
 
// Exemplo de uso
try {
  someFunction();
} catch (e) {
  Sentry.captureException(e);
}

Para Laravel:

// config/sentry.php
return [
    'dsn' => env('SENTRY_LARAVEL_DSN'),
    'traces_sample_rate' => env('SENTRY_TRACES_SAMPLE_RATE', 0.2),
];
 
// Uso automático para exceções não tratadas
// Para logging manual:
\Sentry\captureException($exception);

13.2.2. New Relic

New Relic é usado para APM (Application Performance Monitoring) detalhado.

Para Node.js:

require('newrelic');
 
// O resto do seu código de aplicação

Para Laravel:

// Instale via Composer
composer require newrelic/newrelic-laravel
 
// Em app/Providers/AppServiceProvider.php
public function boot()
{
    if (extension_loaded('newrelic')) {
        newrelic_set_appname(env('NEW_RELIC_APP_NAME'));
    }
}

13.2.3. CloudWatch

AWS CloudWatch pode ser usado para métricas de infraestrutura e logs centralizados.

Para Node.js:

const AWS = require('aws-sdk');
const cloudwatch = new AWS.CloudWatch({region: 'us-west-2'});
 
cloudwatch.putMetricData({
  MetricData: [
    {
      MetricName: 'PAGES_VISITED',
      Dimensions: [
        {
          Name: 'UNIQUE_PAGES',
          Value: 'URLS'
        }
      ],
      Unit: 'None',
      Value: 1.0
    }
  ],
  Namespace: 'SITE/TRAFFIC'
}, (err, data) => {
  if (err) console.error(err);
  else console.log(data);
});

Para Laravel, considere usar um pacote como aws/aws-sdk-php-laravel.

13.3. Alertas e notificações

Configure alertas para ser notificado proativamente sobre problemas.

  1. Sentry: Configure alertas baseados em frequência de erros ou novos tipos de erro.
  2. New Relic: Configure alertas baseados em métricas de performance, como tempo de resposta ou taxa de erro.
  3. CloudWatch: Configure alarmes para métricas de infraestrutura, como uso de CPU ou memória.

Exemplo de configuração de alerta no New Relic (via API):

const newrelic = require('newrelic');
 
newrelic.noticeError(new Error('Critical system error'));

13.4. Análise de logs e métricas

A análise eficaz de logs e métricas é crucial para entender o comportamento do sistema e identificar problemas.

13.4.1. Dashboards

Crie dashboards personalizados em cada ferramenta para visualizar métricas importantes:

  1. Sentry: Taxas de erro, performance de transações.
  2. New Relic: Tempo de resposta, throughput, uso de recursos.
  3. CloudWatch: Métricas de infraestrutura, logs de aplicação.

13.4.2. Análise de Logs

  1. Use ferramentas como ELK Stack (Elasticsearch, Logstash, Kibana) para análise centralizada de logs.
  2. Implemente buscas e alertas baseados em padrões nos logs.

13.4.3. Tracing Distribuído

Implemente tracing distribuído para entender o fluxo de requisições através de múltiplos serviços.

Para Node.js com OpenTelemetry:

const opentelemetry = require('@opentelemetry/api');
const { NodeTracerProvider } = require('@opentelemetry/node');
const { SimpleSpanProcessor } = require('@opentelemetry/tracing');
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
 
const provider = new NodeTracerProvider();
const exporter = new JaegerExporter();
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
provider.register();
 
const tracer = opentelemetry.trace.getTracer('example-basic-tracer-node');
 
// Uso
const span = tracer.startSpan('main');
// ... lógica da aplicação ...
span.end();

Para Laravel, considere usar o pacote laravel/telescope para tracing interno.

13.5. Práticas Ruins de Logging (O que evitar)

É crucial entender o que constitui práticas ruins de logging para evitá-las em nosso código. Aqui estão alguns exemplos do que nunca deve ser feito:

13.5.1. Logging Excessivo ou Inadequado

Ruim:

console.log("Eu sou o Luan e estou fazendo um request");
console.log(axios.post('https://api.example.com/data', { sensitive: 'information' }));

Problemas:

  • Uso de console.log em produção
  • Logging de informações sensíveis
  • Mensagens de log não estruturadas e pessoais

13.5.2. Logging de Informações Sensíveis

Ruim:

logger.info(`User ${user.email} logged in with password ${user.password}`);

Problemas:

  • Logging de dados sensíveis (senha)
  • Violação de privacidade e potenciais problemas de segurança

13.5.3. Logging Inconsistente

Ruim:

if (error) {
    console.error("Oops, algo deu errado!");
}
// Em outra parte do código
if (anotherError) {
    log.error({ err: anotherError }, "Um erro ocorreu durante o processamento");
}

Problemas:

  • Inconsistência no formato e no método de logging
  • Falta de contexto e detalhes nos logs de erro

13.5.4. Logging sem Níveis Apropriados

Ruim:

logger.error("Usuário fez login");
logger.error("Banco de dados não está respondendo");

Problemas:

  • Uso incorreto dos níveis de log
  • Dificulta a filtragem e análise de logs realmente importantes

13.6. Boas Práticas de Logging com Exemplos

Agora, vamos ver exemplos de boas práticas de logging que proporcionam uma visão clara para auditoria e observabilidade.

13.6.1. Logging Estruturado e Contextual

Bom:

const logger = require('./logger');
 
function processOrder(order) {
    logger.info('Processing order', {
        orderId: order.id,
        userId: order.userId,
        totalAmount: order.totalAmount,
        itemCount: order.items.length
    });
 
    // Processamento da ordem...
 
    logger.info('Order processed successfully', {
        orderId: order.id,
        processingTime: performance.now() - startTime
    });
}

13.6.2. Logging de Erros com Contexto

Bom:

try {
    await saveUserData(userData);
} catch (error) {
    logger.error('Failed to save user data', {
        userId: userData.id,
        error: error.message,
        stack: error.stack
    });
}

13.6.3. Logging para Auditoria

Bom:

function updateUserRole(userId, newRole, adminId) {
    logger.info('User role update', {
        event: 'USER_ROLE_UPDATE',
        userId: userId,
        oldRole: user.currentRole,
        newRole: newRole,
        updatedBy: adminId,
        timestamp: new Date().toISOString()
    });
 
    // Lógica para atualizar o papel do usuário...
}

13.6.4. Logging para Observabilidade

Bom:

const tracer = require('./tracer');
 
async function handleAPIRequest(req, res) {
    const span = tracer.startSpan('api_request');
    span.setTag('http.method', req.method);
    span.setTag('http.url', req.url);
 
    const startTime = Date.now();
 
    try {
        // Processamento da requisição...
        const result = await processRequest(req.body);
 
        logger.info('API request processed', {
            method: req.method,
            url: req.url,
            processingTime: Date.now() - startTime,
            resultStatus: result.status
        });
 
        res.json(result);
    } catch (error) {
        span.setTag('error', true);
        span.log({ event: 'error', 'error.object': error, message: error.message, stack: error.stack });
 
        logger.error('API request failed', {
            method: req.method,
            url: req.url,
            processingTime: Date.now() - startTime,
            error: error.message
        });
 
        res.status(500).json({ error: 'Internal Server Error' });
    } finally {
        span.finish();
    }
}

13.6.5. Métricas de Performance

Bom:

const metrics = require('./metrics');
 
async function queryDatabase(query) {
    const timer = metrics.createTimer('database_query_duration');
    try {
        const result = await database.query(query);
        metrics.increment('database_query_success');
        return result;
    } catch (error) {
        metrics.increment('database_query_error');
        throw error;
    } finally {
        timer.end();
    }
}

13.7. Observabilidade Avançada

Para uma observabilidade verdadeiramente eficaz, combine logging, tracing e métricas:

const opentelemetry = require('@opentelemetry/api');
const { metrics, trace, logger } = opentelemetry;
 
async function processPayment(paymentDetails) {
    const tracer = trace.getTracer('payment-service');
    const meter = metrics.getMeter('payment-metrics');
    const paymentCounter = meter.createCounter('payments_processed');
 
    const span = tracer.startSpan('process_payment');
    span.setAttribute('paymentId', paymentDetails.id);
    span.setAttribute('amount', paymentDetails.amount);
 
    try {
        logger.info('Starting payment processing', {
            paymentId: paymentDetails.id,
            amount: paymentDetails.amount,
            currency: paymentDetails.currency
        });
 
        // Lógica de processamento de pagamento...
 
        paymentCounter.add(1, { status: 'success', paymentMethod: paymentDetails.method });
 
        logger.info('Payment processed successfully', {
            paymentId: paymentDetails.id,
            processingTime: span.duration
        });
 
        span.setStatus({ code: opentelemetry.SpanStatusCode.OK });
    } catch (error) {
        paymentCounter.add(1, { status: 'failed', paymentMethod: paymentDetails.method });
 
        logger.error('Payment processing failed', {
            paymentId: paymentDetails.id,
            error: error.message,
            stack: error.stack
        });
 
        span.setStatus({ code: opentelemetry.SpanStatusCode.ERROR, message: error.message });
        span.recordException(error);
    } finally {
        span.end();
    }
}

Neste exemplo avançado, combinamos:

  • Logging estruturado para eventos importantes
  • Tracing distribuído para acompanhar o fluxo da transação
  • Métricas para medir o volume e o sucesso das transações
  • Atributos e tags consistentes entre logs, traces e métricas para correlação fácil

Diretrizes Adicionais:

  1. Padronização: Mantenha um formato consistente de logging e nomeação de métricas em todos os serviços.
  2. Retenção de Dados: Defina políticas claras de retenção de logs e métricas, considerando requisitos legais e de negócio.
  3. Treinamento: Forneça treinamento regular à equipe sobre como usar efetivamente as ferramentas de monitoramento e logging.
  4. Revisão Contínua: Realize revisões periódicas das estratégias de logging e monitoramento para garantir que atendam às necessidades em evolução do sistema.
  5. Automação: Automatize a criação de dashboards e alertas para novos serviços e funcionalidades.

Responsabilidades da Equipe:

  1. Desenvolvedores:

    • Implementar logging consistente em todo o código novo.
    • Utilizar as ferramentas de APM para identificar e resolver problemas de performance.
  2. DevOps:

    • Manter e otimizar a infraestrutura de logging e monitoramento.
    • Configurar e ajustar alertas baseados em feedback da equipe.
  3. Tech Leads:

    • Revisar e aprovar estratégias de logging e monitoramento.
    • Garantir que as práticas de observabilidade sejam seguidas consistentemente.
  4. QA:

    • Verificar se os logs fornecem informações suficientes para debugging em ambientes de teste.
    • Utilizar dados de monitoramento para identificar problemas de qualidade.