Health Check & Logging
Sistema completo de monitorização e logging para a API e Worker Audissey.
🔭 Observabilidade (Application Insights)
- Ingestão: logs são enviados como
trace(info/warn/debug) eexception(errors) para Application Insights quando a env varAPPLICATIONINSIGHTS_CONNECTION_STRING(ouAPPINSIGHTS_CONNECTION_STRING) está definida. - Cloud role:
apipara o serviço HTTP eworkerpara o serviço de jobs, facilitando filtros por origem. - Auto-collect: tudo desativado por padrão (console, deps, requests, performance). Apenas envio explícito via
CustomLoggerServicee exceções do worker. - Envio em produção: o
CustomLoggerServiceimprime JSON estruturado e, se houver client AI configurado, também envia o mesmo payload como telemetry. - Campos principais enviados:
message,context,method,route,statusCode,duration,userId,correlationId, além de qualquer metadata extra (com chaves sensíveis redigidas).
Correlation ID end-to-end
- Middleware no API injeta/propaga
x-correlation-idem cada request (ou reutiliza o header recebido) e disponibiliza emreq.correlationId. - O
HttpLoggingInterceptorinclui ocorrelationIdem todos os logs de request (sucesso e erro). - O
GlobalExceptionFiltermantém o mesmocorrelationIdna resposta de erro e no log, permitindo seguir a cadeia de chamadas entre serviços. - Para chamadas entre serviços, envie sempre o header
x-correlation-idpara preservar o trace.
Worker Telemetry
- O worker inicia Application Insights no bootstrap; se não houver connection string configurada, roda sem telemetry.
- Exceptions não tratadas (
uncaughtException,unhandledRejection) são logadas e reportadas comoexceptionpara Application Insights.
Consultas rápidas (KQL)
- Traces por rota:
traces | where cloud_RoleName == "api" | where customDimensions.route startswith "/api" | project timestamp, message, customDimensions - Erros recentes:
exceptions | where timestamp > ago(1h) | where cloud_RoleName in ("api","worker") | project timestamp, type, message, customDimensions.correlationId
📋 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:
- ✅ Database (MongoDB): Verifica conexão com ping
- ⚪ Futuramente: Redis, serviços externos, disco, memória
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:
- Método HTTP (GET, POST, PUT, DELETE)
- Rota completa
- Status code da resposta. Num pedido que falha é o do erro (o mesmo que o
GlobalExceptionFilterresponde: o de umaHttpException, 400 para umCastError, 500 para o resto), porque o filtro só põe o status na resposta
depois de o interceptor ter registado o erro; antes de 2026-10 um 404 era
registado como200 - Duração da request (em ms)
- User ID (se autenticado)
Behavior:
- Development: Loga todos os pedidos que chegam ao handler
- Production: Os pedidos bem-sucedidos (status < 400) não são registados;
só os que falham (erro lançado no handler ou nos pipes, ou status >= 400) - Cada pedido registado dá uma linha
[INFO] [HTTP]: o nível não depende do
status. O filtro de exceções acrescenta a sua própria linha a cada erro
(WARNpara 401/403,ERRORpara o resto). Corrigir o status não mudou o
número de linhas, só ostatusCodeque levam - Sem linha
[HTTP]: o interceptor só vê o que passa pelos guards (os
guards correm antes dos interceptors do Nest). Um 401/403 de um guard
(JwtAuthGuard,AdminGuard...), um 429 do throttler e uma rota que não
existe não geram linha[HTTP]; só a do filtro de exceções, comauthHeader,appVersioneappPlatform
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:
- Loga erro com contexto completo
- Retorna resposta padronizada ao cliente
- Inclui metadata útil para debugging
O que é capturado:
HttpException(do NestJS)Errorgenérico- Qualquer outro tipo de exception
Contexto logado:
- Rota e método HTTP
- User ID (se autenticado)
- Status code
- Stack trace
- Request body, query params, path params
appVersioneappPlatform: os headersX-App-Version/X-App-Platform
que o Application Insights também lê (unknownquando o cliente não os
envia)authHeader: o que veio emAuthorization, nunca o token:bearer(esquema
Bearer com token),none(sem header ou em branco) ouother(outro
esquema, ouBearersem token). Distingue um 401 sem token de um 401 com
token expirado ou revogado
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:
passwordtokensecretauthorization- Qualquer campo que contenha essas palavras-chave
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:
- Adicionar
dd-traceao projeto - Configurar custom metrics baseadas nos logs
- 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:
CustomLoggerService: 100%GlobalExceptionFilter: 100%HttpLoggingInterceptor: 100%HealthController: 100%
📂 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
Identificar a rota com problemas:
cat logs/app.log | grep ERROR | grep "route"Ver stack trace completo:
cat logs/app.log | grep "errorStack"Agrupar erros por utilizador:
cat logs/app.log | grep ERROR | grep -o '"userId":"[^"]*"' | sort | uniq -c
✅ Critérios de Aceitação
- Endpoint GET /health devolve estado simples ("ok") + info básica (versão, uptime)
- Endpoint GET /health/info devolve versão, uptime, timestamp, environment
- Health check valida MongoDB com ping check
- Logging configurado com níveis info, error, warn, debug
- CustomLoggerService com métodos para todos os níveis
- Erros críticos registados com contexto mínimo (rota, utilizador, stack trace)
- HTTP requests logados automaticamente com método, rota, status, duration, userId
- Exception filter global captura todos os erros não tratados
- Sanitização de dados sensíveis (passwords, tokens)
- Formato JSON em production para agregadores de logs
- Formato human-readable em development com cores
- Testes completos para todos os componentes
- Documentação completa com exemplos de uso
🎯 Métricas Recomendadas
Configure alertas para:
Health Check Down
- GET /health retorna erro ou não responde
- Action: Restart container
Error Rate Alto
5% de requests com status 5xx
- Action: Investigar logs
Latência Alta
- P95 > 1000ms
- Action: Performance profiling
Database Connection Failures
- Logs com "database" + "ERROR"
- Action: Check MongoDB status