Source code for nlp_shap.runtime.telemetry

"""Optional observability hooks for explain pipeline stages."""

import time
from collections.abc import Iterator
from contextlib import contextmanager
from dataclasses import dataclass, field
from typing import Protocol


[docs] @dataclass(frozen=True, slots=True) class SpanRecord: """One completed pipeline stage span.""" name: str """Stage identifier emitted by the orchestrator or estimator.""" duration_ms: float """Wall-clock duration of the stage in milliseconds."""
[docs] class ObservabilitySink(Protocol): """Protocol for recording explain pipeline stage spans."""
[docs] @contextmanager def span(self, name: str) -> Iterator[None]: """Record wall-clock duration for one named pipeline stage."""
[docs] class NullObservabilitySink: """No-op observability sink used when telemetry is disabled."""
[docs] @contextmanager def span(self, name: str) -> Iterator[None]: """Yield without recording span metadata.""" _ = name yield
[docs] @dataclass class InMemoryObservabilitySink: """Collect span records in memory for tests and diagnostics.""" spans: list[SpanRecord] = field(default_factory=list)
[docs] @contextmanager def span(self, name: str) -> Iterator[None]: """Append one span record when the context exits.""" started = time.perf_counter() try: yield finally: duration_ms = (time.perf_counter() - started) * 1000.0 self.spans.append(SpanRecord(name=name, duration_ms=duration_ms))