Skip to content

OpenTelemetry

One span per query and per-node metrics, sent wherever your OpenTelemetry SDK sends them.

Use it when

  • You run polars in a service or pipeline that already reports to OpenTelemetry, and want queries in the same traces.
  • You want dashboards and alerts on query time across many runs.

Set up

The package depends on the OpenTelemetry API only. Your application installs the SDK and decides where data goes; polars-telemetry[otlp] adds the SDK and the OTLP exporter.

pip install 'polars-telemetry[otlp]'
from opentelemetry import metrics, trace
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

tracer_provider = TracerProvider()
tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(tracer_provider)
metrics.set_meter_provider(
    MeterProvider(metric_readers=[PeriodicExportingMetricReader(OTLPMetricExporter())])
)

import polars_telemetry

polars_telemetry.install()  # OpenTelemetry is the default exporter

To combine it with another exporter, pass it explicitly: exporter=[OTelExporter(config), ...].

What you get

A span named polars.collect, a child of whatever span was active when the query ran. For a group-by over a filtered parquet scan:

polars.collect
  polars.query.label          nightly/revenue_by_region
  polars.engine               streaming
  polars.plan.fingerprint     f7d144838d88
  code.file.path              /srv/app/reports.py
  code.line.number            23
  code.function.name          revenue_by_region
  polars.cpu_ms               81.714
  polars.parallelism          3.987
  polars.result.rows          4
  polars.hot_node.kind        GroupBy
  polars.hot_node.share       0.9925
  polars.scan.sources         ["orders.parquet"]
  polars.scan.predicates      ["col(\"amount\") > 100.0"]
  polars.scan.predicate_pushed  true
  polars.groupby.keys         ["col(\"region\")"]
  ...

The span's status is ERROR with polars' message when the query failed. There are no child spans per node: polars reports no per-node timestamps.

Metrics: three per query (polars.query.duration, polars.query.cpu_time, polars.query.parallel_efficiency) by plan fingerprint and engine, and fifteen per node by node kind. Every name and unit is in Spans and metrics.

Options

Through Config, passed to both install() and OTelExporter:

Option Default Effect here
node_metrics True Per-node counters and all that comes from them: node metrics, polars.cpu_ms, the hot node, most diagnostics
include_plan False The whole plan as JSON in polars.plan
call_site True The code.* attributes
redaction None Mask literals and more in span attributes and the error message; set on install()'s config or with redacted()

Your data

Scan paths, predicates, join keys and group-by keys go into span attributes as written, literals included. Metrics never carry them: every metric dimension comes from a bounded set. To send this exporter a masked copy while others keep full detail, wrap it in redacted(); see Data and privacy.

Cost

About 0.1 ms per query for the span. The metrics cost about 9 µs per value recorded: one per node for each time and ratio, and one per node kind for each count, since counts are summed first. That is most of the cost on a large plan: about 2.4 ms for a 22-node TPC-H query. node_metrics=False removes it, along with everything derived from the counters. This runs on the thread that ran the query, after it finished.

When it fails

Without an OpenTelemetry SDK, the API's providers do nothing: no error, and nothing is sent. A collector that is down is the SDK's to handle; its batch processor drops and logs. An error inside this exporter is logged once, and after five it is disabled for the rest of the process.