Instrumentação

Instrumentação manual para OpenTelemetry Python

Instrumentação é o ato de adicionar código de observabilidade a uma aplicação por conta própria.

Se você estiver instrumentando uma aplicação, será necessário utilizar o SDK do OpenTelemetry para sua linguagem. Você irá utilizar o SDK para inicializar o OpenTelemetry e a API para instrumentar seu código. Isso passará a emitir dados de telemetria da sua aplicação e de qualquer biblioteca que você tenha instalado que também possua instrumentação.

Se você estiver instrumentando uma biblioteca, instale apenas o pacote da API do OpenTelemetry para sua linguagem. Sua biblioteca não emitirá telemetria por conta própria; ela só emitirá telemetria quando fizer parte de uma aplicação que utiliza o SDK do OpenTelemetry. Para mais informações sobre a instrumentação de bibliotecas, consulte a seção Bibliotecas.

Para mais informações sobre a API e o SDK do OpenTelemetry, consulte a especificação.

Configuração

Primeiro, certifique-se de ter os pacotes da API e SDK:

pip install opentelemetry-api
pip install opentelemetry-sdk

Rastros

Obter um Rastreador

Para começar a rastrear, você precisará inicializar um TracerProvider e opcionalmente defini-lo como o padrão global.

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
    BatchSpanProcessor,
    ConsoleSpanExporter,
)

provider = TracerProvider()
processor = BatchSpanProcessor(ConsoleSpanExporter())
provider.add_span_processor(processor)

# Define o provedor global padrão de rastreador
trace.set_tracer_provider(provider)

# Cria um rastreador a partir do provedor global de rastreador
tracer = trace.get_tracer("meu.rastreador.nome")

Criando Trechos

Para criar um trecho, normalmente você vai querer que seja iniciado como o trecho atual.

def fazer_trabalho():
    with tracer.start_as_current_span("nome-do-trecho") as span:
        # faça algum trabalho que 'span' irá rastrear
        print("fazendo algum trabalho...")
        # Quando o bloco 'with' sair do escopo, 'span' será fechado para você

Você também pode usar start_span para criar um trecho sem torná-lo o trecho atual. Isso geralmente é feito para rastrear operações concorrentes ou assíncronas.

Criando Trechos Aninhados

Se você tiver uma sub-operação distinta que gostaria de rastrear como parte de outra, você pode criar trechos para representar a relação:

def fazer_trabalho():
    with tracer.start_as_current_span("pai") as parent:
        # faça algum trabalho que 'pai' rastreia
        print("fazendo algum trabalho...")
        # Crie um trecho aninhado para rastrear o trabalho aninhado
        with tracer.start_as_current_span("filho") as child:
            # faça algum trabalho que 'filho' rastreia
            print("fazendo algum trabalho aninhado...")
            # o trecho aninhado é fechado quando sai do escopo

        # Este trecho também é fechado quando sai do escopo

Quando você visualizar trechos em uma ferramenta de visualização de rastros, filho será rastreado como um trecho aninhado sob pai.

Criando Trechos com Decoradores

É comum ter um único trecho rastreando a execução de uma função inteira. Nesse cenário, há um decorador que você pode usar para reduzir o código:

@tracer.start_as_current_span("fazer_trabalho")
def fazer_trabalho():
    print("fazendo algum trabalho...")

O uso do decorador é equivalente a criar o trecho dentro de fazer_trabalho() e finalizá-lo quando fazer_trabalho() for concluído.

Para usar o decorador, você deve ter uma instância de tracer disponível globalmente para a declaração da sua função.

Obter o Trecho Atual

Às vezes, é útil acessar o trecho atual em um ponto no tempo para que você possa enriquecê-lo com mais informações.

from opentelemetry import trace

current_span = trace.get_current_span()
# enriqueça 'current_span' com algumas informações

Adicionar Atributos em um Trecho

