💚

Health & Logging

Health Check & Logging

Sistema completo de monitorização e logging para a API e Worker Audissey.

🔭 Observabilidade (Application Insights)

Correlation ID end-to-end

Worker Telemetry

Consultas rápidas (KQL)

📋 Health Check

Endpoints

`GET /api/health`

Health check completo com validação de dependências.

Response:

{
  "status": "ok",
  "info": {
    "database": {
      "status": "up"
    }
  },
  "error": {},
  "details": {
    "database": {
      "status": "up"
    }
  }
}

`GET /api/health/info`

Informação da aplicação (versão, uptime, ambiente).

Response:

{
  "status": "ok",
  "version": "1.0.0",
  "uptime": 3600,
  "timestamp": "2025-12-20T14:30:00.000Z",
  "environment": "development"
}

Configuração

O health check usa @nestjs/terminus e valida:

Uso

# Check se a API está saudável
curl http://localhost:3000/api/health

# Ver informações da aplicação
curl http://localhost:3000/api/health/info

📝 Logging System

Sistema de logging estruturado com níveis configuráveis e contexto completo.

Níveis de Log

Nível Quando Usar Ambiente
INFO Eventos normais (requests HTTP, operações bem-sucedidas) Todos
WARN Situações suspeitas mas recuperáveis Todos
ERROR Erros que precisam atenção Todos
DEBUG Informação detalhada para desenvolvimento Development apenas

Formato de Logs

Development (Human-readable)

[2025-12-20T14:30:00.000Z] [INFO] [HTTP] GET /api/radios 200 - 150ms {"userId":"507f...","duration":150}
[2025-12-20T14:30:05.000Z] [ERROR] [Error] Radio not found {"route":"/api/radios/invalid","userId":"507f..."}

Production (JSON para agregadores)

{
  "timestamp": "2025-12-20T14:30:00.000Z",
  "level": "INFO",
  "context": "HTTP",
  "message": "GET /api/radios 200 - 150ms",
  "metadata": {
    "method": "GET",
    "route": "/api/radios",
    "statusCode": 200,
    "duration": 150,
    "userId": "507f1f77bcf86cd799439011"
  }
}

CustomLoggerService

Service centralizado para logging em toda a aplicação.

Métodos Disponíveis

// Log INFO
logger.log('User created', { context: 'Users', userId: '123' });

// Log ERROR com stack trace
logger.error('Database connection failed', {
  context: 'Database',
  errorStack: error.stack,
  query: 'SELECT * FROM users',
});

// Log WARNING
logger.warn('High memory usage', { context: 'System', memory: '85%' });

// Log DEBUG (apenas em development)
logger.debug('Cache miss', { context: 'Cache', key: 'user:123' });

// Log HTTP request (usado pelo interceptor)
logger.logHttpRequest('GET', '/api/radios', 200, 150, 'userId');

// Log error com contexto completo
logger.logError(error, 'RadiosService', { radioId: '123' });

Uso nos Services

import { Injectable } from '@nestjs/common';
import { CustomLoggerService } from '../common/logger/custom-logger.service';

@Injectable()
export class RadiosService {
  constructor(private readonly logger: CustomLoggerService) {}

  async findAll() {
    this.logger.log('Fetching all radios', { context: 'RadiosService' });

    try {
      const radios = await this.radioModel.find();
      this.logger.log(`Found ${radios.length} radios`, {
        context: 'RadiosService',
        count: radios.length,
      });
      return radios;
    } catch (error) {
      this.logger.logError(error, 'RadiosService', { operation: 'findAll' });
      throw error;
    }
  }
}

HTTP Logging Interceptor

Interceptor global que loga os HTTP requests que chegam ao handler (ver abaixo o que fica de fora).

O que é logado:

Behavior:

Exemplo de output:

[INFO] [HTTP] GET /api/radios 200 - 45ms
[INFO] [HTTP] POST /api/radios 201 - 120ms {"userId":"507f..."}
[INFO] [HTTP] GET /api/radios/invalid 404 - 25ms

Global Exception Filter

Captura todos os erros não tratados e:

  1. Loga erro com contexto completo
  2. Retorna resposta padronizada ao cliente
  3. Inclui metadata útil para debugging

O que é capturado:

Contexto logado:

Os valores dos headers vêm do cliente e são limitados a 200 caracteres, sem
caracteres de controlo, antes de entrarem no log (readClientHeader).

Verificar: nos traces WARN/ERROR de um 401, customDimensions.authHeader
(none = o pedido nem trazia token) e appVersion / appPlatform para saber
que build o enviou.

Response ao cliente:

{
  "statusCode": 404,
  "timestamp": "2025-12-20T14:30:00.000Z",
  "path": "/api/radios/invalid-id",
  "method": "GET",
  "message": "Radio not found",
  "error": "NotFoundException"
}

Exemplo de log interno:

{
  "timestamp": "2025-12-20T14:30:00.000Z",
  "level": "ERROR",
  "context": "Error",
  "message": "Radio not found",
  "metadata": {
    "route": "/api/radios/invalid-id",
    "method": "GET",
    "statusCode": 404,
    "userId": "507f1f77bcf86cd799439011",
    "errorStack": "NotFoundException: Radio not found\n    at RadiosService.findOne...",
    "errorName": "NotFoundException",
    "body": {},
    "query": {},
    "params": { "id": "invalid-id" }
  }
}

🔐 Privacy & Security

Sanitização de Dados Sensíveis

O logger automaticamente redacta campos sensíveis:

Campos redactados:

Exemplo:

