Métricas dizem que a latência subiu. Traces dizem onde. Cada requisição vira uma árvore de spans com a duração de cada etapa, atravessando os serviços do projeto.
Como funciona
Sua aplicação envia os traces usando OpenTelemetry, o padrão aberto de instrumentação. A Veloz recebe, isola por organização e guarda por 7 dias.
Você não configura endpoint nem credencial. Ao declarar traces no veloz.json, a Veloz injeta no serviço todas as variáveis que o OpenTelemetry precisa. As bibliotecas oficiais leem essas variáveis sozinhas.
{
"services": {
"apps/api": {
"id": "svc_api456",
"name": "loja-api",
"type": "web",
"root": "apps/api",
"runtime": {
"command": "node dist/index.js",
"port": 3000
},
"observability": {
"traces": { "sampleRate": 1 }
}
}
}
}| Campo | Padrão | Descrição |
|---|---|---|
enabled |
true |
false desliga o envio sem apagar a configuração |
sampleRate |
1 |
Fração de traces mantidos, de 0 a 1 |
Para ligar com os padrões, "traces": {} já basta.
Variáveis definidas automaticamente
A cada deploy de um serviço que declara traces, a Veloz define:
| Variável | Valor |
|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
Endpoint de recebimento da sua organização |
OTEL_EXPORTER_OTLP_HEADERS |
Credencial de envio |
OTEL_EXPORTER_OTLP_PROTOCOL |
http/protobuf |
OTEL_SERVICE_NAME |
O nome do serviço no veloz.json |
OTEL_RESOURCE_ATTRIBUTES |
Projeto e serviço de origem |
OTEL_TRACES_SAMPLER |
parentbased_traceidratio |
OTEL_TRACES_SAMPLER_ARG |
O valor de sampleRate |
Atenção: se você definir qualquer uma dessas variáveis, o seu valor prevalece e a Veloz não sobrescreve. Isso permite apontar a aplicação para um coletor próprio, mas também é a causa mais comum de trace que "para de funcionar" sem erro nenhum. Confira suas variáveis com
veloz env listantes de investigar o código.
Protocolo
A Veloz recebe traces por OTLP sobre HTTP, tanto em protobuf quanto em JSON. Não há endpoint gRPC.
Isso importa porque vários SDKs do OpenTelemetry usam gRPC por padrão, e um exporter gRPC apontado para um endpoint HTTP não gera erro visível: a aplicação sobe normalmente, atende requisições e nenhum trace chega.
O que fazer em cada linguagem:
| Linguagem | Faça | Evite |
|---|---|---|
| Node/TS | @opentelemetry/exporter-trace-otlp-proto ou -http |
@opentelemetry/exporter-trace-otlp-grpc |
| Python | opentelemetry-instrument com as variáveis da Veloz |
Definir OTEL_EXPORTER_OTLP_PROTOCOL como grpc |
| Go | otlptrace/otlptracehttp |
otlptrace/otlptracegrpc |
| Java | O agente com as variáveis da Veloz | Definir OTEL_EXPORTER_OTLP_PROTOCOL como grpc |
Se você não definir OTEL_EXPORTER_OTLP_PROTOCOL, a Veloz já define http/protobuf e as bibliotecas que respeitam essa variável fazem a coisa certa sozinhas. Em Go, a variável não decide nada: quem decide é o pacote de exporter que você importa.
Node e TypeScript: aqui o pacote também decide o formato, e a variável não tem efeito.
@opentelemetry/exporter-trace-otlp-protoenvia protobuf e@opentelemetry/exporter-trace-otlp-httpenvia JSON. Os dois funcionam. O nome engana: ambos falam HTTP, e a única diferença é o formato do corpo da requisição.Recomendamos o
-protoporque o payload é menor, o que pesa em serviços com volume alto de spans. Mas o-httpnão é erro, e se você já usa um dos dois não precisa mudar nada. O que não funciona é o@opentelemetry/exporter-trace-otlp-grpc, porque não existe endpoint gRPC.
Falhas de envio de traces são silenciosas por padrão em qualquer linguagem. A aplicação sobe, atende requisições e descarta os spans em background, porque o SDK do OpenTelemetry só registra o erro quando o diagnóstico está ligado. Em Node e TypeScript, para ver o que está acontecendo:
import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ERROR);Com essa linha, erros de envio aparecem como OTLPExporterError nos logs do serviço. Sem ela, não aparece nada.
Node e TypeScript
npm install @opentelemetry/api \
@opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-proto// instrumentation.mjs
import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
// O exporter lê OTEL_EXPORTER_OTLP_ENDPOINT e OTEL_EXPORTER_OTLP_HEADERS
// do ambiente. A Veloz define essas variáveis automaticamente.
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter(),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();{
"scripts": {
"start": "node --import ./instrumentation.mjs dist/index.js"
}
}Importante: o arquivo de instrumentação precisa ser carregado antes da sua aplicação. Use
--import(Node 20.6 ou superior) ou--requireem CommonJS. Umimport "./instrumentation.js"no topo do seuindex.jsnão funciona: nesse ponto as bibliotecas que precisam ser instrumentadas já foram carregadas.
As auto-instrumentações cobrem HTTP, Express, Fastify, pg, mysql2, ioredis e outras. Para marcar um trecho do seu próprio código:
import { trace } from "@opentelemetry/api";
const tracer = trace.getTracer("loja-api");
await tracer.startActiveSpan("calcularFrete", async (span) => {
try {
return await calcularFrete(pedido);
} finally {
span.end();
}
});Python
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install# comando de start do serviço
opentelemetry-instrument python app.pyO opentelemetry-instrument lê OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS e OTEL_EXPORTER_OTLP_PROTOCOL do ambiente, definidas automaticamente pela Veloz. Ajuste o runtime.command do serviço no veloz.json para incluir o prefixo.
Go
go get go.opentelemetry.io/otel \
go.opentelemetry.io/otel/sdk \
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttppackage main
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func iniciarTracing(ctx context.Context) (func(context.Context) error, error) {
// Sem opções, o exporter lê OTEL_EXPORTER_OTLP_ENDPOINT e
// OTEL_EXPORTER_OTLP_HEADERS do ambiente.
exp, err := otlptracehttp.New(ctx)
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exp))
otel.SetTracerProvider(tp)
return tp.Shutdown, nil
}Chame iniciarTracing no início do main e adie o desligamento, para que os spans em memória sejam enviados antes da saída. O trecho abaixo também usa o pacote log, então adicione "log" ao bloco de imports:
func main() {
ctx := context.Background()
shutdown, err := iniciarTracing(ctx)
if err != nil {
log.Fatal(err)
}
defer shutdown(ctx)
// ...
}Atenção: em Go, o pacote importado decide o protocolo.
otlptracegrpcnão conecta ao endpoint da Veloz e falha em silêncio, mesmo comOTEL_EXPORTER_OTLP_PROTOCOLdefinido comohttp/protobuf. Use sempreotlptracehttp.
Amostragem
sampleRate controla a fração de traces mantidos. A Veloz traduz esse valor para o amostrador parentbased_traceidratio, que decide uma vez por trace: se um serviço decide manter, os serviços seguintes da mesma requisição mantêm também. A árvore nunca sai pela metade.
{
"observability": {
"traces": { "sampleRate": 0.25 }
}
}Como escolher:
| Volume do serviço | Valor sugerido |
|---|---|
| Baixo, ambiente de teste | 1 |
| Moderado | 0.25 |
| Alto, milhares de req/min | 0.05 |
Atenção:
sampleRateigual a0desliga o envio por completo. Nenhum trace chega enquanto o valor for zero. Para desligar de propósito, prefira"enabled": false, que deixa a intenção explícita no arquivo.
Onde ver os dados
No app da Veloz, cada serviço web ou worker tem a aba Traces. Ela abre em três camadas, da mais ampla para a mais específica, porque uma lista crua de traces é difícil de ler sem saber antes o que procurar:
- Resumo do período: total de requisições, quantas falharam, p50, p95, p99 e a mais lenta. Quando o período tem mais requisições do que a Veloz consegue varrer de uma vez, o resumo diz isso, para você não ler os números como se fossem a janela inteira.
- Operações ranqueadas: as rotas e operações que mais consomem tempo e que mais falham, ordenadas por impacto, não por nome. Clicar em uma operação filtra a lista abaixo.
- Traces individuais: a lista dos mais recentes. Clicar em uma linha abre a árvore de spans, com o nome do serviço em cada linha, inclusive quando a requisição atravessa mais de um serviço do projeto.
Na árvore de spans, a barra de cada span mostra duas coisas: a duração total, mais clara, e o tempo próprio dentro dela, sólido. Tempo próprio é o tempo que aquele span gastou por conta própria, sem contar o que ele passou esperando os filhos. É o número que responde "onde o tempo foi", porque a duração sozinha sempre culpa o span de cima. A cor identifica o tipo de operação: banco de dados, cache, chamada externa, modelo de IA, fila ou execução interna.
Acima da árvore, a Veloz lista o que encontrou de suspeito no trace em linguagem direta: o span que falhou de fato, o span que domina o tempo, chamadas repetidas em sequência (o caso clássico de uma consulta por item de uma lista) e tempo não explicado por nenhum filho. Nada disso é uma chamada a um modelo, é leitura direta dos spans.
Para levar o trace a um assistente de código, use Copiar para IA. Ele copia a árvore em texto, já com esse diagnóstico, pronta para colar. Pelo terminal e por agentes, o mesmo conteúdo sai em veloz traces show.
Do trace para os logs, e de volta
Se a sua aplicação escreve o identificador do trace no log estruturado, no campo trace_id, as duas telas passam a se conectar sozinhas:
- Cada linha de log com
trace_idganha um link para a requisição que a produziu. - Cada trace ganha o link Ver logs deste request, que abre a aba Logs já filtrada por aquele identificador.
Esse é o único trabalho de instrumentação necessário, e é o que transforma "algo falhou" em "esta linha de código falhou nesta requisição". Sem o campo, o link simplesmente não aparece.
Visão da organização
Fora do serviço, a entrada Observabilidade no menu lateral responde a pergunta anterior a todas: existe algo quebrado agora, e onde. Ela lista todos os serviços da organização ordenados por gravidade, com estado, volume de requisições, p95 e a operação mais crítica de cada um, e leva direto ao serviço responsável. Serviços sem instrumentação aparecem como tal, e não como saudáveis.
Pelo terminal:
# Traces recentes, somente com erro
veloz traces list --range 1h --errors
# Árvore de spans de um trace
veloz traces show 4bf92f3577b34da6
# Estado do envio
veloz obs status Traces (última 1h, somente erros)
HORÁRIO OPERAÇÃO DURAÇÃO SPANS ID
14:02:11 POST /api/checkout 1.84s 23 4bf92f3577b34da6
13:58:40 POST /api/checkout 2.10s 21 a3ce929d0e0e4736
13:51:07 GET /api/orders/:id 940ms 11 9c2b7f14dd0a41e8
3 trace(s) com erro de 41 no período.
Retenção
Traces ficam disponíveis por 7 dias. Os filtros de período no app da Veloz vão até 24 horas, porque uma busca de 7 dias é lenta e raramente útil. Para investigar algo além disso, prenda o identificador do trace nos logs, que têm retenção maior: escrever trace_id no log estruturado guarda o rastro da requisição depois que o trace já expirou.
Problemas comuns
Nada chega, a aplicação sobe normalmente
Em ordem de frequência:
- A instrumentação não é carregada antes da aplicação. Em Node, use
node --import ./instrumentation.mjs. Em Python, useopentelemetry-instrumentcomo prefixo do comando de start. sampleRateigual a0. Nada é enviado com zero.- Exporter gRPC. Veja Protocolo.
- Uma variável
OTEL_*definida à mão. O seu valor prevalece sobre o da Veloz. Confira comveloz env list. - O serviço não recebeu tráfego. Sem requisição não há trace.
veloz obs status identifica os casos 2 e 5 diretamente.
Os traces aparecem com o nome errado
OTEL_SERVICE_NAME é definido pela Veloz com o nome do serviço no veloz.json. Se você também define service.name no código, ao criar o recurso do SDK, o valor do código prevalece e o trace aparece com outro nome. Remova a definição no código e deixe o SDK ler a variável.
Um serviço aparece na árvore e o outro não
A propagação de contexto entre serviços depende de o cliente HTTP também estar instrumentado. As auto-instrumentações cobrem os clientes mais comuns. Se você usa um cliente próprio, ele precisa propagar o cabeçalho traceparent.
A aplicação perde os últimos spans ao reiniciar
Os spans ficam em um buffer antes do envio. Chame o desligamento do provedor ao receber o sinal de encerramento (tp.Shutdown em Go, sdk.shutdown() em Node) para esvaziar o buffer antes de sair.
Próximos passos
- Observabilidade: visão geral e diagnósticos
- Métricas customizadas: exponha as métricas da sua aplicação
- Dashboards: dashboards do Grafana versionados no repositório
- Logs: busca e streaming de logs