Skip to content

Adopt proposal 0121: stable event names on emitted diagnostics - #295

Open
chris-colinsky wants to merge 3 commits into
mainfrom
feature/0121-diagnostic-event-names
Open

Adopt proposal 0121: stable event names on emitted diagnostics#295
chris-colinsky wants to merge 3 commits into
mainfrom
feature/0121-diagnostic-event-names

Conversation

@chris-colinsky

Copy link
Copy Markdown
Member

Implements proposal 0121, accepted at spec v0.115.0. Two halves: stable event names on the diagnostics openarmature emits, and the otel_observer fixture directive.

This one closes a gap we reported ourselves. Review item #11 in the v0.17.0 batched review was that expected.log_records could not pin the WARNING it existed to pin: entries declare only a level, so an adapter satisfies them by emitting any warning. We found it by mutation, downgrading the mandated emitter and watching fixture 158 stay green because an unrelated cached-client notice fired on the same logger.

The event names

A log record signalling a condition the spec defines now carries a stable name in the openarmature. namespace. Three are emitted:

Event name Condition Obligation
openarmature.langfuse.payload_suppressed §6, the client's provider binding cannot be established, so every harvested-payload channel is suppressed MUST
openarmature.langfuse.shared_provider_accepted §6, the caller accepted a shared provider and openarmature proceeds MUST
openarmature.token_budget.exceeded §5.5.15, an active prompt's budget is exceeded SHOULD

The fourth name 0121 defines, openarmature.langfuse.supplied_client_shared_provider, has no emitter here. It is a MAY, and §6 mode (a) leaves a caller-supplied client's provider the caller's responsibility rather than something we inspect. The obligation is on the record: where one is emitted it carries its name, and a diagnostic we choose not to emit needs none.

Names are stable identifiers and will not be reworded once shipped. A record's human-readable message is free to change independently.

Getting the name onto the OTel field takes work

§7 puts the name on the OTel LogRecord's event_name field. Neither OTel logging handler populates it. Both map a stdlib record's attributes into the LogRecord's attributes and leave the field unset, so a name passed through extra= arrives as an attribute and the field stays empty.

install_log_bridge now installs a handler that lifts it. That reaches past _translate, the handlers' own private surface, deliberately: it is the only seam where a stdlib record becomes an OTel one. It degrades rather than breaks if that surface moves, falling back to the pre-0121 attributes-only shape, and a test pins the field so an OTel upgrade that breaks the lift fails loudly instead of silently reverting us.

Worth stating plainly because the distinction is easy to lose: an attributes-only implementation satisfies a fixture reading the stdlib record while leaving an OTel consumer unable to filter on the name at all.

The otel_observer directive

0121 moves the OTel observer's construction knobs under an otel_observer: block; 12 fixtures carry the new spelling at v0.118.0 while the pinned copy still uses the bare case-level key. The harness reads both, with the directive winning where both appear, so a half-migrated fixture cannot resolve to the stale value. Without that, a fixture that moved to the directive would silently fall back to the observer default (payloads off) and a case asserting a payload is absent would keep passing for the wrong reason.

Retiring our own workaround

Our harness had narrowed the level-only match by discriminating on the emitting call site (_apply_isolation_policy). That worked but is not portable to another implementation, which is exactly what we told spec. It now matches on the event name where a fixture declares one, and keeps the call-site match only as a fallback for a fixture that does not.

Testing

7 mutants, all killed: each of the three event names dropped individually, the bridge's lift removed, the harness ignoring the directive, and the directive losing to the bare key.

One is worth calling out. My first token-budget test logged the record itself rather than driving the emitter, so it passed with the name dropped entirely. The mutant caught it; the test now drives a real over-budget event through the observer.

Ahead of the pin

Spec v0.115.0 is beyond the current v0.112.0 pin, so this ships unit-tested. The conformance.toml entry, fixture 158's event_name assertions, and the 12 fixtures carrying the directive all ride the pin bump.

Proposal 0121. A log record signalling a condition the spec defines
carries a stable name in the openarmature namespace, on the OTel
LogRecord's event_name field.

