Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

otelfeature-instrument

A zero-code OpenTelemetry launcher, like opentelemetry-instrument, but with one addition: INTERNAL spans can be suppressed live, via a feature flag, with no application restart.

otelfeature-instrument uvicorn main:app --host 0.0.0.0 --port 8000

Add it as a dependency, change your CMD/entrypoint to run through otelfeature-instrument instead of your app directly (or instead of opentelemetry-instrument), and everything else - resource detection, OTLP export, zero-code instrumentation of installed libraries - works the same as opentelemetry-instrument. The one difference: any span of kind INTERNAL is suppressed while the telemetryLevel OpenFeature flag evaluates to IO, with its children reattaching to the real parent instead of being orphaned. See How it works for why that's not simply a Sampler.

Why this exists

This started as a prototype directly inside one service (see poc-otelfeature): most INTERNAL spans are connective tissue between the actual I/O boundaries (SERVER, CLIENT, PRODUCER, CONSUMER) a trace cares about - useful in detail, but frequently just noise once a system is understood. This package makes that verbosity control a reusable, install-and-go capability instead of per-service boilerplate.

Usage

uv add otelfeature-instrument
CMD ["sh", "-c", "exec otelfeature-instrument uvicorn main:app --host 0.0.0.0 --port ${PORT}"]

No code changes required in the target application - the same zero-code promise opentelemetry-instrument makes. If the application creates its own manual spans (e.g. tracer.start_as_current_span("some.internal.op", kind=SpanKind.INTERNAL)), those are covered too, not just spans created by auto-instrumentation.

How it works

The launcher mechanism

Identical to opentelemetry-instrument: cli.py prepends this package's own directory to PYTHONPATH (where sitecustomize.py lives) and execs the target command. Python's site module auto-imports sitecustomize at interpreter startup, so bootstrap.py's initialize() runs before the target application's own code is ever imported. initialize() then reuses opentelemetry-instrumentation's own entry-point discovery (_load_distro/_load_instrumentors) to zero-code-instrument whatever instrumentation packages (opentelemetry-instrumentation-fastapi, etc.) are installed - the exact same mechanism opentelemetry-instrument uses for that part.

Suppressing INTERNAL spans without orphaning children

A Sampler-based DROP decision happens too late: by the time a Sampler runs, the span's context has already been minted and handed to its children as parent_span_id. Dropping it after the fact leaves those children pointing at a parent that was never exported - an orphan.

Instead, tracing.py wraps the real Tracer: when verbosity is IO and a span's kind is INTERNAL, no span is created and context is never advanced past the real parent (start_span/start_as_current_span just hand back trace.get_current_span() unchanged). Anything created afterward - however deep in the call stack - inherits that same untouched parent, so it attaches directly to it instead of to the (nonexistent) suppressed span. The decision is made once, at the suppressed span's own creation, so it's stable for that span's entire lifetime even if the flag flips mid-request.

Example - a request that would otherwise produce:

POST /geocode                    (SERVER)
├─ http receive / http send × N  (INTERNAL, from ASGI instrumentation)
└─ geocode.resolve                (INTERNAL, application code)
   └─ GET                         (CLIENT, httpx)

produces, with telemetryLevel=IO:

POST /geocode      (SERVER)
└─ GET              (CLIENT, httpx)

The feature flag

flags.py registers a flagd OpenFeature provider in "in-process" resolver mode: the flag ruleset is synced from flagd once (and kept updated in the background over a gRPC stream) and evaluated in memory on every span - no network call on the per-span hot path. Editing the flag in flagd takes effect on the very next span, with no restart of the instrumented application.

Configuration

Environment variable Default Description
OTEL_SERVICE_NAME - Service name reported in telemetry
OTEL_EXPORTER_OTLP_ENDPOINT - OTLP/HTTP collector endpoint for traces and metrics
FLAGD_HOST localhost flagd host
FLAGD_PORT 8015 flagd gRPC sync port (in-process resolver)
OTELFEATURE_FLAG_PROVIDER flagd flagd to auto-register a flagd provider, none to manage OpenFeature yourself (see flags.py)

The telemetryLevel flag itself (variants FULL/IO) needs to exist in whatever flagd instance FLAGD_HOST/FLAGD_PORT point at - see poc-otelfeature/src/flagd for an example flag definition.

Known limitations

  • bootstrap.py reaches into opentelemetry.instrumentation.auto_instrumentation._load, a private module of opentelemetry-instrumentation - hence the exact version pin in pyproject.toml. A future opentelemetry-instrumentation release could change or remove it.
  • The flag key (telemetryLevel) and its variants (FULL/IO) are hardcoded for now, not yet configurable per consuming application.
  • Only traces and metrics are bootstrapped (matching poc-otelfeature's current scope) - no log correlation setup.

About

Zero-code OpenTelemetry launcher with live, flagd-controlled internal-span suppression

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages