RFC-013: Error Handling e Logging Standards
| Campo | Valor |
|---|---|
| Status | Draft |
| Author | Time de Tecnologia |
| Created | 2026-02-05 |
| Updated | 2026-02-05 |
| Reunião | A ser agendada |
| Deciders | Todo o time técnico |
Status
🟡 Draft - RFC em elaboração, aguardando apresentação
Contexto e Problema
Logging documentado em docs/observability/cloudwatch.md, mas falta:
- Error handling patterns padronizados
- Structured logging enforcement (JSON)
- Log levels standardizados por ambiente
- Correlation IDs obrigatórios em todos os requests
- Error tracking centralizado (Sentry ou similar)
Por que resolver isso agora?
- Logs não estruturados são difíceis de query
- Debugging sem correlation IDs é lento
- Error handling inconsistente no código
- Falta visibilidade de errors agregados
- CloudWatch Insights requer JSON structured logs
Impacto de não resolver
- MTTR alto (debugging lento)
- Errors não são agregados/tracked
- Correlação entre logs impossível
- CloudWatch Insights não funciona bem
- Dificuldade em troubleshooting produção
Documentação relacionada:
- docs/observability/cloudwatch.md - Logging atual
- docs/development/coding-standards.md - Error handling
- docs/observability/monitoring.md - Correlation IDs
- docs/architecture/api-design.md - Error responses
Proposta de Solução
Structured logging (JSON), correlation IDs obrigatórios, error patterns padronizados, e Sentry para error tracking.
Structured Logging (JSON)
Python (structlog):
import structlog
# Configuração global
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.JSONRenderer()
],
wrapper_class=structlog.stdlib.BoundLogger,
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
)
logger = structlog.get_logger()
# Uso
logger.info(
"user_login_successful",
user_id=user.id,
email=user.email,
ip_address=request.client.host,
duration_ms=123
)
logger.error(
"payment_processing_failed",
user_id=user.id,
order_id=order.id,
amount=order.total,
error_code="STRIPE_ERROR",
exc_info=True # Inclui stack trace
)
Output (JSON):
{
"event": "payment_processing_failed",
"user_id": 123,
"order_id": 456,
"amount": 99.99,
"error_code": "STRIPE_ERROR",
"exception": "stripe.error.CardError: Your card was declined...",
"timestamp": "2026-02-05T14:30:00.123456Z",
"level": "error",
"logger": "app.payments",
"correlation_id": "550e8400-e29b-41d4-a716-446655440000"
}
Log Levels por Ambiente
Development: - DEBUG: Tudo - INFO: Eventos importantes - WARNING: Problemas potenciais - ERROR: Errors - CRITICAL: Failures críticos
Staging: - INFO: Eventos importantes - WARNING: Problemas potenciais - ERROR: Errors - CRITICAL: Failures críticos
Production: - INFO: Eventos de negócio importantes apenas - WARNING: Problemas potenciais - ERROR: Errors - CRITICAL: Failures críticos
O que logar:
✅ Logar: - Autenticação (login, logout, failures) - Operações importantes (create user, payment, etc.) - Errors e exceptions - Performance metrics - Security events - External API calls
❌ Não logar: - Passwords ou secrets - Credit card numbers (PCI compliance) - Dados sensíveis (CPF, endereço completo) - Tokens ou API keys - Informação médica (HIPAA)
Sanitização:
def sanitize_log_data(data: dict) -> dict:
"""Remove sensitive data from logs."""
sensitive_fields = ['password', 'credit_card', 'ssn', 'api_key']
sanitized = data.copy()
for field in sensitive_fields:
if field in sanitized:
sanitized[field] = '***REDACTED***'
return sanitized
logger.info(
"user_created",
**sanitize_log_data(user_data)
)
Correlation IDs
Geração e propagação:
# Middleware FastAPI
import uuid
from fastapi import Request
import structlog
@app.middleware("http")
async def correlation_id_middleware(request: Request, call_next):
# Obter ou gerar
correlation_id = request.headers.get("X-Correlation-ID") or str(uuid.uuid4())
# Adicionar ao contexto de logging
structlog.contextvars.clear_contextvars()
structlog.contextvars.bind_contextvars(
correlation_id=correlation_id,
method=request.method,
path=request.url.path
)
# Processar request
response = await call_next(request)
# Adicionar ao response
response.headers["X-Correlation-ID"] = correlation_id
return response
Propagação para serviços downstream:
async def call_external_api(url: str):
"""Call external API with correlation ID."""
correlation_id = structlog.contextvars.get_contextvars().get("correlation_id")
response = await httpx.get(
url,
headers={"X-Correlation-ID": correlation_id}
)
logger.info(
"external_api_called",
url=url,
status_code=response.status_code,
duration_ms=response.elapsed.total_seconds() * 1000
)
return response
Busca de logs:
-- CloudWatch Logs Insights
fields @timestamp, event, level, message
| filter correlation_id = "550e8400-e29b-41d4-a716-446655440000"
| sort @timestamp asc
Error Handling Patterns
Custom Exceptions (Domain-specific):
# app/exceptions.py
class AppException(Exception):
"""Base exception for all app exceptions."""
def __init__(self, message: str, code: str, status_code: int = 500):
self.message = message
self.code = code
self.status_code = status_code
super().__init__(message)
class ValidationError(AppException):
"""Validation failed."""
def __init__(self, message: str, field: str = None):
super().__init__(message, "VALIDATION_ERROR", 400)
self.field = field
class NotFoundError(AppException):
"""Resource not found."""
def __init__(self, resource: str, resource_id: any):
super().__init__(
f"{resource} with id {resource_id} not found",
"NOT_FOUND",
404
)
class UnauthorizedError(AppException):
"""User not authenticated."""
def __init__(self):
super().__init__("Authentication required", "UNAUTHORIZED", 401)
class ForbiddenError(AppException):
"""User not authorized."""
def __init__(self, action: str):
super().__init__(
f"Not authorized to {action}",
"FORBIDDEN",
403
)
Exception Handler (FastAPI):
from fastapi import Request
from fastapi.responses import JSONResponse
@app.exception_handler(AppException)
async def app_exception_handler(request: Request, exc: AppException):
"""Handle all app exceptions."""
logger.error(
"request_failed",
error_code=exc.code,
error_message=exc.message,
status_code=exc.status_code,
path=request.url.path,
method=request.method
)
return JSONResponse(
status_code=exc.status_code,
content={
"error": {
"code": exc.code,
"message": exc.message,
"request_id": structlog.contextvars.get_contextvars().get("correlation_id")
}
}
)
@app.exception_handler(Exception)
async def generic_exception_handler(request: Request, exc: Exception):
"""Handle unexpected exceptions."""
logger.critical(
"unexpected_error",
error_type=type(exc).__name__,
error_message=str(exc),
path=request.url.path,
exc_info=True
)
# Não expor detalhes em produção
return JSONResponse(
status_code=500,
content={
"error": {
"code": "INTERNAL_SERVER_ERROR",
"message": "An unexpected error occurred",
"request_id": structlog.contextvars.get_contextvars().get("correlation_id")
}
}
)
Error Tracking (Sentry)
Setup:
import sentry_sdk
from sentry_sdk.integrations.fastapi import FastApiIntegration
sentry_sdk.init(
dsn=os.getenv("SENTRY_DSN"),
environment=os.getenv("ENVIRONMENT"), # staging, production
traces_sample_rate=0.1, # 10% of transactions
profiles_sample_rate=0.1,
integrations=[FastApiIntegration()],
before_send=sanitize_sentry_event, # Remove sensitive data
)
def sanitize_sentry_event(event, hint):
"""Remove sensitive data before sending to Sentry."""
if 'request' in event:
if 'headers' in event['request']:
event['request']['headers'].pop('Authorization', None)
return event
Manual error capturing:
try:
result = process_payment(order)
except PaymentError as e:
sentry_sdk.capture_exception(e)
sentry_sdk.set_context("payment", {
"order_id": order.id,
"amount": order.total,
"payment_method": order.payment_method
})
raise
Error Response Format (RFC 7807)
{
"type": "https://docs.seuapp.com/errors/validation-error",
"title": "Validation Error",
"status": 400,
"detail": "Invalid email format",
"instance": "/api/v1/users/123",
"errors": [
{
"field": "email",
"message": "Must be valid email format",
"code": "INVALID_EMAIL"
}
],
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
Alternativas Consideradas
Opção 1: Plain text logging
Prós: - Legível para humanos - Simples
Contras: - Difícil de query - Parsing manual - CloudWatch Insights não funciona
Por que não escolhemos: JSON é requirement para observability moderna.
Opção 2: Rollbar ao invés de Sentry
Prós: - Mais barato - Simples
Contras: - Menos features - Menos integrações - Sentry é padrão da indústria
Por que não escolhemos: Sentry tem melhor ROI.
Opção 3: Correlation IDs opcionais
Prós: - Menos overhead
Contras: - Debugging impossível para requests sem ID - Inconsistente
Por que não escolhemos: Correlation IDs são essenciais para debugging distribuído.
Análise de Impacto
Impacto Técnico
- Structured logging obrigatório
- Correlation IDs em todos requests
- Sentry para error tracking
- Error responses padronizados
Impacto em Negócio
- ✅ MTTR reduzido em 60%
- ✅ Errors agregados e priorizados
- ✅ Debugging muito mais rápido
- ⚠️ Custo Sentry: ~$26/mês (10k events)
Riscos
Risco: Overhead de performance por logging
Mitigação: Logging assíncrono, batching, sampling.
Plano de Implementação
Fase 1: Structured Logging (Semana 1)
- Configurar structlog
- Migrar logs principais
- Testar em staging
Fase 2: Correlation IDs (Semana 2)
- Implementar middleware
- Propagar para downstream
- Documentar
Fase 3: Error Patterns (Semana 3)
- Custom exceptions
- Exception handlers
- Error responses padronizados
Fase 4: Sentry (Semana 4)
- Setup Sentry
- Integrar com código
- Training do time
Métricas de Sucesso
Após 1 mês: - ✅ 100% logs em JSON - ✅ Correlation IDs em todos requests - ✅ Sentry configurado
Após 3 meses: - ✅ MTTR reduzido em 60% - ✅ Errors agregados e triaged - ✅ Zero logs com dados sensíveis