OpenTelemetry Python distro for Uptrace

uptrace-pythonopen in new window configures opentelemetry-pythonopen in new window to export spans and metrics to Uptrace using OpenTelemetry protocol (OTLP).

Resources

Installation

To install uptrace-python:

pip install uptrace

Configuration

You configure Uptrace client using a DSN (Data Source Name, e.g. https://<token>@api.uptrace.dev/<project_id>) from the project settings page. Add the following code to the app main file (manage.py for Django):

import uptrace
from opentelemetry import trace

# Set dsn or UPTRACE_DSN env var.
uptrace.configure_opentelemetry(dsn="")
tracer = trace.get_tracer("app_or_package_name", "1.0.0")

The following configuration options are supported.

OptionDescription
dsnA data source that is used to connect to uptrace.dev. For example, https://<key>@api.uptrace.dev/<project_id>.
service_nameservice.name resource attribute. For example, myservice.
service_versionservice.version resource attribute. For example, 1.0.0.
resource_attributesAny other resource attributes.
resourceResource contains attributes representing an entity that produces telemetry. Resource attributes are copied to all spans and events.

You can also use environment variables to configure the client:

Env varDescription
UPTRACE_DSNA data source that is used to connect to uptrace.dev. For example, https://<key>@api.uptrace.dev/<project_id>.
OTEL_RESOURCE_ATTRIBUTESKey-value pairs to be used as resource attributes. For example, service.name=myservice,service.version=1.0.0.
OTEL_PROPAGATORSPropagators to be used as a comma separated list. The default is tracecontext,baggage.

OpenTelemetry API

All the code below is also available as a runnable exampleopen in new window.

Creating a tracer

To start creating spans, you need a tracer. You create a tracer by specifying a tracer name (AKA instrumentation library name):

from opentelemetry import trace

tracer = trace.get_tracer("app_or_package_name", "1.0.0")

You can have as many tracers as you want, but usually you need only one tracer for each app/library. Use tracer names to identify the library that produces the spans.

Creating a span

Once you have a tracer, creating spans is easy:

# Create a span with name "operation-name" and kind="server".
with tracer.start_as_current_span("operation-name", kind=trace.SpanKind.SERVER) as span:
    do_some_work()

Adding span attributes

To record contextual information, you can annotate spans with attributes that carry information specific to the operation. For example, an HTTP endpoint may have such attributes as http.method = GET and http.route = /projects/:id.

# To avoid expensive computations, check that span is recording
# before setting any attributes.
if span.is_recording():
    span.set_attribute("http.method", "GET")
    span.set_attribute("http.route", "/projects/:id")

You can name attributes as you want, but for common operations you should use semantic attributesopen in new window convention. It defines a list of common attribute keys with their meaning and possible values.

Adding span events

You can annotate spans with events that have start time and arbitrary number of attributes. The main difference between events and spans is that events don't have end time (and therefore no duration).

Events usually represent exceptions, errors, logs, and messages (such as in RPC). But you can record custom events as well.

span.add_event("log", {
    "log.severity": "error",
    "log.message": "User not found",
    "enduser.id": "123",
})

Recording exceptions

OpenTelemetry provides a shortcut to record an exception.

except ValueError as exc:
    # Record the exception and update the span status.
    span.record_exception(exc)
    span.set_status(trace.Status(trace.StatusCode.ERROR, str(exc)))

Trace context and the active span

OpenTelemetry stores the current active span in a context and saves the context in a thread-local storage. You can nest contexts inside each other and OpenTelemetry will automatically activate the parent span context when you end the span.

start_as_current_span sets the active span for you, but you can also activate the span manually:

# Activate the span in the current context.
with trace.use_span(span, end_on_exit=True):
    do_some_work()

To get the current active span:

span = trace.get_current_span()

Auto-instrumentation

!!! note When possible you should prefer using explicit instrumentation. For example, auto-instrumentation does not work with Flask in debug mode.

opentelemetry-python allows you to automatically instrument any Python app using opentelemetry-instrument utility from opentelemetry-instrumentation package.

First you need to install the opentelemetry-instrument executable:

pip install opentelemetry-instrumentation

Then install instrumentations that should be applied automatically by opentelemetry-instrument:

pip install opentelemetry-instrumentation-flask

And run you app using opentelemetry-instrument wrapper:

UPTRACE_DSN="https://<token>@api.uptrace.dev/<project_id>" opentelemetry-instrument python3 main.py

See flask-auto-instrumentationopen in new window example for full details.

How auto-instrumentation works?

uptrace-python registers an OpenTelemetry distro using an entry point in setup.cfgopen in new window. Instrumentations register themselves using the same mechanism. OpenTelemetry then just calls the code specified in entry points to configure OpenTelemetry SDK and instrument the code.

What is next?

By now, you should be able to use OpenTelemetry API to instrument your app. To help with that, we've created examplesopen in new window that show how to use OpenTelemetry instrumentationsopen in new window for popular frameworks and libraries.