Skip to main content
The SDK emits traces and logs through libs/telemetry.py in one of two modes: collector fan-out (the default) or a direct single-sink fallback. This page explains both modes, how to deploy the collector, and which environment variables belong where.

Design goals

Telemetry is never load-bearing. Every export path is fire-and-forget, exporter failures degrade to no-ops, and instrumenting a hot path can never fail it:
  • If neither mode is configured, init_tracer and init_log_exporter return None and every span or event call is a no-op.
  • If the opentelemetry-* packages are missing from an image, initialization prints one stderr line and degrades instead of crashing the container.
  • The collector’s queue is in-memory, not volume-backed. A container recycle can drop an unflushed batch — that loss is acceptable by design.

Collector fan-out (default)

By default, applications never talk to a telemetry provider directly. A custom OTEL exporter in libs/telemetry.py serializes each batch to OTLP protobuf and fire-and-forget .spawn()s the collector Modal function (src/otel_collector.py) over Modal RPC. There is no public endpoint anywhere in this path.
The fan_out function feeds each batch to a real OpenTelemetry Collector (otelcol) running as a localhost sidecar in the same container. The container is pinned always-warm and singular (min_containers=1, max_containers=1), so the sidecar’s in-memory queue is a single shared buffer. The sidecar fans out to all configured providers — Dash0, HyperDX, Logfire, and Grafana — with real batching, retry, and queueing handled by otelcol rather than hand-rolled Python. Two properties make this the default:
  • Provider credentials live only on the collector. Application containers hold no provider tokens; they only need permission to spawn the collector function.
  • No inbound attack surface. The sidecar’s OTLP receiver binds 127.0.0.1, so it is reachable from fan_out in the same container but never from outside. Ingress is Modal’s authenticated RPC.
The collector app name is hard-coded as DEFAULT_COLLECTOR_APP = "otel-collector" in libs/telemetry.py, so collector mode works with zero producer-side configuration. Override the app with TELEMETRY_COLLECTOR_APP=<COLLECTOR_APP_NAME> (for example, a dev collector) and the function name with TELEMETRY_COLLECTOR_FUNCTION (defaults to fan_out).

Direct single-sink (fallback)

Set TELEMETRY_COLLECTOR_APP="" (explicit empty string) to opt out of collector mode. The producer then exports to a single OTLP HTTP sink resolved from HYPERDX_API_KEY, HYPERDX_OTLP_ENDPOINT, or OTEL_EXPORTER_OTLP_ENDPOINT. If none of those are set either, telemetry is a clean no-op. This path is intended for local development and tests.
The direct path has no Logfire exporter. Logfire is reachable only through the collector, so an app running in direct mode silently sends nothing to Logfire — no error, no log line. If Logfire is missing data, first confirm the producer is actually in collector mode (that is, TELEMETRY_COLLECTOR_APP is unset or non-empty).

Deploying the collector

The collector is a standalone Modal app — it has its own modal.App and is deliberately not registered in src/app.py, and it exposes no web endpoint. Deploy it on its own, wrapped in Infisical so the provider credentials are present in the deploy-time environment:
At deploy time, the provider credentials found in the host environment are baked into the collector’s own inline Modal secret. At runtime, they select which exporters the sidecar configures — a collector deployed with no provider credentials degrades to a clean no-op rather than a boot loop.
Grafana is special-cased: the raw GRAFANA_INSTANCE_ID and GRAFANA_API_KEY are deploy-time-only inputs. The deploy derives a pre-encoded Basic credential (GRAFANA_OTLP_AUTH) from them, so the raw token never reaches the collector container. See the Grafana guide linked below.

Environment variables

Producer-side variables configure applications, webhooks, and the CLI — the processes that emit telemetry: Collector-side provider credentials live only in the collector’s secret (names from _PROVIDER_SECRET_KEYS in src/otel_collector.py):

Provider setup guides

Dash0 setup

Regional endpoints, Infisical secrets, and dataset configuration for Dash0.

Grafana Cloud setup

Region-scoped OTLP gateway and Basic-auth credential derivation for Grafana Cloud.

Verifying

Two scripts check the pipeline end to end: scripts/collector-deployment-verify.py confirms the collector app is deployed and reports which provider exporters its configuration includes, and scripts/dash0-telemetry-verify.py emits real logs, traces, and span events so you can confirm they arrive in Dash0. Run both under infisical run so the provider credentials they inspect are present in the environment.
Last modified on August 24, 2026