O desenvolvimento não termina no deploy. Depois que a aplicação entra no ar, começa outra disciplina: medir o comportamento real, entender como o software é usado e transformar dado em decisão. Esta demonstração percorre a camada de observabilidade deste ecossistema — da telemetria de front-end deste site aos logs estruturados das APIs em produção.
Observabilidade em Produção — front-end · 3:33narrado
// o que esta demonstração mostra
✓A primeira semana do site em números: visitantes, sessões, origem, dispositivo e duração de visita.
✓Taxonomia de eventos tipados — nenhum evento genérico; cada interação vira informação com contexto.
✓Dado → informação: 44 falhas de vídeo no cliente que seriam invisíveis sem instrumentação.
✓Nas APIs: logs estruturados com correlação por requisição, trilha de auditoria imutável e cache-aside visível no log.
✓Diagnóstico real: um pico de 404 no primeiro dia revelando scanners em busca de arquivos de segredo.
Problema, arquitetura, decisões e o que foi medido — tudo rastreável às fontes listadas no fim. Sem números inventados.
01Problema
Antes da instrumentação, o site voava às cegas: nenhum analytics (zero visibilidade de visitantes, origem ou interesse), erros JavaScript só no ErrorBoundary visual — nenhum reportado para fora —, Web Vitals não medidos e nenhum log estruturado. Consequência prática: não saber qual projeto converte, erros silenciosos em produção e regressões de performance invisíveis.
02Contexto
Restrições assumidas de saída: site estático (sem backend próprio) na CDN da Vercel, custo zero, sem banner de cookies e sem dependência nova de runtime. O modelo mental que guia tudo: em site estático não existe servidor nem banco — todo dado que importa precisa sair da página no momento em que acontece. Por outro lado, o navegador não expõe o IP ao JavaScript e o Umami o descarta no coletor; até então nenhuma camada sob controle do projeto registrava o IP de uma requisição — e scanners que nunca executam JS eram invisíveis ao analytics.
03Arquitetura
Duas camadas separadas de propósito. No navegador, src/observability/: uma única fachada (telemetry.trackEvent / trackAttrs) sobre um catálogo tipado de eventos, fila em memória com flush em lote, providers intercambiáveis (Umami via Send API própria, sem script de terceiros; console em dev), Web Vitals nativos por PerformanceObserver e captura de erros. Na borda da Vercel, middleware.ts + api/session-link.ts + bibliotecas puras em src/server/: logs JSON http_request, security_event e session_link com IP da cadeia de confiança, requestId, geo e detecção de abuso. A ponte entre as duas é o sessionId anônimo por aba — o analytics nunca recebe IP; os logs nunca recebem dado de produto.
diagrama SVG inline — sem biblioteca, herda o tema da página
Fachada + catálogo
telemetry.ts e events.ts: um único ponto de emissão, tipado pelo catálogo (snake_case, verbo no passado); fila, dedupe (1,5 s), once-per-session, teto de 300 eventos e 10 erros por sessão, beacon na descarga da página.
Sinais nativos
performance.ts (LCP/CLS/INP/FCP/TTFB via PerformanceObserver, limiares do web.dev) e error-tracking.ts (window.error, unhandledrejection, falha de recurso, ErrorBoundary).
Providers
providers/umami-provider.ts (Send API + sendBeacon) e console-provider.ts; contrato TelemetryProvider que nunca lança exceção.
Correlação
session-link.ts: 1 POST por sessão com o sessionId (o mesmo que o Umami recebe) para /api/session-link, respeitando opt-out, DNT e kill switch.
Borda (Vercel Edge)
middleware.ts gera requestId, resolve IP (src/server/ip.ts), avalia sinais (src/server/security.ts) e emite registros (src/server/security-log.ts); api/session-link.ts grava o vínculo sessionId ⇄ IP.
04Decisões e trade-offs
decisão 01
Umami Cloud como única ferramenta, consumido pela Send API própria (fetch keepalive / sendBeacon) — 0 KB de script de terceiros.
em vez de
GA4 (~50 KB + cookies → exigiria banner), Vercel Analytics (eventos custom só no plano Pro), Speed Insights (redundante), Clarity (replay invasivo), PostHog (poder demais), Plausible (sem plano gratuito), OpenTelemetry (sem backend para receber), Sentry (adiado: erros viram eventos no Umami; reavaliar se o volume crescer).
trade-off
O endpoint do coletor ainda pode ser bloqueado por ad-blocker — perda aceitável e comum a qualquer analytics. Trocar de ferramenta = escrever um provider novo; nenhum componente muda.
evidência
docs/OBSERVABILIDADE.md §4 (tabela de ferramentas avaliadas)
decisão 02
Catálogo tipado de eventos com fachada única: telemetry.trackEvent(nome, payload) só aceita nomes do catálogo com o payload correspondente; cliques via trackAttrs e um listener delegado no document.
em vez de
Strings soltas de evento espalhadas pelos componentes.
trade-off
Todo evento novo exige entrada no catálogo (tipo + descrição + origem, validado por teste) — fricção deliberada que impede taxonomia genérica.
Boot da telemetria no idle do navegador (requestIdleCallback, timeout 2 s), fila com flush em lote (800 ms), flush imediato para erros/engajamento e sendBeacon em visibilitychange/pagehide.
trade-off
Um trackEvent antes do boot seria descartado — por isso initObservability(onReady) entrega um callback para eventos de carregamento (é como a DemoPage emite demo_page_viewed). Custo medido: +6,6 kB gzip no bundle.
IP registrado exclusivamente na borda (Edge Middleware + /api/session-link), nunca no frontend nem no analytics, com cadeia de confiança x-vercel-forwarded-for → x-real-ip → primeiro item de x-forwarded-for.
em vez de
Confiar em cf-connecting-ip/Forwarded (excluídos: sem Cloudflare/proxy próprio na frente seriam headers forjáveis); enviar IP ao Umami (rejeitado por design).
trade-off
Retenção curta dos Runtime Logs da Vercel — tratada como feature de minimização (LGPD: finalidade específica, campos enumerados). A janela deslizante de contagem vive na memória do isolate edge e zera em cold start; contagem global exata exigiria um KV — anotado como evolução, não pendência.
evidência
src/server/ip.ts · docs/SEGURANCA-LOGS.md §2 e §6
decisão 06
Bloqueio na borda só nos dois casos sem falso positivo plausível — probe de path sensível (/.env, /.git, wp-admin, dumps, traversal…) → 404 imediato; ≥ 180 req/10 s do mesmo IP → 429. Todo o resto (scanner UA, UA ausente, ≥ 60 req/10 s, ≥ 30 paths distintos/10 s) é log-only.
trade-off
Anti-ruído: 1 security_event por IP a cada 30 s e teto de 2 000 IPs rastreados por isolate. O middleware não enxerga o status de arquivos servidos pela CDN (roda antes da resposta); 404 de rota SPA continua sendo reportado pelo cliente (page_not_found_viewed).
Correlação Umami ⇄ logs pelo sessionId aleatório por aba: o frontend envia 1× por sessão apenas esse id; o servidor grava session_link (sessionId ⇄ IP).
trade-off
O pageview nativo do Umami não carrega sessionId — sessões que só geraram pageview não são correlacionáveis; sessões sem JS (scanners) não têm session_link, mas são exatamente as que o middleware enxerga por inteiro. Visitante com opt-out/DNT não envia o vínculo: coerência de consentimento vale mais que cobertura.
Fail-safe total: provider fora do ar, storage bloqueado ou API ausente são engolidos com try/catch; no middleware, qualquer exceção resulta em "deixar passar".
trade-off
Observabilidade jamais derruba o site — ao custo de falhas da própria telemetria serem silenciosas (logger em nível debug).
›Atribuição de origem das páginas de demonstração sem identificar ninguém: resume (sem referrer — cliques em PDF não enviam referrer e o currículo é quem publica as URLs), campaign (whitelist utm_*/ref), internal/external.
›Sanitização: strings truncadas (200–300 ch), stacks a 2 000 ch, URLs de recurso sem query string, qualquer parâmetro de query fora da whitelist descartado antes do envio.
›Do Not Track respeitado, opt-out persistido (telemetry.optOut()), kill switch por variável de ambiente; sem Website ID nada é enviado (padrão seguro).
›Segurança de borda: headers nosniff, X-Frame-Options, Referrer-Policy, Permissions-Policy, HSTS em tudo; CSP restritiva (script-src 'self'; connect-src 'self' + coletor) nas rotas da SPA.
›Registros de borda construídos por builders que só copiam campos enumerados — Authorization, Cookie, tokens, corpo e valores de query nunca entram nos logs (apenas as chaves).
›Matcher do middleware exclui assets/, videos-v4/ e favicon* para não gastar invocação de edge com tráfego sem valor investigativo.
06Testes
Suíte Vitest (jsdom) que cobre a camada de telemetria e as bibliotecas puras da borda; tsc -b garante a tipagem forte do catálogo inteiro.
›Catálogo/convenção de nomes, enriquecimento de contexto, dedupe, once-per-session, teto de erros, provider quebrado (fail-safe), listener delegado, opt-out, serialização de erros, sanitização de URL, achatamento para o Umami e limiares de Web Vitals (src/observability/__tests__/).
›Resolução de IP pela cadeia de confiança, normalização IPv4/IPv6 (incl. IPv4 mapeado em IPv6) e detectores/limiares de segurança (src/server/__tests__/).
›Roteamento das páginas de demonstração com .html, barra final e forma limpa (src/__tests__/routing.test.ts).
07Observabilidade
Este é o case study da própria observabilidade — o que ela já entregou como informação, não só como dado.
›Primeira semana do site em números (visitantes, sessões, origem, dispositivo, duração) e funil do currículo: demo_page_viewed → project_video_started → progresso/quartis → project_video_completed → project_deploy_clicked/project_repository_clicked.
›44 falhas de vídeo no cliente que, sem instrumentação, seriam invisíveis (project_video_failed / resource_load_failed).
›Pico de 404 no primeiro dia revelando scanners em busca de arquivos de segredo — hoje respondido na borda com 404 e security_event.
›Fluxo de investigação: sessão suspeita no Umami → data.sessionId → session_link → IP → http_request/security_event por IP → requestId amarrando cada linha.
›Métricas de negócio definidas com regra de decisão (taxa de conversão em deploy, interesse por projeto, conclusão de vídeo, funil de contato, vitals por dispositivo).
08Performance
Números medidos — custo da própria telemetria e baseline de produção do site registrado em 2026-08-19 (antes das etapas de otimização do plano de auditoria).
›Telemetria: +6,6 kB gzip no bundle (70,8 → 77,4 kB), zero scripts de terceiros, zero requisições até o idle.
›Lighthouse 12 em produção (2026-08-19): Performance 95 (mobile) / 96 (desktop); Best Practices 100; SEO 100; CLS 0; TBT 21 ms (mobile); TTFB 18–22 ms.
09Resultados e aprendizados
✓Decisão de produto sustentada por dado: o site mede qual projeto converte, onde o visitante desiste e como o vídeo performa — e o currículo ganhou um funil mensurável.
✓Duas fronteiras honestas documentadas: analytics de produto (Umami, sem IP) e observabilidade de segurança (borda, com IP e finalidade específica) seguem separados de propósito — um mede interesse, o outro protege.
✓Scanners que nunca executam JavaScript saíram da invisibilidade: probes conhecidos são bloqueados na borda e registrados com IP confiável, requestId e geo.
✓Aprendizado: privacidade por construção (builders com campos enumerados, whitelist de query, sem identify) custa menos do que remediar depois.
10Fontes
Tudo o que está escrito acima é rastreável a estes arquivos e repositórios.
docs/OBSERVABILIDADE.md
docs/SEGURANCA-LOGS.md
docs/BASELINE.md
middleware.ts
api/session-link.ts
src/observability/*.ts
src/server/*.ts
src/data/projects.ts (descrições dos clipes site-obs / agenda-obs)