O OpenTelemetry fornece bibliotecas de instrumentação para várias bibliotecas, geralmente feitas por meio de hooks de biblioteca ou monkey-patching do código da biblioteca.
A instrumentação nativa de bibliotecas com OpenTelemetry oferece melhor observabilidade e experiência para desenvolvedores, eliminando a necessidade das bibliotecas exporem e documentarem hooks:
Confira as convenções semânticas disponíveis, que abrangem frameworks web, clientes RPC, bancos de dados, clientes de mensagens, componentes de infraestrutura e muito mais!
Se a sua biblioteca se enquadra em alguma dessas categorias, siga as convenções. Elas são a principal fonte de verdade e indicam quais informações devem ser incluídas nos trechos. As convenções tornam a instrumentação consistente: usuários que trabalham com telemetria não precisam aprender as especificidades de cada biblioteca, e fornecedores de observabilidade podem criar experiências para uma ampla variedade de tecnologias (por exemplo, bancos de dados ou sistemas de mensagens). Quando as bibliotecas seguem as convenções, muitos cenários podem ser habilitados automaticamente, sem necessidade de intervenção ou configuração por parte do usuário.
As convenções semânticas estão em constante evolução, e novas são adicionadas regularmente. Se ainda não existirem convenções para a sua biblioteca, considere adicioná-las. Preste atenção especial aos nomes dos trechos; procure usar nomes significativos e considere a cardinalidade ao defini-los.
Há um atributo schema_url
que pode ser
usado para registrar a versão das convenções semânticas em uso. Sempre que
possível, configure esse atributo.
Se tiver algum feedback ou quiser adicionar uma nova convenção, participe e contribua! O Instrumentation Slack ou o repositório de Specification são ótimos pontos de partida!
Pense na sua biblioteca do ponto de vista de um usuário e no que ele poderia querer saber sobre o comportamento e a atividade da biblioteca. Como mantenedor da biblioteca, você conhece os detalhes internos, mas o usuário provavelmente estará mais interessado na funcionalidade da aplicação do que no funcionamento interno da biblioteca. Considere quais informações podem ser úteis para analisar o uso da sua biblioteca e pense em uma maneira apropriada de modelar esses dados. Algumas considerações incluem:
Por exemplo, se sua biblioteca está fazendo requisições a um banco de dados, crie trechos apenas para a requisição lógica ao banco de dados. As requisições físicas pela rede devem ser instrumentadas nas bibliotecas que implementam essa funcionalidade. Além disso, é preferível capturar outras atividades, como a serialização de objetos/dados como eventos em trechos, ao invés de trechos adicionais.
Siga as convenções semânticas ao definir atributos dos trechos.
Algumas bibliotecas atuam como camadas finas que encapsulam chamadas de rede. Há uma grande chance de que o OpenTelemetry já tenha uma biblioteca de instrumentação para o cliente RPC subjacente (confira o registry). Nesse caso, pode não ser necessário instrumentar a biblioteca que encapsula essas chamadas. Como diretriz geral, só instrumente sua biblioteca em seu próprio nível.
Não instrumente se:
Se estiver em dúvida - não instrumente - você sempre pode fazê-lo mais tarde, quando perceber a necessidade.
Se optar por não instrumentar, ainda pode ser útil fornecer uma maneira de configurar handlers do OpenTelemetry para a instância interna do cliente RPC. Isso é essencial em linguagens que não suportam instrumentação totalmente automática e ainda é útil em outras.
O restante deste documento fornece orientações sobre o que e como instrumentar, caso decida fazê-lo.
O primeiro passo é adicionar a dependência do pacote OpenTelemetry API.
O OpenTelemetry possui dois módulos principais - API e SDK. A API do OpenTelemetry é um conjunto de abstrações e implementações não operacionais. A menos que sua aplicação importe o SDK do OpenTelemetry, sua instrumentação não faz nada e não impacta o desempenho da aplicação.
Bibliotecas devem usar apenas a API do OpenTelemetry.
Você pode estar com receio de adicionar novas dependências, então aqui estão algumas considerações para ajudar a minimizar problemas com dependências:
Toda a configuração da aplicação é ocultada da sua biblioteca por meio da API de
Rastreamento. As bibliotecas podem permitir que as aplicações passem instâncias
de TracerProvider
para facilitar testes e injeção de dependências, ou podem
obtê-las a partir do
TracerProvider global. As
implementações do OpenTelemetry em diferentes linguagens podem ter preferências
distintas para passar instâncias ou acessar o global, dependendo do que é mais
comum na linguagem.
Ao obter o rastreador, forneça o nome e a versão da sua biblioteca (ou do pacote de rastreamento) - essas informações aparecerão na telemetria e ajudarão os usuários a processar e filtrar a telemetria, além de entender sua origem e depurar/relatar quaisquer problemas de instrumentação.
APIs públicas são bons candidatos para rastreamento: trechos criados para chamadas de APIs públicas permitem que os usuários mapeiem a telemetria para o código da aplicação, entendam a duração e o resultado das chamadas da biblioteca. Quais chamadas devem ser rastreadas:
Exemplo de instrumentação:
private static Tracer tracer = getTracer(TracerProvider.noop());
public static void setTracerProvider(TracerProvider tracerProvider) {
tracer = getTracer(tracerProvider);
}
private static Tracer getTracer(TracerProvider tracerProvider) {
return tracerProvider.getTracer("demo-db-client", "0.1.0-beta1");
}
private Response selectWithTracing(Query query) {
// consulte as convenções para obter orientações sobre nomes de trechos e atributos
Span span = tracer.spanBuilder(String.format("SELECT %s.%s", dbName, collectionName))
.setSpanKind(SpanKind.CLIENT)
.setAttribute("db.name", dbName)
...
.startSpan();
// torna o trecho ativo e permite correlacionar logs e trechos aninhados
try (Scope unused = span.makeCurrent()) {
Response response = query.runWithRetries();
if (response.isSuccessful()) {
span.setStatus(StatusCode.OK);
}
if (span.isRecording()) {
// preencha atributos de resposta para códigos de resposta e outras informações
}
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, e.getClass().getSimpleName());
throw e;
} finally {
span.end();
}
}
Siga as convenções para preencher atributos! Se nenhuma delas se aplicar, consulte as convenções gerais.
Chamadas de rede são geralmente rastreadas com auto-instrumentações do OpenTelemetry através da implementação correspondente do cliente.
Se o OpenTelemetry não suportar o rastreamento do seu cliente de rede, use seu melhor julgamento. Aqui estão algumas considerações para ajudar:
Se o OpenTelemetry já suportar o rastreamento de suas chamadas de rede, você provavelmente não quer duplicá-lo. Pode haver algumas exceções:
AVISO: Solução genérica para evitar duplicação está em construção 🚧.
Rastros são um tipo de sinal que seus aplicativos podem emitir. Eventos (ou logs) e traces se complementam, não se duplicam. Sempre que você tiver algo que deva ter uma verbosidade, logs são uma escolha melhor do que traces.
É provável que seu aplicativo já use log ou algum módulo semelhante. Seu módulo pode já ter integração com o OpenTelemetry – para descobrir, veja o registry. As integrações geralmente adicionam o contexto de rastros ativo em todos os logs, para que os usuários possam correlacioná-los.
Se sua linguagem e ecossistema não tiverem suporte comum para logs, use span events para compartilhar detalhes adicionais do aplicativo. Eventos podem ser mais convenientes se você quiser adicionar atributos também.
Como regra geral, use eventos ou logs para dados verbosos em vez de rastros. Sempre anexe eventos à instância do trecho que sua instrumentação criou. Evite usar o trecho ativo se puder, pois você não controla a que ele se refere.
Se você trabalha em uma biblioteca ou serviço que recebe chamadas upstream,
como um framework web ou um consumidor de mensagens, você deve extrair o
contexto da requisição/mensagem recebida. O OpenTelemetry fornece a API
Propagator
, que oculta padrões específicos de propagação e lê o Context
de
rastreamento do cabeçalho. No caso de uma única resposta, há apenas um contexto
no cabeçalho, que se torna o pai dos novos trechos criado pela biblioteca.
Após criar um trecho, você deve passar o novo contexto de rastreamento para o código da aplicação (callback ou handler), tornando o rastro ativo; se possível, você deve fazer isso explicitamente.
// extrair o contexto
Context extractedContext = propagator.extract(Context.current(), httpExchange, getter);
Span span = tracer.spanBuilder("receive")
.setSpanKind(SpanKind.SERVER)
.setParent(extractedContext)
.startSpan();
// tornar o trecho ativo para que qualquer telemetria aninhada seja correlacionada
try (Scope unused = span.makeCurrent()) {
userCode();
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR);
throw e;
} finally {
span.end();
}
Aqui estão os exemplos completos de extração de contexto em Java, consulte a documentação do OpenTelemetry no seu idioma.
No caso de um sistema de mensagens, você pode receber mais de uma mensagem de uma vez. As mensagens recebidas se tornam links no trecho que você cria. Consulte as convenções de mensagens para mais detalhes (AVISO: as convenções de mensagens estão em construção 🚧).
Quando você faz uma chamada de saída, geralmente vai querer propagar o contexto
para o serviço downstream. Nesse caso, você deve criar um novo trecho para
rastrear a chamada de saída e usar a API Propagator
para injetar o contexto na
mensagem. Podem haver outros casos em que você queira injetar o contexto, por
exemplo, ao criar mensagens para processamento assíncrono.
Span span = tracer.spanBuilder("send")
.setSpanKind(SpanKind.CLIENT)
.startSpan();
// tornar o trecho ativo para que qualquer telemetria aninhada seja correlacionada
// até mesmo chamadas de rede podem ter camadas aninhadas de trechos, logs ou eventos
try (Scope unused = span.makeCurrent()) {
// injetar o contexto
propagator.inject(Context.current(), transportLayer, setter);
send();
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR);
throw e;
} finally {
span.end();
}
Aqui está o exemplo completo de injeção de contexto em Java.
Podem haver algumas exceções:
Por favor, adicione sua biblioteca de instrumentação ao registro do OpenTelemetry, para que os usuários possam encontrá-la.
A API do OpenTelemetry não executa operações quando não há SDK configurado na aplicação. Quando o SDK do OpenTelemetry é configurado, ele consome recursos limitados.
Aplicações da vida real, especialmente em grande escala, frequentemente têm amostragem baseada em cabeçalho configurada. Techos não amostrados são baratos e você pode verificar se o trecho está gravando, para evitar alocações extras e cálculos potencialmente caros, enquanto preenche atributos.
// alguns atributos são importantes para a amostragem e devem ser fornecidos no momento da criação
Span span = tracer.spanBuilder(String.format("SELECT %s.%s", dbName, collectionName))
.setSpanKind(SpanKind.CLIENT)
.setAttribute("db.name", dbName)
...
.startSpan();
// outros atributos, especialmente aqueles caros de calcular
// devem ser adicionados se o trecho estiver gravando
if (span.isRecording()) {
span.setAttribute("db.statement", sanitize(query.statement()))
}
A API do OpenTelemetry é tolerante em tempo de execução – não falha em argumentos inválidos, nunca lança exceções, e as elimina. Dessa forma, problemas de instrumentação não afetam a lógica da aplicação. Teste a instrumentação para identificar problemas que o OpenTelemetry pode esconder em tempo de execução.
Como o OpenTelemetry oferece uma variedade de auto-instrumentações, é útil verificar como a sua instrumentação interage com outras telemetrias: solicitações de entrada, solicitações de saída, logs, etc. Use uma aplicação típica, com frameworks e bibliotecas populares e com todo o rastreamento ativado ao testar sua instrumentação. Verifique como bibliotecas semelhantes à sua são exibidas.
Para testes unitários, você geralmente pode simular ou criar versões fictícias
de SpanProcessor
e SpanExporter
.
@Test
public void checkInstrumentation() {
SpanExporter exporter = new TestExporter();
Tracer tracer = OpenTelemetrySdk.builder()
.setTracerProvider(SdkTracerProvider.builder()
.addSpanProcessor(SimpleSpanProcessor.create(exporter)).build()).build()
.getTracer("test");
// executa teste ...
validateSpans(exporter.exportedSpans);
}
class TestExporter implements SpanExporter {
public final List<SpanData> exportedSpans = Collections.synchronizedList(new ArrayList<>());
@Override
public CompletableResultCode export(Collection<SpanData> spans) {
exportedSpans.addAll(spans);
return CompletableResultCode.ofSuccess();
}
...
}
Was this page helpful?
Thank you. Your feedback is appreciated!
Please let us know how we can improve this page. Your feedback is appreciated!