Fazer deploy ↗
docs

Traces

Envie traces OpenTelemetry da sua aplicação para a Veloz e veja a árvore de spans.

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 list antes 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-proto envia protobuf e @opentelemetry/exporter-trace-otlp-http envia JSON. Os dois funcionam. O nome engana: ambos falam HTTP, e a única diferença é o formato do corpo da requisição.

Recomendamos o -proto porque o payload é menor, o que pesa em serviços com volume alto de spans. Mas o -http nã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 --require em CommonJS. Um import "./instrumentation.js" no topo do seu index.js nã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.py

O opentelemetry-instrumentOTEL_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/otlptracehttp
package 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. otlptracegrpc não conecta ao endpoint da Veloz e falha em silêncio, mesmo com OTEL_EXPORTER_OTLP_PROTOCOL definido como http/protobuf. Use sempre otlptracehttp.

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: sampleRate igual a 0 desliga 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:

  1. 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.
  2. 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.
  3. 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_id ganha 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:

  1. A instrumentação não é carregada antes da aplicação. Em Node, use node --import ./instrumentation.mjs. Em Python, use opentelemetry-instrument como prefixo do comando de start.
  2. sampleRate igual a 0. Nada é enviado com zero.
  3. Exporter gRPC. Veja Protocolo.
  4. Uma variável OTEL_* definida à mão. O seu valor prevalece sobre o da Veloz. Confira com veloz env list.
  5. 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