Three names are emitted: the two section 6 isolation decisions and a
section 5.5.15 token-budget breach. The fourth 0121 defines is a MAY for
a caller-supplied client, which mode (a) leaves the caller's
responsibility rather than inspecting, so nothing emits it.

Neither OTel logging handler populates the field. Both map a stdlib
record's attributes into the LogRecord's attributes and leave event_name
unset, so a name passed through extra= arrives as an attribute and the
field stays empty. install_log_bridge now lifts it, reaching past a
private surface deliberately and degrading to the attributes-only shape
rather than breaking if that surface moves.

This is what the names are for: a log_records fixture entry declaring
only a severity is satisfied by any record at that level, so silencing a
mandated emitter left a fixture green when an unrelated warning fired on
the same logger. Our harness had narrowed that by matching the emitting
call site, which no other implementation can do. It now matches the name
where a fixture declares one.

The harness also reads the otel_observer directive carrying the OTel
observer's construction knobs, preferring it over the bare case-level
key so either spelling resolves the same way across the pin bump.
Copilot AI lite review requested due to automatic review settings September 9, 2026 19:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

install_log_bridge’s new _EventNameHandler override can crash rather than degrade if OTel removes/renames the private _translate method, so the fallback logic needs to be made real.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Implements spec proposal 0121 by introducing stable diagnostic event names for key warnings, ensuring those names propagate to the OTel LogRecord.event_name field, and updating the conformance harness to support the new otel_observer: fixture directive plus event_name-based log matching.

Changes:

  • Add a central observability.diagnostics module that defines stable event-name constants plus helpers for tagging (extra=) and reading names from logging.LogRecord.
  • Tag the relevant diagnostics (Langfuse isolation warnings, token budget exceedance) and update the OTel logs bridge to lift the stdlib event_name attribute onto the OTel LogRecord.event_name field.
  • Update conformance and unit tests to assert event-name behavior and to parse/precedence-resolve the new otel_observer: directive (while remaining compatible with the pre-0121 bare keys).
File summaries
File Description
src/openarmature/observability/diagnostics.py Introduces stable diagnostic event-name constants and helper functions (diagnostic, event_name_of).
src/openarmature/observability/otel/logs.py Wraps the installed OTel logging handler to lift event_name onto the OTel LogRecord field.
src/openarmature/observability/otel/observer.py Tags the token-budget warning with the stable TOKEN_BUDGET_EXCEEDED event name.
src/openarmature/observability/langfuse/observer.py Tags the two Langfuse isolation-decision warnings with stable event names.
tests/unit/test_observability_otel.py Adds unit coverage for the OTel log bridge “field lift” and the token-budget diagnostic name.
tests/unit/test_observability_langfuse.py Adds unit coverage that the mandated isolation diagnostics carry the correct event names.
tests/conformance/test_observability.py Adds _observer_kwargs_for_case to support the new otel_observer: directive with correct precedence over bare keys.
tests/conformance/test_observability_langfuse.py Updates log_records matching to prefer event_name discrimination when declared, with call-site fallback.
tests/conformance/test_fixture_parsing.py Adds focused tests for directive vs bare-key equivalence/precedence and for event_name discrimination behavior.
CHANGELOG.md Documents proposal 0121 adoption, including the event names and the OTel field-lift rationale.
Review details
  • Files reviewed: 10/10 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/openarmature/observability/otel/logs.py
The harness branch that reads a fixture's event_name is unreachable at
the current pin: no fixture at v0.112.0 declares one, and the six that
do arrive at v0.118.0. Mutation confirmed it, forcing the branch never
to fire left the whole suite green.

Extracted as match_expected_log_record and driven directly, so a
regression to level-only matching, which is the looseness 0121 closes,
fails now rather than at the pin bump.
The comment claimed the bridge degrades rather than breaks if OTel's
private _translate moves. It did not: super()._translate(...) inside an
override raises AttributeError once the method is gone, which breaks
logging instead of falling back.

The subclass is now built only while the method exists, and the upstream
handler is used unchanged otherwise, which is the attributes-only shape
that predates the lift. A test drives the missing-method path, so the
degradation is pinned rather than asserted in prose.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants