Repository navigation
Description
FastStreamOpenTelemetryInstrument.bootstrap() overrides the base without calling
super().bootstrap():
def bootstrap(self) -> None:
config = self.bootstrap_config
if config.opentelemetry_middleware_cls and config.application.broker:
config.application.broker.add_middleware(
config.opentelemetry_middleware_cls(tracer_provider=get_tracer_provider())
)It is the only OpenTelemetry instrument in FastStreamBootstrapper.instruments_types, so on FastStream
lite-bootstrap never constructs a TracerProvider at all. The middleware is wired to whatever
opentelemetry.trace.get_tracer_provider() returns, which in a process where nothing else installed one
is a ProxyTracerProvider handing out NonRecordingSpans.
What is inert
FastStreamConfig inherits OpenTelemetryConfig, so all of these are accepted and silently do nothing:
opentelemetry_endpoint,opentelemetry_exporter_protocol,opentelemetry_insecure— no exporter is builtopentelemetry_service_name,opentelemetry_namespace,opentelemetry_container_name— noResourceis builtopentelemetry_log_traces— no console span processoropentelemetry_instrumentors—_apply_instrumentorslives in the basebootstrap()opentelemetry_sampler(OpenTelemetry sampler is not configurable #184, feat: make the OpenTelemetry sampler configurable, and compress the ADRs #222) — joins the list
is_configured still returns True for a config carrying opentelemetry_endpoint and
opentelemetry_middleware_cls, so the instrument reports as active in build_summary() while exporting
nothing. The docs reinforce it: docs/introduction/configuration.md documents these fields in one
OpenTelemetry section and then adds "For FastStream you must provide additionally:
opentelemetry_middleware_cls", which reads as "the above, plus one more".
Teardown is asymmetric for the same reason: the inherited teardown() calls uninstrument() on every
entry in opentelemetry_instrumentors that bootstrap() never instrumented, and the base's
_silence_otel_loggers() never ran, so OTel's "Attempting to uninstrument while already uninstrumented"
warning is not suppressed.
Why it is not obvious
Nothing pins the current behaviour — no test asserts that FastStream uses the ambient provider, and no ADR
records it as a decision. It may well be deliberate (a FastStream service is often bootstrapped alongside
something else that owns the provider), but if so it is undocumented, and the config surface contradicts it.
Options
- Call
super().bootstrap()and pass the instrument's own provider to the middleware. Makes every
documented field work. It is a behaviour change: lite-bootstrap would start constructing and
set_tracer_provider-ing for FastStream services that today supply their own, and the SDK enforces
set-once per process, so a service doing both would find one of the two silently ignored. - Keep the ambient provider and narrow the surface. Document that FastStream consumes an
externally-installed provider, and make the inert fields say so — at minimum in the docs, possibly as a
warn_at_callerwhen a FastStream config carriesopentelemetry_endpoint.
Either way it wants an ADR, since the choice is exactly the kind of thing docs/adr/ records.
Found while implementing #184, whose "This applies to every bootstrapper, since they share
OpenTelemetryInstrument" holds for FastAPI, Litestar and Free but not for FastStream.
Activity