Os Atributos permitem que você anexe pares de chave/valor em um trecho para transportar mais informações sobre a operação que está sendo rastreada.

from opentelemetry import trace

current_span = trace.get_current_span()

current_span.set_attribute("operacao.valor", 1)
current_span.set_attribute("operacao.nome", "Dizendo olá!")
current_span.set_attribute("operacao.outras-coisas", [1, 2, 3])

Adicionar Atributos Semânticos

Os Atributos Semânticos são Atributos predeterminados, que são nomenclaturas bastante conhecidas para tipos comuns de dados. Usar Atributos Semânticos permite que você normalize esse tipo de informação em seus sistemas.

Para usar Atributos Semânticos em Python, certifique-se de ter o pacote de convenções semânticas:

pip install opentelemetry-semantic-conventions

Então você pode usá-lo no código:

from opentelemetry import trace
from opentelemetry.semconv.trace import SpanAttributes

// ...

current_span = trace.get_current_span()
current_span.set_attribute(SpanAttributes.HTTP_METHOD, "GET")
current_span.set_attribute(SpanAttributes.HTTP_URL, "https://opentelemetry.io/")

Adicionando Eventos

Um evento é uma mensagem legível por humanos em um trecho que representa “algo está acontecendo” durante sua vida. Você pode pensar nisso como um log primitivo.

from opentelemetry import trace

current_span = trace.get_current_span()

current_span.add_event("Vou tentar!")

# Faça alguma coisa

current_span.add_event("Consegui!")

Um trecho pode ser criado com zero ou mais links de trecho que o vinculam causalmente a outro trecho. Um link precisa de um contexto de trecho para ser criado.

from opentelemetry import trace

tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span("trecho-1"):
    # Faça algo que 'trecho-1' rastreia.
    ctx = trace.get_current_span().get_span_context()
    link_from_span_1 = trace.Link(ctx)

with tracer.start_as_current_span("trecho-2", links=[link_from_span_1]):
    # Faça algo que 'trecho-2' rastreia.
    # O link em 'trecho-2' está causalmente associado ao 'trecho-1',
    # mas não é um trecho filho.
    pass

Definir Status do Trecho

A Status can be set on a Span, typically used to specify that a Span has not completed successfully - Error. By default, all spans are Unset, which means a span completed without error. The Ok status is reserved for when you need to explicitly mark a span as successful rather than stick with the default of Unset (i.e., “without error”).

The status can be set at any time before the span is finished.

from opentelemetry import trace
from opentelemetry.trace import Status, StatusCode

current_span = trace.get_current_span()

try:
    # algo que pode falhar
except:
    current_span.set_status(Status(StatusCode.ERROR))

Registrar Exceções em Trechos

Pode ser uma boa ideia registrar exceções quando elas acontecem. Recomenda-se fazer isso em conjunto com a definição do status do trecho.

from opentelemetry import trace
from opentelemetry.trace import Status, StatusCode

current_span = trace.get_current_span()

try:
    # algo que pode falhar

# Considere capturar uma exceção mais específica em seu código
except Exception as ex:
    current_span.set_status(Status(StatusCode.ERROR))
    current_span.record_exception(ex)

Alterar o Formato de Propagação Padrão

Por padrão, o OpenTelemetry Python usará os seguintes formatos de propagação:

  • W3C Trace Context
  • W3C Baggage

Se você precisar alterar os padrões, pode fazê-lo por meio de variáveis de ambiente ou no código:

Usando Variáveis de Ambiente

Você pode definir a variável de ambiente OTEL_PROPAGATORS com uma lista separada por vírgulas. Os valores aceitos são:

  • "tracecontext": W3C Trace Context
  • "baggage": W3C Baggage
  • "b3": B3 Single
  • "b3multi": B3 Multi
  • "jaeger": Jaeger
  • "xray": AWS X-Ray (terceiros)
  • "ottrace": OT Trace (terceiros)
  • "none": Nenhum propagador configurado automaticamente.

