reflexr.telemetry¶
Tracing and metrics through the OpenTelemetry API. See Observability.
reflexr's OpenTelemetry instrumentation: spans, the metric registry and attribute names.
reflexr depends on the OpenTelemetry API only and never configures the SDK (ADR-0018). Pass
tracer and meter providers to Workspaces, or configure the global
ones, to see its spans and metrics. reflexr.telemetry.traces names the spans in reflexr's
traces, and runs polling untraced (ADR-0040).
The metric registry¶
Metric
dataclass
¶
Metric(
name: str,
instrument: Instrument,
unit: str,
description: str,
attributes: frozenset[str] = SCOPED,
scope: str = SCOPE,
buckets: tuple[float, ...] | None = None,
)
One metric: what it measures, and the attributes it may carry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The OpenTelemetry metric name. |
required |
instrument
|
Instrument
|
The instrument it is recorded with. |
required |
unit
|
str
|
The UCUM unit, or an annotation in braces such as |
required |
description
|
str
|
What it measures. |
required |
attributes
|
frozenset[str]
|
The attributes it may carry, tenant and workspace included. |
SCOPED
|
scope
|
str
|
The instrumentation scope that records it: |
SCOPE
|
buckets
|
tuple[float, ...] | None
|
For a histogram, the bucket boundaries it advises the SDK to use. |
None
|
METRICS
module-attribute
¶
METRICS: Mapping[str, Metric] = {
metric.name: metric
for metric in (
EVENTS_PUBLISHED,
EVALUATION_LAG,
EVALUATION_DURATION,
FIRINGS,
RULE_ERRORS,
RUNS,
RUN_DURATION,
RUN_ATTEMPTS,
DEAD_LETTERS,
SCHEDULE_TICKS,
FEEDBACK,
STREAM_CONNECTIONS,
STREAM_DISCONNECTS,
)
}
Every metric reflexr records, by name.
EXTERNAL_METRICS
module-attribute
¶
EXTERNAL_METRICS: Mapping[str, Metric] = {
metric.name: metric
for metric in (
Metric(
"gen_ai.client.token.usage",
"histogram",
"{token}",
"Tokens per model request, by model and type.",
frozenset(),
scope="pydantic-ai",
),
Metric(
"operation.cost",
"histogram",
"{USD}",
"Estimated cost per model request, by model.",
frozenset(),
scope="pydantic-ai",
),
)
}
Metrics recorded by others that reflexr's dashboards read, by name: pydantic-ai's, per model request of an agent action. They carry the model and token type, not reflexr's attributes.
Instrument
¶
Instrument = Literal[
"counter", "up_down_counter", "histogram", "gauge"
]
The kind of OpenTelemetry instrument a metric is recorded with.
MetricsDetail
¶
MetricsDetail = Literal['workspace', 'tenant', 'none']
How much tenancy detail metrics keep: tenant and workspace, the tenant only, or neither.
kept_attributes
¶
kept_attributes(
metric: Metric, detail: MetricsDetail
) -> frozenset[str]
Return the attributes of metric that a deployment keeps at a level of detail.
SCOPE
module-attribute
¶
SCOPE: Final = 'reflexr'
The instrumentation scope of reflexr's own spans and metrics.
SCOPED
module-attribute
¶
The tenancy attributes, which MetricsDetail limits.
Traces¶
Which spans are reflexr's, and polling that makes no traces. See ADR-0040.
TRACE_SCOPES
module-attribute
¶
TRACE_SCOPES: Final = frozenset(
{
SCOPE,
"pydantic-graph",
"mcp-python-sdk",
"opentelemetry.instrumentation.fastapi",
"opentelemetry.instrumentation.asgi",
"opentelemetry.instrumentation.sqlalchemy",
"opentelemetry.instrumentation.asyncpg",
"opentelemetry.instrumentation.httpx",
}
)
The instrumentation scopes whose spans make up reflexr's traces, besides the model calls: reflexr's own, pydantic-graph's, the MCP SDK's, and the FastAPI, ASGI, SQLAlchemy, asyncpg and httpx instrumentations'.
is_trace_scope
¶
Return whether spans of an instrumentation scope belong in reflexr's traces.
A sub-scope of one of TRACE_SCOPES, such as reflexr.workspace, does too.
untraced
¶
untraced() -> Generator[None]
Run a block untraced: every span started in it is a child of a span that is never sampled.
Under a parent-based sampler, the SDK's default, those spans are not recorded, so nothing started in the block is exported: the database queries of a poll, say. Metrics recorded in the block are recorded as usual, the instrumentations' own included. reflexr polls storage this way, in the reactor, subscriptions and the feedback mirror, so an idle application sends no traces; the work a poll finds, such as an evaluation or a run attempt, is traced where it happens.
Don't commit or publish in the block: its trace ids are the unsampled parent's, so an
envelope or a run would record a trace that does not exist. A sampler that ignores the
parent, such as always_on or traceidratio, records the block's spans again, each
poll in a trace of its own.
Attributes¶
The names of the attributes on reflexr's spans and metrics.
They live in one module because the OpenTelemetry GenAI conventions are still in development:
if a name changes, it changes here. reflexr's own names match artifactr's, with reflexr. in
place of artifactr..
DUPLICATE
module-attribute
¶
Whether a publish found its event id already in the log and appended nothing.
RUN_REASON
module-attribute
¶
A stable code for why a run attempt failed; bounded, since reasons are codes.
CHECKPOINT_REASON
module-attribute
¶
Why a run's checkpoint was not resumed: a stable code.
CHECKPOINT_ERROR
module-attribute
¶
What in a run's checkpoint did not validate, without the values.
EVALUATED_RULES
module-attribute
¶
The rules that evaluated new envelopes in an evaluation pass.
CLOSE_CODE
module-attribute
¶
How a WebSocket connection ended.
SESSION_ID
module-attribute
¶
The causal chain's correlation id. Langfuse groups a session's traces by it.
CONVERSATION_ID
module-attribute
¶
The same value under the GenAI name, which pydantic-ai sets on agent spans.
Recording¶
These are what reflexr's components record with. Applications rarely need them, except the attribute helpers to attribute spans of their own.
Telemetry
¶
Telemetry(
*,
tracer_provider: TracerProvider | None = None,
meter_provider: MeterProvider | None = None,
stored_namespaces: Set[str] = frozenset(),
)
reflexr's tracer, and an instrument for every metric in the registry.
Tenant and workspace are always recorded; a deployment that wants less detail says so to
the SDK, with reflexr.otel.metric_views.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tracer_provider
|
TracerProvider | None
|
Where spans go. Defaults to the global provider. |
None
|
meter_provider
|
MeterProvider | None
|
Where metrics go. Defaults to the global provider. |
None
|
stored_namespaces
|
Set[str]
|
The rule namespaces of stored rules, whose names code does not bound.
Metrics record a rule in one as the namespace, such as |
frozenset()
|
rule_attribute
¶
Return what metrics record as a rule's reflexr.rule.
That is its name, or for a stored rule its namespace, such as chat:*, so tenants'
rules share a series per namespace.
record
¶
record(
metric: Metric,
value: float,
*,
tenant_id: TenantId,
workspace_id: WorkspaceId,
attributes: Attributes | None = None,
) -> None
Record a measurement of a registered metric.
A rule is recorded as rule_attribute says.
Raises:
| Type | Description |
|---|---|
KeyError
|
If the metric is not in the registry. |
ValueError
|
If an attribute is not one the metric declares. |
workspace_attributes
¶
workspace_attributes(
tenant_id: TenantId, workspace_id: WorkspaceId
) -> dict[str, str]
Return the span attributes that attribute work to a workspace.
actor_attributes
¶
Return the span attributes that attribute work to an actor.
chain_attributes
¶
Return the span attributes that put a span in its causal chain's session.
current_trace_id
¶
current_trace_id() -> str | None
Return the current span's trace id as 32 hex digits, if there is a valid span.
current_traceparent
¶
current_traceparent() -> str | None
Return the W3C trace context of the current span, if there is one.
parse_traceparent
¶
parse_traceparent(
traceparent: str | None,
) -> SpanContext | None
Return the span context a W3C traceparent names, if it is valid.