Instrumentación

Instrumentación manual para OpenTelemetry Python

Instrumentation is the act of adding observability code to an app yourself.

If you’re instrumenting an app, you need to use the OpenTelemetry SDK for your language. You’ll then use the SDK to initialize OpenTelemetry and the API to instrument your code. This will emit telemetry from your app, and any library you installed that also comes with instrumentation.

If you’re instrumenting a library, only install the OpenTelemetry API package for your language. Your library will not emit telemetry on its own. It will only emit telemetry when it is part of an app that uses the OpenTelemetry SDK. For more on instrumenting libraries, see Libraries.

For more information about the OpenTelemetry API and SDK, see the specification.

Configuración

Primero, asegúrate de tener los paquetes API y SDK:

pip install opentelemetry-api
pip install opentelemetry-sdk

Trazas

Adquirir el trazador

Para comenzar a realizar trazas, necesitarás inicializar un TracerProvider y opcionalmente configurarlo como el proveedor global predeterminado.

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)

# Establece el proveedor de tracer global predeterminado
trace.set_tracer_provider(provider)

# Crea un tracer a partir del proveedor de tracer global
tracer = trace.get_tracer("my.tracer.name")

Crear spans

Para crear un span, normalmente querrás iniciarlo como el span actual.

def do_work():
    with tracer.start_as_current_span("span-name") as span:
        # realiza algún trabajo que 'span' rastreará
        print("haciendo algún trabajo...")
        # Cuando el bloque 'with' sale del contexto, 'span' se cierra automáticamente

También puedes usar start_span para crear un span sin hacerlo el span actual. Esto se suele hacer para rastrear operaciones concurrentes o asíncronas.

Crear spans anidados

Si tienes una sub-operación distinta que deseas rastrear como parte de otra, puedes crear spans para representar la relación:

def do_work():
    with tracer.start_as_current_span("parent") as parent:
        # realiza algún trabajo que 'parent' rastreará
        print("haciendo algún trabajo...")
        # Crea un span anidado para rastrear el trabajo anidado
        with tracer.start_as_current_span("child") as child:
            # realiza algún trabajo que 'child' rastreará
            print("haciendo trabajo anidado...")
            # el span anidado se cierra cuando sale del contexto

        # Este span también se cierra cuando sale del contexto

Cuando veas spans en una herramienta de visualización de trazas, child se rastreará como un span anidado bajo parent.

Crear spans con decoradores

Es común que un único span rastree la ejecución de una función completa. En ese caso, hay un decorador que puedes usar para reducir el código:

@tracer.start_as_current_span("do_work")
def do_work():
    print("haciendo algún trabajo...")

El uso del decorador es equivalente a crear el span dentro de do_work() y finalizarlo cuando do_work() termine.

Para usar el decorador, debes tener una instancia de tracer disponible globalmente para la declaración de tu función.

Obtener el span actual

A veces es útil acceder al span actual en un momento dado para poder enriquecerlo con más información.

from opentelemetry import trace

current_span = trace.get_current_span()
# enriquecer 'current_span' con más información

Agregar atributos a un span

Los atributos te permiten adjuntar pares clave/valor a un span para que contenga más información sobre la operación actual que está rastreando.

from opentelemetry import trace

current_span = trace.get_current_span()

current_span.set_attribute("operation.value", 1)
current_span.set_attribute("operation.name", "¡Diciendo hola!")
current_span.set_attribute("operation.other-stuff", [1, 2, 3])

Agregar atributos semánticos

Los atributos semánticos son atributos predefinidos que son convenciones de nombres bien conocidas para tipos comunes de datos. Usar atributos semánticos te permite normalizar este tipo de información en tus sistemas.

Para usar los atributos semánticos en Python, asegúrate de tener instalado el paquete de convenciones semánticas:

pip install opentelemetry-semantic-conventions

Luego puedes usarlos en el 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/")

Agregar eventos

Un evento es un mensaje legible en un span que representa “algo que sucede” durante su vida útil. Puedes pensarlo como un registro o log primitivo.

from opentelemetry import trace

current_span = trace.get_current_span()

current_span.add_event("¡Voy a intentarlo!")

# Haz la cosa

current_span.add_event("¡Lo hice!")

Agregar enlaces

Un span se puede crear con uno o más enlaces que lo vinculen causalmente con otro span. Un enlace necesita un contexto de span para ser creado.

from opentelemetry import trace

tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span("span-1"):
    # Hacer algo que 'span-1' rastrea.
    ctx = trace.get_current_span().get_span_context()
    link_from_span_1 = trace.Link(ctx)

