benzene.otel

Export the port's existing traces to OpenTelemetry, plus a response-as-event pattern. Distribution: benzene-otel (depends on benzene-core, benzene-mesh).

pip install "benzene-otel[otel]"   # the [otel] extra pulls opentelemetry-api / opentelemetry-sdk

Overview

The Benzene Python port already traces itself: benzene.mesh.trace_middleware times every invocation and emits one benzene.mesh.TraceEvent (topic, status, W3C trace ids, start time, duration) through a benzene.mesh.TraceExporter seam. Until now that tracing was Benzene-internal only — it fed a mesh collector, never a general OpenTelemetry pipeline. This distribution closes that gap without re-instrumenting anything:

Both seams — the tracer and the sink — are duck-typed, so everything here runs (and is fully tested) with no opentelemetry package and no network. The SDK is imported lazily and only when no tracer is injected. Mirrors the role of .NET's Benzene.Diagnostics OTel wiring and Benzene.ResponseEvents.

OtelTraceExporter — trace export

OtelTraceExporter consumes the mesh trace model and pushes each span to an OpenTelemetry tracer, rather than instrumenting the pipeline a second time. Wire it as the sink of the port's existing tracing:

from benzene.mesh import trace_middleware
from benzene.otel import OtelTraceExporter

definition.middleware.insert(0, trace_middleware(OtelTraceExporter(), service="orders"))

OtelTracer and OtelSpan are runtime_checkable protocols describing the slice of an OpenTelemetry Tracer / Span the exporter drives (start_span(name, *, start_time=...); set_attribute / set_status / end) — a real opentelemetry object satisfies them, and so does a test fake.

Response-as-event

Where the exporter records traces (how long, where in the trace), this pattern records outcomes: after a handler runs, the invocation's result — its topic, semantic status, and optional payload, tagged with the business correlation id — is emitted as a discrete event to a pluggable ResponseEventSink. That feeds an event stream (an OTel event/log pipeline, an outbox, an audit topic) with "topic x produced status y" without the handler knowing it is observed.

from benzene.otel import RecordingSink, response_event_interception

sink = RecordingSink()  # swap for an OTel-log / outbox / audit-topic sink in production
definition.middleware.append(response_event_interception(sink))

Troubleshooting

Exports

OtelTraceExporter, OtelTracer, OtelSpan, response_event_interception, ResponseEvent, ResponseEventSink, RecordingSink, CORRELATION_ID_HEADER.

See also