| name | otel-python-style |
| description | Python OpenTelemetry style: module-scope tracers/meters, decorators for bounded work, error spans, logs, and no wrappers. |
OTel Python Style
Acquire OTel objects at module scope.
from opentelemetry import metrics, trace
from opentelemetry.trace import Status, StatusCode
tracer = trace.get_tracer("mugline.voice")
meter = metrics.get_meter("mugline.voice")
greetings = meter.create_counter("voice.greetings.delivered", unit="1")
Bounded Work
Prefer decorators for functions with clear boundaries.
@tracer.start_as_current_span("voice.deliver_initial_greeting")
async def _deliver_initial_greeting(*, tenant_id: str, user_id: str) -> None:
span = trace.get_current_span()
span.set_attributes({
"tenant.id": tenant_id,
"user.id": user_id,
"voice.use_case": "initial_greeting",
})
Use a context manager when a decorator does not fit.
with tracer.start_as_current_span("order.validate") as span:
span.set_attribute("tenant.id", tenant_id)
validate_order(order)
Do not use detached tracer.start_span(...); span.end() for bounded work.
Error Paths
Record exceptions on the active span.
try:
result = await client.messages.create(...)
except Exception as exc:
span = trace.get_current_span()
span.record_exception(exc)
span.set_status(Status(StatusCode.ERROR))
logger.exception("llm mug copy failed", extra={"tenant_id": tenant_id})
raise
Logs
If logs are claimed as OTLP-forwarded, configure both:
- an OTel LoggerProvider + OTLPLogExporter + LoggingHandler
set_logger_provider(logger_provider) from opentelemetry._logs
- log correlation for existing records, e.g.
LoggingInstrumentor().instrument(...)
Preserve existing logging.basicConfig, console/file handlers, and log levels.
Init Behavior
If the runtime needs OTLP auth headers, check OTEL_EXPORTER_OTLP_HEADERS during
initialization. When it is absent, log one clear warning and return without
installing exporters.
def init_observability() -> bool:
if not os.getenv("OTEL_EXPORTER_OTLP_HEADERS"):
logging.getLogger(__name__).warning(
"otel disabled; OTEL_EXPORTER_OTLP_HEADERS is not set"
)
return False
...
return True
Add a small _INITIALIZED guard only when the app can realistically call this
function more than once.
Metrics
Counters:
llm.tokens.input
llm.tokens.output
- requests/events/jobs/errors
Use semantic units when the SDK supports them: token counters use
unit="tokens". Do not add app-side llm.cost_usd pricing metrics for normal
LLM calls; Superlog estimates cost centrally from provider/model/token data.
Histograms:
- duration
- latency
- payload size
Avoid raw high-cardinality values in metric attributes. Prefer
tenant/org/project, operation/use case, provider/model, and outcome dimensions
over user-level metric tags.