pain001.observability package#

Observability surface for pain001: metric events + OpenTelemetry tracing.

Two complementary mechanisms:

  • emit_metric_event() and the register_metrics_callback / clear_metrics_callbacks / has_metrics_callbacks helpers implement the in-process metric-event fan-out the generator core has used since v0.0.40. Callbacks see MetricEvent instances enriched with any active OpenTelemetry trace context.

  • traced(), init_otel(), set_span_attributes(), is_enabled(), and reset_for_tests() (re-exported from pain001.observability.otel) implement opt-in OpenTelemetry distributed tracing. Off by default; flipped on with OTEL_ENABLED=true and pip install "pain001[otel]".

The two surfaces compose: when OTel is enabled, every emit_metric_event call attaches the active span’s (trace_id, span_id, is_remote) to the event so the downstream metrics backend can correlate metrics back to traces.

class pain001.observability.MetricEvent(name: str, timestamp: float, attributes: dict[str, ~typing.Any] = <factory>, trace_context: dict[str, ~typing.Any] = <factory>)[source]#

Structured event emitted from processing steps.

pain001.observability.clear_metrics_callbacks() → None[source]#

Remove all registered metric callbacks.

pain001.observability.emit_metric_event(name: str, **attributes: Any) → None[source]#

Emit a structured metric event to registered callbacks.

pain001.observability.has_metrics_callbacks() → bool[source]#

Return True when metrics emission is enabled.

pain001.observability.register_metrics_callback(fn: Callable[[MetricEvent], None]) → None[source]#

Register a callback for structured metric events.

Submodules#

pain001.observability.otel module#

OpenTelemetry tracing surface for pain001 (opt-in, zero-cost when off).

Enterprise teams running pain001 in a microservices mesh need distributed traces across their generator + REST surface alongside Datadog / Honeycomb / Tempo / Jaeger. This module provides a single, env-gated tracing seam that:

  • defaults to a complete no-op (zero startup or per-call overhead) when OTEL_ENABLED is unset or when the pain001[otel] extra is not installed;

  • exposes one decorator (traced()) the generator and the REST handlers wear unconditionally - the decorator decides at call time whether to create a span;

  • exposes one bootstrap (init_otel()) called from the CLI serve subcommand and from the FastAPI app’s startup hook so the SDK is initialised once per process.

When enabled, the decorator stamps the span with rich attributes the operator can pivot on:

  • pain001.message_type - ISO 20022 message type being generated.

  • pain001.row_count - number of payment rows in the batch.

  • pain001.scheme - active scheme profile (sepa-sct etc.).

  • pain001.format - input format (csv, json, …).

  • http.route - the canonical route of a REST handler span.

Span names: pain001.generate (one batch, process_files), pain001.generate.streaming, pain001.render (template to string), pain001.validate (XSD), pain001.validate.scheme (rulebooks), pain001.write (render + validate + file write), and pain001.api.* for each REST handler.

Configuration via the OpenTelemetry SDK’s own env vars (OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME, …) so the operator surface stays the one every OTel-aware tool already uses. OTEL_ENABLED=true is the master switch pain001 owns on top of that.

pain001.observability.otel.OTEL_ENABLED_ENV = 'OTEL_ENABLED'#

Master switch operators flip to opt in to tracing.

pain001.observability.otel.PAIN001_SERVICE_NAME = 'pain001'#

Default service.name resource attribute (overridable by OTEL_SERVICE_NAME).

pain001.observability.otel.init_otel(service_name: str = 'pain001') → Any[source]#

Initialise the OTel SDK once per process and return the tracer.

Idempotent: subsequent calls return the cached tracer without re-initialising. Safe to call when OTEL_ENABLED is unset or when the pain001[otel] extra is missing - both cases return None and tracing stays off.

Parameters:

service_name – service.name resource attribute. Defaults to "pain001"; override when wrapping pain001 inside a larger service ("acme-payments").

Returns:

The tracer object the rest of the SDK exposes (an opentelemetry.trace.Tracer), or None when tracing is disabled or the SDK is not installed.

pain001.observability.otel.is_enabled() → bool[source]#

Return True iff the operator has set OTEL_ENABLED=true.

A separate env-var name from OTel’s own toggle so pain001 can be instrumented without enabling tracing for every other library in the same process.

Returns:

True when the env var is one of true / 1 / yes (case-insensitive); False otherwise.

pain001.observability.otel.reset_for_tests() → None[source]#

Drop the cached tracer + reset the init flag.

Test-only seam. Production code never resets; the tracer lives for the lifetime of the process.

pain001.observability.otel.set_span_attributes(**attributes: Any) → None[source]#

Stamp the current span with the supplied attributes.

Helper for tracing-aware call sites that want to enrich the span set up by traced() without restructuring the function. Safe to call when tracing is disabled (no-op) and when called outside any span (no-op).

Conventionally pain001 attribute names are kebab-cased and prefixed with pain001. (pain001.message_type, pain001.row_count).

Parameters:

**attributes – Keyword arguments mapped onto span attributes.

pain001.observability.otel.traced(span_name: str, attributes: dict[str, Any] | None = None) → Callable[[F], F][source]#

Decorate a function so each call becomes an OTel span when enabled.

The decorator is always applied; the per-call decision is made by checking is_enabled() (cached after the first call). When tracing is off, the wrapper is a single if plus the original call - effectively free.

Coroutine functions (the FastAPI handlers) get an async wrapper so the span stays open across await and the function is still recognised as a coroutine by the framework. Call sites enrich the span with set_span_attributes().

Parameters:
  • span_name – Stable, kebab-cased span name ("pain001.generate", "pain001.validate").

  • attributes – Static attributes stamped on every span the decorator opens, such as {"http.route": "/api/v1/validate"} for a REST handler.

Returns:

A decorator producing a function that creates a span on each call (when enabled) and propagates the original function’s result + exceptions verbatim.