with tracer.start_as_current_span("span-2", links=[link_from_span_1]):
    # Hacer algo que 'span-2' rastrea.
    # El enlace en 'span-2' está causalmente asociado con 'span-1',
    # pero no es un span hijo.
    pass

Establecer el estado del span

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 podría fallar
except:
    current_span.set_status(Status(StatusCode.ERROR))

Registrar excepciones en spans

Puede ser una buena idea registrar excepciones cuando ocurren. Se recomienda hacerlo a la vez que se establece el estado del span.

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

current_span = trace.get_current_span()

try:
    # algo que podría fallar

# Considera capturar una excepción más específica en tu código
except Exception as ex:
    current_span.set_status(Status(StatusCode.ERROR))
    current_span.record_exception(ex)

Cambiar el formato de propagación predeterminado

Por defecto, OpenTelemetry Python usa los siguientes formatos de propagación:

  • W3C Trace Context
  • W3C Baggage

Si necesitas cambiar los valores predeterminados, puedes hacerlo a través de variables de entorno o en el código.

Usando variables de entorno

Puedes establecer la variable de entorno OTEL_PROPAGATORS con una lista separada por comas. Los valores aceptados son:

  • "tracecontext": W3C Trace Context
  • "baggage": W3C Baggage
  • "b3": B3 Single
  • "b3multi": B3 Multi
  • "jaeger": Jaeger
  • "xray": AWS X-Ray (tercero)
  • "ottrace": OT Trace (tercero)
  • "none": Sin propagador configurado automáticamente.

La configuración predeterminada es equivalente a OTEL_PROPAGATORS="tracecontext,baggage".

Usando APIs del SDK

Alternativamente, puedes cambiar el formato en el código.

Por ejemplo, si necesitas usar el formato de propagación B3 de Zipkin, puedes instalar el paquete B3:

pip install opentelemetry-propagator-b3

Luego configura el propagador B3 en tu código de inicialización de trazado:

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

set_global_textmap(B3Format())

Nota que las variables de entorno anularán lo que esté configurado en el código.

Lectura adicional

Métricas

Para comenzar a recopilar métricas, necesitarás inicializar un MeterProvider y opcionalmente configurarlo como el proveedor global predeterminado.

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])

# Establece el proveedor de medidores global predeterminado
metrics.set_meter_provider(provider)

# Crea un medidor a partir del proveedor de medidores global
meter = metrics.get_meter("my.meter.name")

Crear y usar instrumentos síncronos

Los instrumentos se utilizan para realizar mediciones de tu aplicación. Los instrumentos síncronos se usan en línea con la lógica de procesamiento de aplicaciones/negocios, como cuando se maneja una solicitud o se llama a otro servicio.

Primero, crea tu instrumento. Los instrumentos generalmente se crean una vez al nivel del módulo o clase y luego se utilizan en línea con la lógica del negocio. Este ejemplo utiliza un contador para contar la cantidad de tareas de trabajo completadas:

work_counter = meter.create_counter(
    "work.counter", unit="1", description="Cuenta la cantidad de trabajo realizado"
)

Usando la operación de agregado del contador, el código a continuación incrementa el conteo en uno, utilizando el tipo de elemento de trabajo como un atributo.

def do_work(work_item):
    # contar el trabajo que se está realizando
    work_counter.add(1, {"work.type": work_item.work_type})
    print("haciendo algún trabajo...")

Crear y usar instrumentos asíncronos

Los instrumentos asíncronos permiten al usuario registrar funciones de devolución de llamada (callbacks), que se invocan cuando sea necesario para realizar mediciones. Esto es útil para medir periódicamente un valor que no se puede instrumentar directamente. Los instrumentos asíncronos se crean con una o más callbacks que serán invocadas durante la recopilación de métricas. Cada callback acepta opciones del SDK y devuelve sus observaciones.

Este ejemplo usa un medidor asíncrono (gauge) para reportar la versión actual de la configuración proporcionada por un servidor de configuración al hacer scraping de un endpoint HTTP. Primero, escribe una callback para hacer observaciones:

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


def scrape_config_versions(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"]}
        )

Nota que OpenTelemetry pasará opciones a tu callback que contienen un tiempo de espera. Las callbacks deben respetar este tiempo de espera para evitar bloquearse indefinidamente. Finalmente, crea el instrumento con la callback para registrarlo:

meter.create_observable_gauge(
    "config.version",
    callbacks=[scrape_config_versions],
    description="La versión activa de la configuración para cada configuración",
)

Lectura adicional

Logs

La API y SDK de logs están actualmente en desarrollo.

Próximos pasos

Tal vez quieras configurar un exportador adecuado para exportar tus datos de telemetría a uno o más backends de telemetría.