logger.log('User authenticated', {
  context: 'Auth',
  email: 'user@example.com',
  password: 'secret123', // Será "***REDACTED***"
  token: 'jwt-token', // Será "***REDACTED***"
});

Output:

[INFO] [Auth] User authenticated {"email":"user@example.com","password":"***REDACTED***","token":"***REDACTED***"}

📊 Integração com Agregadores de Logs

CloudWatch (AWS)

Em produção, logs são outputados em JSON para fácil ingestão:

CloudWatch Logs Insights Query:

fields @timestamp, level, context, message, metadata.userId, metadata.route
| filter level = "ERROR"
| sort @timestamp desc
| limit 100

Datadog

Configuração recomendada:

  1. Adicionar dd-trace ao projeto
  2. Configurar custom metrics baseadas nos logs
  3. Criar dashboards com:
    • Request latency por rota
    • Error rate por endpoint
    • Top users com mais erros

ELK Stack (Elasticsearch, Logstash, Kibana)

Logs em JSON podem ser enviados diretamente para Elasticsearch via Filebeat ou Logstash.

Exemplo de index pattern:

{
  "timestamp": "2025-12-20T14:30:00.000Z",
  "level": "ERROR",
  "context": "Database",
  "message": "Connection timeout",
  "metadata": {
    "route": "/api/radios",
    "userId": "507f1f77bcf86cd799439011"
  }
}

🧪 Testes

Todos os componentes têm testes unitários completos:

# Executar todos os testes
npx nx test api

# Executar testes de logging especificamente
npx nx test api --testPathPattern=logger
npx nx test api --testPathPattern=filter
npx nx test api --testPathPattern=interceptor
npx nx test api --testPathPattern=health

Coverage:


📂 Estrutura de Ficheiros

apps/api/src/app/
├── common/
│   ├── logger/
│   │   ├── custom-logger.service.ts
│   │   └── custom-logger.service.spec.ts
│   ├── filters/
│   │   ├── global-exception.filter.ts
│   │   └── global-exception.filter.spec.ts
│   └── interceptors/
│       ├── http-logging.interceptor.ts
│       └── http-logging.interceptor.spec.ts
├── health/
│   ├── health.controller.ts
│   ├── health.controller.spec.ts
│   └── health.module.ts
├── app.module.ts (logging integrado)
└── main.ts (logger configurado)

🚀 Deploy & Production

Variáveis de Ambiente

# .env
NODE_ENV=production
APP_VERSION=1.2.3
LOG_LEVEL=info  # info, warn, error (debug desabilitado em prod)

Docker

Logs são enviados para stdout/stderr, capturados automaticamente pelo Docker:

# Ver logs do container
docker logs -f audissey-api

# Ver apenas erros
docker logs audissey-api 2>&1 | grep ERROR

Kubernetes

Configuração recomendada:

apiVersion: v1
kind: Pod
metadata:
  name: audissey-api
spec:
  containers:
    - name: api
      image: audissey-api:1.2.3
      env:
        - name: NODE_ENV
          value: 'production'
        - name: APP_VERSION
          value: '1.2.3'

Logs são coletados automaticamente pelo cluster e enviados para agregador centralizado.


💡 Best Practices

1. **Use contexto apropriado**

// ✅ BOM
logger.log('User created', { context: 'UsersService', userId: '123' });

// ❌ MAU
logger.log('User created');

2. **Logue erros com stack trace**

// ✅ BOM
try {
  // código
} catch (error) {
  logger.logError(error, 'UsersService', { operation: 'create' });
  throw error;
}

// ❌ MAU
catch (error) {
  logger.log('Error: ' + error.message);
}

3. **Não logue dados sensíveis**

// ✅ BOM
logger.log('User logged in', { context: 'Auth', userId: user.id });

// ❌ MAU
logger.log('User logged in', { password: user.password });

4. **Use DEBUG para troubleshooting**

// ✅ BOM (não aparece em produção)
logger.debug('Cache lookup', { key: 'user:123', found: false });

// ❌ MAU (polui logs de produção)
logger.log('Cache lookup', { key: 'user:123' });

5. **Logue operações críticas**

// ✅ BOM
logger.log('Payment processed', {
  context: 'Payments',
  userId: user.id,
  amount: 10.5,
  transactionId: 'tx_123',
});

🔍 Debugging

Ver logs em desenvolvimento

# Iniciar API com logs coloridos
npx nx serve api

# Filtrar apenas erros
npx nx serve api 2>&1 | grep ERROR

# Ver apenas requests HTTP
npx nx serve api 2>&1 | grep HTTP

Análise de Performance

Os logs incluem duration de cada request HTTP:

# Encontrar requests lentas (>1000ms)
cat logs/app.log | grep "HTTP" | grep -E "[0-9]{4,}ms"

Troubleshooting Erros

  1. Identificar a rota com problemas:

    cat logs/app.log | grep ERROR | grep "route"
    
  2. Ver stack trace completo:

    cat logs/app.log | grep "errorStack"
    
  3. Agrupar erros por utilizador:

    cat logs/app.log | grep ERROR | grep -o '"userId":"[^"]*"' | sort | uniq -c
    

✅ Critérios de Aceitação


🎯 Métricas Recomendadas

Configure alertas para:

  1. Health Check Down

    • GET /health retorna erro ou não responde
    • Action: Restart container
  2. Error Rate Alto

    • 5% de requests com status 5xx

    • Action: Investigar logs
  3. Latência Alta

    • P95 > 1000ms
    • Action: Performance profiling
  4. Database Connection Failures

    • Logs com "database" + "ERROR"
    • Action: Check MongoDB status

📚 Referências