Documentação / Observabilidade - Pilot Status

Observabilidade - Pilot Status

Entrar

Observabilidade - Pilot Status

Stack LGTM (Self-Hosted)

O projeto utiliza a stack Grafana LGTM para observabilidade:

  • Loki - Agregação de logs
  • Grafana - Visualização e dashboards
  • Tempo - Distributed tracing
  • Mimir/Prometheus - Métricas
  • Alloy - Coleta e roteamento de sinais (OTLP receiver)

Subir a Stack

# Subir toda a stack de observabilidade
npm run obs:up

# Derrubar (remove volumes)
npm run obs:down

# Ver logs
npm run obs:logs

Acessos padrão:

  • Grafana: http://localhost:3001 (admin/admin)
  • Prometheus: http://localhost:9090
  • Tempo: http://localhost:3200
  • Loki: http://localhost:3100
  • Alloy UI: http://localhost:12345

Apontar o App para o Alloy Local

No .env.development ou variáveis de ambiente:

OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_TRACES_SAMPLER_ARG=1.0
OTEL_LOGS_ENABLED=true
OTEL_NODE_EXPERIMENTAL_SDK_METRICS=true

Arquitetura de Instrumentação

Pacote Compartilhado: packages/shared/src/observability/

observability/
  index.ts           # Re-exports
  init.ts            # initObservability() - NodeSDK bootstrap
  tracer.ts          # withSpan() helper para Use Cases
  metrics.ts         # Counters e Histograms de negócio
  logger.ts          # OTelLogger com correlação trace↔log
  bullmq-trace.ts    # injectTraceContext/extractTraceContext
  pii-span-processor.ts  # Redaction de PII em spans
  resource.ts        # resolveInstanceId, getResourceAttributes
  sampling.ts        # ParentBasedSampler config

Padrão de Instrumentação por Camada

| Camada | Instrumentação | |--------|---------------| | Controllers (route.ts) | Auto-instrumentation HTTP (via @opentelemetry/auto-instrumentations-node) | | Use Cases | withSpan("UseCase.XYZ", { attrs }, fn) manual | | Adapters Prisma | @prisma/instrumentation (auto) | | Adapters HTTP | Instrumentation undici/fetch (auto) | | BullMQ | injectTraceContext(jobData) no producer + runBullJobWithTrace(queueName, job, fn) no worker | | RabbitMQ | @opentelemetry/instrumentation-amqplib (auto) | | Redis/ioredis | Instrumentation ioredis (auto) |

Métricas de Negócio

Prefixo pilot_*:

  • pilot_messages_sent_total{instance, status}
  • pilot_webhook_events_total{source, channel, outcome}
  • pilot_ai_request_duration_seconds{provider, model}
  • pilot_use_case_duration_seconds{module, use_case}
  • pilot_job_duration_seconds{queue_name}

Spans de Negócio

Nomeados como UseCase.<Name>:

  • UseCase.SendMessage
  • UseCase.SyncStripePeriod
  • UseCase.RenewFreePlan
  • UseCase.ConnectInstance
  • UseCase.SyncInstanceStatus

Correlação Trace↔Log

O OTelLogger anexa automaticamente trace_id e span_id em cada log quando há um span ativo.

Variáveis de Ambiente

| Variável | Default | Descrição | |----------|---------|-----------| | OTEL_EXPORTER_OTLP_ENDPOINT | (vazio = desabilitado) | URL do OTLP receiver (Alloy) | | OTEL_EXPORTER_OTLP_PROTOCOL | http/protobuf | Protocolo de export | | OTEL_SERVICE_NAME | automático | pilot-fullstack ou pilot-worker | | OTEL_TRACES_SAMPLER | parentbased_traceidratio | always_on, always_off, parentbased_always_on, parentbased_traceidratio | | OTEL_TRACES_SAMPLER_ARG | 0.1 | Taxa de sampling raiz (0.0-1.0) quando sampler usa ratio | | OTEL_LOGS_ENABLED | false (dev compose) | Somente true ou 1 habilita LoggerProvider OTLP (além do JSON em stdout) | | OTEL_NODE_EXPERIMENTAL_SDK_METRICS | — | Defina true em produção para registrar o MeterProvider do NodeSDK (métricas OTLP) | | OTEL_SERVICE_INSTANCE_ID | HOSTNAME | ID único da instância | | APP_VERSION | dev | Versão do serviço |

Rodando Múltiplas Réplicas

Cada réplica recebe automaticamente service.instance.id (resolvido de OTEL_SERVICE_INSTANCE_ID > HOSTNAME > UUID).

Para distinguir réplicas no Grafana:

  • Logs: filtrar por {service_instance_id="xxx"}
  • Métricas: usar sum by (service_name, instance) para ver desbalanceamento
  • Traces: cada span carrega service.instance.id

Política de PII

O PiiRedactingSpanProcessor redactiona automaticamente atributos cujo nome contem palavras sensíveis: token, api_key, secret, password, authorization, cookie, session, phone, email, content, etc.

O OTelLogger aplica a mesma redaction nos campos de metada dos logs.

Dashboards Provisionados

  1. RED Overview (pilot-red-overview) - Request rate, duration p99, error rate por rota + métricas de mensagens, webhooks, AI, use cases
  2. Worker & BullMQ (pilot-worker-bullmq) - Job duration, messages sent por instância, traces recentes, logs do worker

Adicionar Instrumentação em Novos Use Cases

import { withSpan } from "@pilot-status/shared";

export class MyNewUseCase {
  async execute(input: MyInput): Promise<MyOutput> {
    return withSpan("UseCase.MyNew", {
      "my.input_id": input.id,
    }, async () => this._execute(input));
  }

  private async _execute(input: MyInput): Promise<MyOutput> {
    // ... lógica de negócio
  }
}

Propagar Trace Context em Jobs BullMQ

No producer (enfileirar job):

import { injectTraceContext } from "@pilot-status/shared";
const jobData = injectTraceContext({ myData: "value" });
await queue.add("job-name", jobData);

No consumer (handler), prefira o helper que restaura o contexto e abre o span BullMQ.<queue>.<jobName>:

import { runBullJobWithTrace } from "@pilot-status/shared";

await runBullJobWithTrace(MY_QUEUE_NAME, job, async () => {
  // processar job
});

Prometheus e remote write

O Alloy envia métricas OTLP convertidas via prometheus.remote_write para http://prometheus:9090/api/v1/write. O serviço Prometheus na stack local sobe com --web.enable-remote-write-receiver para aceitar esse endpoint (ver docker-compose.observability.yml).