A configuração padrão é equivalente a OTEL_PROPAGATORS="tracecontext,baggage".

Usando APIs do SDK

Como alternativa, você pode alterar o formato no código.

Por exemplo, se você precisar usar o formato de propagação B3 do Zipkin, pode instalar o pacote B3:

pip install opentelemetry-propagator-b3

E então definir o propagador B3 no seu código de inicialização de rastreamento:

from opentelemetry.propagate import set_global_textmap
from opentelemetry.propagators.b3 import B3Format

set_global_textmap(B3Format())

Observe que as variáveis de ambiente substituirão o que está configurado no código.

Leituras Adicionais

Métricas

Para começar a coletar métricas, você precisará inicializar um MeterProvider e opcionalmente defini-lo como o padrão global.

from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import (
    ConsoleMetricExporter,
    PeriodicExportingMetricReader,
)

metric_reader = PeriodicExportingMetricReader(ConsoleMetricExporter())
provider = MeterProvider(metric_readers=[metric_reader])

# Define o provedor global padrão de medidor
metrics.set_meter_provider(provider)

# Cria um medidor a partir do provedor global de medidor
meter = metrics.get_meter("meu.medidor.nome")

Criando e Usando Instrumentos Síncronos

Os instrumentos síncronos são usados para fazer medições do seu aplicativo e são usados em linha com a lógica de processamento de aplicativos/negócios, como ao lidar com uma solicitação ou chamar outro serviço.

Primeiro, crie seu instrumento. Os instrumentos geralmente são criados uma vez no nível do módulo ou da classe e depois usados em linha com a lógica de negócios. Este exemplo usa um instrumento Counter para contar o número de itens de trabalho concluídos:

work_counter = meter.create_counter(
    "trabalho.contador", unit="1", description="Conta a quantidade de trabalho feito"
)

Usando a operação de adição do Counter, o código abaixo incrementa a contagem em um, usando o tipo de item de trabalho como um atributo.

def fazer_trabalho(item_trabalho):
    # conta o trabalho sendo feito
    work_counter.add(1, {"trabalho.tipo": item_trabalho.tipo_trabalho})
    print("fazendo algum trabalho...")

Criando e Usando Instrumentos Assíncronos

Instrumentos assíncronos fornecem ao usuário uma maneira de registrar funções de callback, que são invocadas sob demanda para fazer medições. Isso é útil para medir periodicamente um valor que não pode ser instrumentado diretamente. Os instrumentos assíncronos são criados com zero ou mais callbacks que serão invocados durante a coleta de métricas. Cada callback aceita opções do SDK e retorna suas observações.

Este exemplo usa um instrumento Gauge Assíncrono para relatar a versão de configuração atual fornecida por um servidor de configuração, por meio da extração de um endpoint HTTP. Primeiro, escreva um callback para fazer observações:

from typing import Iterable
from opentelemetry.metrics import CallbackOptions, Observation


def raspar_versoes_configuracao(options: CallbackOptions) -> Iterable[Observation]:
    r = requests.get(
        "http://configserver/version_metadata", timeout=options.timeout_millis / 10**3
    )
    for metadata in r.json():
        yield Observation(
            metadata["version_num"], {"config.name": metadata["version_num"]}
        )

Observe que o OpenTelemetry passará opções para seu callback contendo um timeout. Os callbacks devem respeitar esse timeout para evitar bloqueios indefinidamente. Por fim, crie o instrumento com o callback para registrá-lo:

meter.create_observable_gauge(
    "config.versao",
    callbacks=[raspar_versoes_configuracao],
    description="A versão de configuração ativa para cada configuração",
)

Leituras Adicionais

Logs

A API e o SDK de logs estão atualmente em desenvolvimento.

Próximos Passos

Você também desejará configurar um exportador apropriado para exportar seus dados de telemetria para um ou mais backends de telemetria.