# Plano 06 — Observabilidade desde o Dia Zero

> **Milestone:** M1 · **Depende de:** 03 (paralelo a 04/05) · **Habilita:** 07–15
> **Objetivo:** todo log ser JSON estruturado, todo request rastreável e todo erro em
> RFC 7807 — **nativo (Monolog + `X-Request-ID`)**. OpenTelemetry é **trilha opcional**
> (perfil `telemetry`), atrás de interface fina e fail-open.

---

## 1. Princípios

- **Log como contrato.** Campos obrigatórios: `ts`, `level`, `request_id`,
  `trace_id`, `module`, `action`, `user`, `duration_ms`, `message`, `context`.
- **Nunca texto livre.** Use o `Log` nativo com **contexto estruturado**
  (`Log::info('noticia.publicada', [...])`); um helper fino (`log_event(...)`) garante os campos
  obrigatórios. **Sem fachada paralela** ao `Log`.
- **RFC 7807** em toda resposta de erro (HTML amigável + JSON para APIs).
- **Obrigatório é nativo.** JSON + `X-Request-ID` + RFC 7807 não dependem de nada externo.
- **Fail-open (OTel opcional).** Se o coletor OTel estiver fora/lento, a request segue
  (circuit breaker > 10ms); sem OTel, tudo continua funcionando.
- **LGPD:** PII mascarada antes de sair do processo (redaction no handler).

## 2. Arquitetura interna

```
src/core/Internal/Observability/
├── log_event.php              # helper fino sobre o `Log` nativo (campos obrigatórios)
├── JsonFormatter.php          # formatter com campos obrigatórios
├── RequestIdMiddleware.php    # gera/propaga X-Request-ID (UUIDv7)
├── TraceContext.php           # W3C traceparent ↔ OTel
├── pii_redact.php             # cpf/email/telefone/token → máscara (função)
├── PiiRedactor.php            # implementação da máscara
├── NullTelemetry.php          # no-op (padrão, zero custo)
├── OtlpTelemetry.php          # span OTLP/HTTP JSON + fail-open + circuit breaker (APCu)
├── OtlpHttpSender.php         # POST nativo (ext-curl) para <endpoint>/v1/traces
└── Test/
```

- `X-Request-ID` sempre retornado ao cliente e impresso no HTML de erro (transparência).
- Spans manuais nas Actions relevantes; auto-instrumentação do HTTP quando viável.
- Métricas base: latência p95, taxa de erro, filas, cache hit, jobs.

## 3. Debug local

- Perfil `debug` do compose: Mailpit (e-mail) + Dozzle (logs por container).
- `make logs` formata o JSON (jq) para leitura humana.
- Perfil `telemetry` opcional (export OTLP externo ou stack local leve).

## 4. Atividades

- [x] Canal de log `stack` (`json` em stderr + `daily` local) em `config/logging.php`, ambos
      com `JsonFormatter`; arquivo local com **rotação diária e retenção de 730 dias** (`LOG_DAILY_DAYS`).
- [x] Implementar `RequestIdMiddleware` (UUIDv7) e `TraceContext`.
- [x] Implementar `log_event()` (função fina sobre o `Log`, com testes).
- [x] Implementar `pii_redact()` (função, com testes) — `PiiRedactor` redige log e erros (T2).
- [x] Handler global de exceções → RFC 7807 (`type`, `title`, `status`, `detail`,
      `instance`, `trace_id`) e renderer HTML acessível com `X-Request-ID` (T2).
- [x] (Opcional, perfil `telemetry`) telemetria OTLP atrás de `Core\Contracts\Telemetry`,
      **nativa** (OTLP/HTTP JSON via `ext-curl`, sem SDK pesado) e **fail-open** com circuit
      breaker compartilhado (APCu); o núcleo funciona sem ela — T3.
- [x] Middleware de latência: emite log `duration_ms` por request (amostragem configurável
      via `LOG_REQUESTS`/`observability.slow_request_ms`).
- [ ] Rate limit/anti-abuso instrumentado: log `action=rate_limit` (motivo, chave) + métrica de `429` (ADR-021).
- [x] Documentar os campos obrigatórios em `wiki/specs/06-observabilidade.md`.
- [x] Correlacionar log↔trace: `trace_id` (W3C `traceparent` ou gerado) no JSON do log e no RFC 7807.

## 5. Testes e evidências

- [x] Teste: um request gera exatamente um log JSON com todos os campos obrigatórios (`JsonFormatterTest` + evento `http.request`).
- [x] Teste: `X-Request-ID` é ecoado e único por request.
- [ ] Teste: bloqueio por rate limit gera log estruturado com motivo e `X-Request-ID` (ADR-021).
- [x] Teste: erro de negócio vira RFC 7807 com `trace_id` (`ProblemDetailsTest`; HTML com código de rastreio).
- [x] Teste: `pii_redact()` mascara CPF/CNPJ/e-mail/telefone/segredos (`PiiRedactTest`; redação no formatter e no erro).
- [x] Teste: com o exportador OTLP apontando para porta morta (quando habilitado), a request
      continua 200 e o overhead fica limitado (`TelemetryFailOpenTest`; breaker APCu).

## 6. Definition of Done

- [x] Log, trace e erro padronizados e testados.
- [x] (Se OTel habilitado) um trace visível ponta a ponta (Jaeger OTLP/HTTP);
      sem OTel, JSON + `request_id`/`trace_id` cobrem.
- [x] Telemetria comprovadamente fail-open (breaker APCu compartilhado entre workers).

## 7. Riscos

| Risco | Mitigação |
|---|---|
| Overhead de telemetria | Desligada por padrão; timeout curto + circuit breaker APCu (fail-open) |
| Vazamento de PII em log | Redaction por padrão no formatter; teste obrigatório |
| Dependência de OTel no core | `Core\Contracts\Telemetry`; `NullTelemetry` padrão; OTLP isolado/opcional |
