Skip to content

feat: record telemetry span outcomes - #402

Open
codeforester wants to merge 2 commits into
mainfrom
enhancement/392-20260930-enhancement-record-span-status-and-exceptions-on-the-lifecyc
Open

codeforester wants to merge 2 commits into
mainfrom
enhancement/392-20260930-enhancement-record-span-status-and-exceptions-on-the-lifecyc

Conversation

@codeforester

Copy link
Copy Markdown
Contributor

Fixes #392

Summary

  • set OpenTelemetry span status from the normalized lifecycle outcome
  • record the original exception on failed invocations when available
  • cover successful and failed telemetry paths

Validation

  • UV_CACHE_DIR=/private/tmp/base-cli-uv-cache uv run --extra dev --extra telemetry pytest -q tests/test_integrations.py
  • lifecycle and attachment integration tests pass with telemetry enabled
  • Ruff and strict mypy with the Typer extra pass locally

Hosted checks are expected to run on this branch.

Comment thread lib/python/base_cli/integrations.py Outdated
}
for key, value in attributes.items():
_safe_span_call(session.span, "set_attribute", key, value)
if exception is not None:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correctness (contradicts this PR's own stated goal): finish_telemetry calls record_exception() whenever exception is not None, without checking whether the outcome actually represents a failure — so successful invocations that exit via SystemExit(0)/click.exceptions.Exit(0) (an already-tested, legitimate early-success pattern; see tests/test_app_run_metadata.py lines 245-246) still get an exception recorded on their span. In _app_core.py's wrapper, except BaseException as exc: exception = exc catches this and still passes it to finish_telemetry(..., exception=exception) in the finally block, even though outcome.status == "ok". The span ends up simultaneously "OK" and "has a recorded exception" — misleading telemetry for a perfectly successful run. Suggest gating record_exception on outcome.status != "ok", matching the precedent in _record_lifecycle_diagnostic which already branches on outcome.kind before treating something as an error.

Comment thread lib/python/base_cli/_attach.py Outdated
state.attached_completion = True

def record_exception(self, exc: BaseException) -> None:
self.exception = exc

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correctness: same defect as the _app_core.py/integrations.py path, reproduced in the attached-lifecycle path. record_exception unconditionally sets self.exception = exc for every ctx.exit(code) call, including code=0 — so a normal, successful early exit from an attached Click tree also gets a fabricated exception recorded on its telemetry span once self.exception reaches finish_telemetry (see the exception=self.exception call at line 244 in _finalize).


with tempfile.TemporaryDirectory() as tmpdir:
result = invoke(app, [], home=Path(tmpdir))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Test gap: this new test only exercises the failure path (asserts one recorded exception on a raised RuntimeError) and never asserts that a successful invocation leaves tracer.span.exceptions empty. So this PR's own test suite can't catch the unconditional-record_exception bug flagged on integrations.py/_attach.py — the exact regression this PR introduces for ctx.exit(0)/SystemExit(0) exits would pass this test unchanged, since nothing here asserts zero exceptions on a success run.

This branch has not been deployed

No deployments
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.

enhancement: record span status and exceptions on the lifecycle telemetry span

1 participant