Skip to content

Resilience & Observability

Tratamento de erros, logs estruturados, alarmes e tracing.


Error Boundary

Arquivo: src/common/error-boundary.ts

Toda invocação do Lambda passa por withErrorBoundary:

typescript
export const handler = (event: APIGatewayProxyEventV2) =>
  withErrorBoundary(logger, () => route(event))

Tradução exceção → HTTP:

ExceçãoStatusBody
ValidationError400{ "error": <message> }
UnauthorizedError401{ "error": <message ou 'Unauthorized'> }
BotError(message, statusCode)statusCode{ "error": <message> }
Error genérico500{ "error": "Internal server error" } (sem leak)
Não-Error lançado (string, number)500{ "error": "Internal server error" }

Quando lançar cada exceção

  • ValidationError: payload externo malformado (body vazio, schema quebrado)
  • UnauthorizedError: reservado para Fase 4+
  • BotError: falhas em integrações controladas (ex.: ZapiClient lança 502)
  • Error cru, bugs ou estados inesperados; mascarado automaticamente

Erros da API NestJS → Mensagem ao Usuário

ContrasyncApiClient faz uma distinção importante:

HTTP status do upstreamExceção lançadaTratamento no BotRouterService
4xx (400, 401, 403, 404, 409)ValidationError com mensagem do upstreamreplyApiError envia a mensagem como resposta via Z-API e devolve 200
5xx, timeout, networkBotError 502 com mensagem genéricaBubble up para o boundary → 500 mascarado

Isso garante que erros de negócio (ex.: "Nenhum usuário encontrado para esse email") cheguem ao usuário final em linguagem clara, enquanto erros operacionais ficam invisíveis para ele e visíveis para a operação no CloudWatch.


Logger

Arquivo: src/common/logger.ts

Formato: JSON Lines via console.log/warn/error. CloudWatch indexa cada linha.

NívelQuando usarCanal
infoEventos esperados (dispatch, reply)console.log
warnPayloads ignorados, falhas previstas antes de throwconsole.warn
errorErros inesperados capturados pelo boundaryconsole.error

Regra: warn antes de throw

Todo throw deve ser precedido por logger.warn/error com contexto. A mensagem do log contém detalhes do upstream; a exceção que sobe tem mensagem genérica.


Loggers existentes e amostras de output

handler (src/handler.ts)

Erro inesperado pego pelo boundary:

json
{"level":"ERROR","logger":"handler","message":"Unhandled error","error":{"name":"SyntaxError","message":"Unexpected token 'o', \"not-json{{{\" is not valid JSON","stack":"..."}}

BotError traduzido pelo boundary:

json
{"level":"WARN","logger":"handler","message":"502 Falha ao comunicar com a API do Contrasync"}

InboundWebhookHandler (src/handlers/inbound-webhook.handler.ts)

json
{"level":"WARN","logger":"InboundWebhookHandler","message":"Webhook payload sem messageId ou phone  ignorado"}
{"level":"INFO","logger":"InboundWebhookHandler","message":"Mensagem do próprio número ignorada","messageId":"MSG-001"}
{"level":"INFO","logger":"InboundWebhookHandler","message":"Mensagem duplicada ignorada","messageId":"MSG-001"}

BotRouterService (src/services/bot-router.service.ts)

json
{"level":"INFO","logger":"BotRouterService","message":"Routing reply","phone":"5511999998888"}

ContrasyncApiClient (src/adapters/contrasync-api/contrasync-api.client.ts)

json
{"level":"WARN","logger":"ContrasyncApiClient","message":"Falha em issueOtp: Nenhum usuário encontrado para esse email.","status":404}
{"level":"WARN","logger":"ContrasyncApiClient","message":"Falha em verifyOtp: network down","status":null}

ZapiClient (src/adapters/zapi/zapi.client.ts)

json
{"level":"WARN","logger":"ZapiClient","message":"Falha ao enviar mensagem para 5511999998888: network down"}

Como ler logs em produção

bash
aws logs tail /aws/lambda/contrasync-whatsapp-bot-prod --follow --format short

Filtrar por número:

bash
aws logs tail /aws/lambda/contrasync-whatsapp-bot-prod --follow \
  --filter-pattern '{ $.phone = "5511999998888" }'

Filtrar por erro:

bash
aws logs tail /aws/lambda/contrasync-whatsapp-bot-prod --follow \
  --filter-pattern '{ $.level = "ERROR" }'

Filtrar por messageId (debug de duplicação):

bash
aws logs tail /aws/lambda/contrasync-whatsapp-bot-prod --follow \
  --filter-pattern '{ $.messageId = "MSG-001" }'

Tracing (X-Ray)

NodejsFunction configurada com tracing: Tracing.ACTIVE. CDK aplica as IAM policies automaticamente.

X-Ray gera segments para:

  • A invocação do Lambda
  • Cada chamada axios para Z-API e Contrasync API (precisa adicionar aws-xray-sdk no futuro para enriquecer, hoje está apenas no nível do Lambda)

Útil para correlacionar latência entre API Gateway, Lambda, Z-API e Contrasync API.


CloudWatch Alarms

Provisionados em infra/whatsapp-bot-stack.ts:

AlarmMétricaThresholdJanela
BotErrorsAlarmAWS/Lambda/Errors≥ 35 min
BotThrottlesAlarmAWS/Lambda/Throttles≥ 15 min

Ambos com treatMissingData: NOT_BREACHING para não disparar em janelas sem invocação.

Sem subscription SNS hoje. Os alarms aparecem no console do CloudWatch; integração com Slack/SNS é trabalho futuro.


Source Maps em Produção

sourceMap: true no esbuild + NODE_OPTIONS=--enable-source-maps em runtime → stack traces no CloudWatch apontam para arquivos .ts originais, não para o bundle minificado.


Documento atualizado em Maio 2026