Aller au contenu

Cette page n'est pas encore traduite. Elle s'affiche en anglais.

OpenTelemetry in Python

Use the opentelemetry API in Python on Elide without an SDK: the vendored and installed API, spans, metrics, logging export, provider handover, and the elide.telemetry module.

Sur cette page

Instrument code with the standard opentelemetry API. Elide implements it: there is no SDK, exporter, or opentelemetry-instrument wrapper to add, and libraries that already use the API work unchanged.

# app.py
import logging
from opentelemetry import trace

tracer = trace.get_tracer("checkout")
with tracer.start_as_current_span("charge") as span:
    span.set_attribute("order.id", 42)
    logging.warning("card declined, retrying")
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 elide run app.py

The API package

Your environmentimport opentelemetry resolves to
No opentelemetry-api installedElide’s vendored copy (opentelemetry-api 1.45.1)
opentelemetry-api installedYour copy, backed by Elide

Elide installs its tracer, meter, and logger providers when your code first imports opentelemetry.trace, opentelemetry.metrics, or opentelemetry._logs. Nothing is imported before your code does, and the import order among those modules does not matter. The vendored copy answers importlib.metadata like an installed wheel, so OTEL_PROPAGATORS, OTEL_PYTHON_CONTEXT, and the OTEL_PYTHON_*_PROVIDER variables work as usual. When telemetry is not configured, the API returns its no-op implementations.

Traces

Tracers, spans, links, events, and status follow the API. Context is the API’s contextvars context, so it follows with blocks, threads that copy the context, and asyncio tasks.

from opentelemetry import trace

tracer = trace.get_tracer("jobs")

@tracer.start_as_current_span("import rows")
def import_rows(rows):
    trace.get_current_span().set_attribute("rows", len(rows))

A span started with no active Python span is parented to the request Elide is serving on that thread, if any. Passing an explicit context= starts a root span instead.

Metrics

from opentelemetry import metrics

pending = []
meter = metrics.get_meter("checkout")
orders = meter.create_counter("orders", unit="{order}")
latency = meter.create_histogram(
    "charge.duration", unit="s", explicit_bucket_boundaries_advisory=[0.01, 0.05, 0.1, 0.5, 1]
)

def queue_depth(options):
    yield metrics.Observation(len(pending))

meter.create_observable_gauge("queue.depth", callbacks=[queue_depth])

orders.add(1, {"region": "eu"})
latency.record(0.042)

All synchronous and observable instruments are supported, and callbacks may be generators. Callbacks run at each collection.

Logging

With logs.console on (the default), records that a logging handler would print are also exported as log records, with the active span’s ids. What logging prints is unchanged, and records no handler would emit are not exported.

logging fieldLog record
Logger nameInstrumentation scope
levelnoSeverity: DEBUG 5, INFO 9, WARNING 13, ERROR 17, CRITICAL 21
getMessage()Body
pathname, funcName, linenocode.file.path, code.function.name, code.line.number
exc_infoexception.type, exception.message, exception.stacktrace

The logs API works too; records keep structured bodies (dict, list, bytes):

from opentelemetry._logs import get_logger, LogRecord, SeverityNumber

get_logger("checkout").emit(
    LogRecord(severity_number=SeverityNumber.WARN, body={"event": "retry", "attempt": 2})
)

An uncaught exception is exported as an error with its Python traceback; see Errors.

Using an OpenTelemetry SDK

An application can still set its own providers, from opentelemetry-sdk or opentelemetry-instrument:

EventResult
The application sets a tracer, meter, or logger providerIt takes over that signal and Elide prints a warning. Tracers, meters, loggers, and observable callbacks created earlier forward to it.
OTEL_PYTHON_TRACER_PROVIDER (or _METER_, _LOGGER_) names a providerElide never installs its own for that signal.
elide.telemetry.release()Elide unsets its providers; the application’s take over without warnings.
An OpenTelemetry SDK set its own tracer provider; Elide's Python tracer provider steps aside

Elide’s runtime telemetry keeps recording either way.

elide.telemetry

Controls the API does not cover. Stubs ship with Elide’s Python type stubs; every function is safe to call when telemetry is not configured.

import elide.telemetry
from opentelemetry import trace

with trace.get_tracer("jobs").start_as_current_span("nightly"):
    print(elide.telemetry.current_span_context()["traceparent"])

elide.telemetry.flush()
print(elide.telemetry.status()["spans"])
FunctionReturnsDescription
active()boolWhether telemetry is configured for this process.
flush(timeout_millis=5000)boolExports everything recorded so far, waiting up to timeout_millis. Whether every destination accepted it.
status()TelemetryStatusApproximate, process-wide pipeline counters; see below.
current_span_context()CurrentSpanContext | NoneThe active span’s trace_id, span_id, trace_flags, and traceparent, or None outside a valid span.
release()boolStops serving as the global OpenTelemetry implementation, so a provider set afterwards owns every signal. Elide’s runtime instrumentation keeps recording. False when already released or never installed.
class TelemetryStatus(TypedDict):
    active: bool
    released: bool
    stepped_aside: list[str]  # "trace", "metrics", "logs"
    spans: SpanCounters  # started, unsampled, ended, live, exported, dropped, table_full, reclaimed,
    #                      handed_off, exit_dropped
    logs: LogCounters  # emitted, filtered, exported, dropped, handed_off, exit_dropped
    metrics: MetricCounters  # handed_off, exit_dropped
    export_failures: int
    exports_abandoned: int

class CurrentSpanContext(TypedDict):
    trace_id: str
    span_id: str
    trace_flags: int
    traceparent: str

Limits

  • Requests served by Elide do not set the Python contextvars context; spans parent to the request through Elide instead, as described in Traces.
  • Python HTTP clients do not get client spans or traceparent; inject it with opentelemetry.propagate.inject(headers).

Samples

Cette page vous a-t-elle aidé ?