01O que é monitorado
- Carga completa da página (o span
documentLoad). - Web Vitals: LCP (maior conteúdo visível), INP (resposta ao toque) e CLS (página pulando), em p75, por página, por versão e por navegador.
- Erros de JavaScript e promessas rejeitadas, agrupados como os erros do backend, com a pilha resolvida pelo source map.
- Não é monitorado: cliques, digitação, gravação de sessão, conteúdo da página, endereço IP do visitante.
02Como funciona
- O token do navegador (
evn_prd_...) é público por natureza: vai no código da página. Por isso ele só vale:- vindo de uma origem permitida (lista do serviço, até 20, sem caminho, por exemplo
https://portal.exemplo.com.br); - para traces e logs (nunca métricas, nunca source map);
- com pedido até 1 MB (4 MB descomprimido);
- com taxa menor: 10 pedidos por segundo por token (rajada de 50) e 1 por segundo por visitante (rajada de 20).
- vindo de uma origem permitida (lista do serviço, até 20, sem caminho, por exemplo
- O navegador manda OTLP por HTTP em JSON. O evobits responde ao CORS só para as origens da lista; de outra origem o navegador nem envia.
- Token de backend (
eva_) vindo de um navegador é recusado: segredo de servidor não vai para página. - Página é a rota, não a URL: o evobits usa
url.templatequando o SDK manda, senão o caminho da URL com os trechos que parecem id trocados por:id(/pedidos/123vira/pedidos/:id); busca e âncora saem. Roteador por âncora (/#/pedidos/12) também vira rota. - A instância é o navegador ("Chrome Android", "Safari iOS"), lido do user agent.
- A máscara roda no evobits antes de gravar (não há pacote no navegador). A amostragem e a cota são as mesmas dos outros serviços.
03Onde aparece
Serviço > Páginas: cartões de LCP, INP e CLS com as faixas do Google (bom até 2,5 s, 200 ms e 0,1; ruim acima de 4 s, 500 ms e 0,25), carga completa com o número de visitas e a taxa de erro de JavaScript, comparação entre as duas últimas versões, tabela por página e por navegador. Serviço > Erros e Aplicações > Erros. O serviço navegador não tem Rotas, Dependências nem Instâncias; tem Visão geral, Páginas, Erros e Configuração. No Dashboard, o widget "Web Vitals".
04Alertas
"Erro novo em produção" e "Regressão de erro" valem para erros da frente (origem navegador). Sensor de contagem de log pelo Explorador de logs (por exemplo, erros de JavaScript de uma página).
05Limites e privacidade
- O que um token vazado permite: mandar logs e traces falsos para este serviço, gastando a cota da empresa, dentro dos tetos de taxa acima. Ele não lê nada, não manda source map e não alcança outro serviço. Se isso acontecer, gere outro token na Configuração e revogue o antigo (vale na hora).
- O evobits nunca grava o IP do visitante (ele só existe em memória para a taxa).
- Até 200 combinações de página e versão por serviço a cada 10 s; acima disso o resto vira "(outras)".
- Nome de pessoa e endereço na URL não estão na máscara fixa: crie uma regra extra.
06Como instalar
1. Crie o serviço em Aplicações > Serviços > Novo serviço, tipo Navegador, com as origens onde a página abre (uma por linha). Copie o token evn_....
2. Instale os pacotes no projeto da página:
npm install @opentelemetry/api @opentelemetry/sdk-trace @opentelemetry/sdk-logs \
@opentelemetry/exporter-trace-otlp-http @opentelemetry/exporter-logs-otlp-http \
@opentelemetry/instrumentation @opentelemetry/instrumentation-document-load \
@opentelemetry/resources web-vitals
3. Ponha o token e a versão nas variáveis do build, não no código: o token é público na página publicada, mas não precisa morar no git. No Vite:
VITE_EVOBITS_TOKEN=evn_prd_... # no ambiente do build (CI ou .env.local, fora do git)
VITE_VERSAO=1.4.2 # a versão do deploy; a mesma do envio do source map
**4. Crie src/telemetria.js:**
import { logs, trace, SeverityNumber } from '@opentelemetry/api';
import { TracerProvider, BatchSpanProcessor } from '@opentelemetry/sdk-trace';
import { LoggerProvider, BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load';
import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';
import { onCLS, onINP, onLCP } from 'web-vitals';
const endereco = 'https://otlp.evobits.com.br';
const headers = { authorization: `Bearer ${import.meta.env.VITE_EVOBITS_TOKEN}` };
const resource = defaultResource().merge(
resourceFromAttributes({
'service.name': 'meu-servico',
'service.version': import.meta.env.VITE_VERSAO ?? '0.0.0',
'user_agent.original': navigator.userAgent,
}),
);
const traces = new TracerProvider({
resource,
spanProcessors: [new BatchSpanProcessor({ exporter: new OTLPTraceExporter({ url: `${endereco}/v1/traces`, headers }) })],
});
trace.setGlobalTracerProvider(traces);
const registros = new LoggerProvider({
resource,
processors: [new BatchLogRecordProcessor({ exporter: new OTLPLogExporter({ url: `${endereco}/v1/logs`, headers }) })],
});
logs.setGlobalLoggerProvider(registros);
registerInstrumentations({ instrumentations: [new DocumentLoadInstrumentation()] });
const log = logs.getLogger('evobits');
const vital = (m) =>
log.emit({
eventName: 'browser.web_vital',
attributes: { 'browser.web_vital.name': m.name, 'browser.web_vital.value': m.value, 'url.full': location.href },
});
onLCP(vital);
onINP(vital);
onCLS(vital);
const erro = (e) =>
log.emit({
severityNumber: SeverityNumber.ERROR,
body: String(e?.message ?? e),
attributes: {
'exception.type': e?.name ?? 'Error',
'exception.message': String(e?.message ?? e),
'exception.stacktrace': e?.stack ?? '',
'url.full': location.href,
},
});
addEventListener('error', (ev) => erro(ev.error ?? ev.message));
addEventListener('unhandledrejection', (ev) => erro(ev.reason));
addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
traces.forceFlush();
registros.forceFlush();
}
});
Fora do Vite, troque import.meta.env.VITE_... pelo mecanismo do seu empacotador (process.env.NEXT_PUBLIC_... no Next.js, define no esbuild, DefinePlugin no webpack). Se o roteador sabe o molde da rota (/pedidos/:id), mande-o como atributo url.template nos logs: a página fica exata.
5. Uma linha, antes de tudo: a primeira linha do arquivo de entrada da página (src/main.js):
import './telemetria.js';
6. Publique e abra a página numa origem permitida. 7. Mande os source maps de cada deploy (guia "Source maps"), com a chave da API, nunca com o token do navegador.
07Como conferir
- 1Abra a página numa origem da lista e navegue um pouco; troque de aba (o envio final sai quando a página fica oculta).
- 2O cartão Primeiro dado mostra "está mandando dados", com o navegador ("Chrome desktop") e a versão.
- 3Na aba Páginas, aparecem carga e Web Vitals da página (as medidas chegam quando a página fica oculta ou depois da primeira interação, no caso do INP).
- 4Force um erro numa página de teste: ele aparece em Erros com a pilha.
- 5No console do navegador, nenhuma linha "blocked by CORS policy" para
otlp.evobits.com.br.
08Problemas comuns
| Sintoma | O que fazer |
|---|---|
| Console: "Access to fetch at '.../v1/logs' ... has been blocked by CORS policy" | a origem da página não está na lista do serviço (confira esquema, www e porta) |
401 no /v1/traces | token do build vazio ou errado (VITE_EVOBITS_TOKEN não chegou ao build) |
| 403 "token de backend não é aceito vindo do navegador" | usou o token eva_ de um serviço backend; crie um serviço do tipo navegador |
| 429 | muitos envios de um visitante só; normal em teste de carga |
| Web Vitals não aparecem | as medidas só saem quando a página fica oculta; troque de aba ou feche |
Página aparece como /:id/... estranha | a URL tem trechos que parecem id; mande url.template |
| Erro sem arquivo e linha do fonte | falta o source map da versão (ou a versão do build difere da do envio) |
09Texto para IA
Para pedir a instalação a um assistente de código (Claude Code ou afins) aberto no projeto da página. Troque os marcadores entre < >; o token fica só na variável EVOBITS_TOKEN.
# Tarefa: medir carga, Web Vitals e erros de JavaScript da página <nome do serviço> no evobits
## Objetivo
Fazer a aplicação web deste projeto mandar ao evobits o tempo de carga, LCP, INP, CLS e os erros de JavaScript, pelo
SDK Web oficial do OpenTelemetry com a biblioteca web-vitals, sem gravar sessão nem cliques.
## Contexto
- Destino: https://otlp.evobits.com.br (caminhos /v1/traces e /v1/logs), OTLP por HTTP em JSON, cabeçalho
`authorization: Bearer <token do navegador>`.
- O token do navegador (`evn_...`) é público e só vale das origens cadastradas no evobits. Mesmo assim ele NÃO vai
escrito no código-fonte: entra pelo ambiente do build (no Vite, `import.meta.env.VITE_EVOBITS_TOKEN`), definido no
CI ou num `.env.local` fora do git. Para esta tarefa, trate-o como EVOBITS_TOKEN do build.
- A versão do deploy vai em `service.version` (no Vite, `import.meta.env.VITE_VERSAO`) e tem de ser a mesma usada
no envio do source map.
- Pacotes: @opentelemetry/api @opentelemetry/sdk-trace @opentelemetry/sdk-logs
@opentelemetry/exporter-trace-otlp-http @opentelemetry/exporter-logs-otlp-http @opentelemetry/instrumentation
@opentelemetry/instrumentation-document-load @opentelemetry/resources web-vitals.
- O arquivo de telemetria: TracerProvider com BatchSpanProcessor e OTLPTraceExporter ({url: endereço + '/v1/traces',
headers}); LoggerProvider com BatchLogRecordProcessor e OTLPLogExporter ({url: endereço + '/v1/logs', headers});
resource com `service.name` = '<nome do serviço>', `service.version` e `user_agent.original` = navigator.userAgent;
DocumentLoadInstrumentation registrada; onLCP, onINP e onCLS emitindo um log com `eventName: 'browser.web_vital'` e
atributos `browser.web_vital.name`, `browser.web_vital.value` e `url.full`; os eventos `error` e
`unhandledrejection` emitindo um log de nível ERROR com `exception.type`, `exception.message`,
`exception.stacktrace` e `url.full`; e `forceFlush` dos dois provedores quando `document.visibilityState` vira
`hidden`. Na versão 3 do SDK, o processador recebe `{ exporter }` (objeto), não o exportador direto.
## Passos
1. Descubra o empacotador (Vite, Next.js, webpack, esbuild), o arquivo de entrada da página e se há roteador.
2. Instale os pacotes da lista com o gerenciador do projeto.
3. Crie `src/telemetria.js` (ou `.ts`) como no contexto, com o endereço https://otlp.evobits.com.br, lendo token e versão das
variáveis de build do empacotador do projeto.
4. Importe `./telemetria.js` como a primeira linha do arquivo de entrada.
5. Se o roteador expõe o molde da rota (por exemplo `/pedidos/:id`), acrescente o atributo `url.template` nos logs.
6. Documente `VITE_EVOBITS_TOKEN=` e `VITE_VERSAO=` (ou os nomes do seu empacotador) SEM valor no `.env.example`.
7. Gere source maps no build (no Vite, `build.sourcemap: true`) e confira que os arquivos `.map` não são publicados
junto com o site, se o projeto não quiser expô-los.
## Regras
- Nunca escreva o token em arquivo versionado nem na resposta.
- Não grave cliques, digitação, conteúdo da página nem gravação de sessão.
- Não mude nada além do pedido; rode o build e os testes do projeto depois.
## Como saber que deu certo
1. O build passa e o bundle publicado contém o endereço https://otlp.evobits.com.br.
2. Aberta numa origem permitida, a página faz POST em /v1/logs e /v1/traces com resposta 200, sem erro de CORS no console.
3. No painel do evobits, o cartão "Primeiro dado" do serviço <nome do serviço> mostra o navegador e a versão, e a aba
Páginas mostra a carga e os Web Vitals da página.