Repository navigation
Conversation
chrikrah
left a comment
There was a problem hiding this comment.
@lumman0 approving at 9d0ba8d. Your base/head split reproduces on Python 3.14.
$ python -m pytest tests/unit/test_otel.py -q -p no:cacheprovider # opentelemetry-sdk 1.45.0
# head 9d0ba8d
73 passed, 2 skipped
# head tests, client.py from base f6b7040
8 failed, 65 passed, 2 skipped
non-blocking question: neither path emits an exception event on cancellation, and @observe emits none even for a RuntimeError; only the context manager records one, through use_span, for Exception subclasses. Probe at head, saved as tests/unit/exc_probe.py:
import asyncio, pytest
from langfuse import observe
@pytest.mark.parametrize("exc", [RuntimeError("boom"), asyncio.CancelledError("stop"), KeyboardInterrupt("ki")])
@pytest.mark.parametrize("path", ["cm", "observe"])
def test_probe(langfuse_memory_client, get_span, exc, path):
def body():
raise exc
with pytest.raises(type(exc)):
if path == "cm":
with langfuse_memory_client.start_as_current_observation(name="obs"):
body()
else:
observe(name="obs")(body)()
langfuse_memory_client.flush()
s = get_span("obs")
print(f"\n{path:8} {type(exc).__name__:18} status={s.status.status_code.name} events={[e.name for e in s.events]}")$ python -m pytest tests/unit/exc_probe.py -q -s -p no:cacheprovider # head 9d0ba8d
cm RuntimeError status=ERROR events=['exception']
cm CancelledError status=ERROR events=[]
cm KeyboardInterrupt status=UNSET events=[]
observe RuntimeError status=ERROR events=[]
observe CancelledError status=ERROR events=[]
observe KeyboardInterrupt status=UNSET events=[]
6 passed, 4 warnings in 0.16s
If you want the event for cancellation, otel_span.record_exception(error) before the update adds it here, and the decorator would need the same. Matching @observe as it stands is also a fine answer.
@hassiebp could you review this one?
|
@chrikrah Thanks for validating this! This PR keeps cancellation handling consistent with |
What does this PR do?
Record
asyncio.CancelledErrorescapingstart_as_current_observationas an observation error, matching the existing@observebehavior introduced in #1566.asyncio.CancelledErrorinherits fromBaseException, so the underlying OpenTelemetry context manager's ordinary exception handling does not mark the span as an error. Before this change, cancelling a task inside the native observation context manager ends the observation without cancellation diagnostics, while an equivalent@observecall recordsERRORandCancelledError.This can occur when a service cancels an in-flight operation during shutdown, or when
asyncio.wait_forcancels an operation on timeout.The shared context manager now:
level="ERROR"and the cancellation message, falling back toCancelledErrorwhen the message is empty.@observehas already recorded and ended it.end_on_exit=False, span parenting, and context cleanup.The public method docstring documents the behavior. Eight regression cases cover span/generation observations, actual task cancellation with and without a message, timeout cancellation, context restoration, and manual ending. All eight failed against the unchanged base and pass with this fix.
Related work: #1566 fixed the decorator path. #1868 addresses helper-thread exception propagation, and #1893 addresses generator wrappers in
observe.py; neither changes this native context-manager path. This PR follows the existing decorator's error classification for cancellation.Type of change
Verification
Tested on Windows with Python 3.12.14, SDK base
f6b7040(v4.17.0), and OpenTelemetry SDK 1.45.0.Also verified with synthetic calls sent through real OTLP ingestion to a local self-hosted Langfuse v4.47.0 instance, then queried the persisted observations using the v2 observations API:
DEFAULT, no status messageERROR,CancelledError@observe, cancelled taskERROR,CancelledErrorERROR,CancelledErrorValueErrorcontrolERROR,ValueError: synthetic failureERROR,ValueError: synthetic failureThe full unit suite, full repository e2e suite, and live-provider suite were not run. Verification focused on the affected tracing/decorator/resource-manager/propagation paths plus the local persisted-data reproduction; no real model calls are needed for cancellation handling. Other Python versions remain for CI.
Checklist
code_review.md..env.templateif needed.This PR appears safe to merge; no actionable issues were found.
What we checked:
end()before leaving the context. The new handler checksis_recording()and skips the ended span.Summary
Records
asyncio.CancelledErrorescaping an observation context asERROR, then re-raises it.CancelledErroras the fallback.end_on_exit=False.No actionable issues were found. Tests were inspected but not run.
lumman0 explicitly described updating only recording spans as intentional.
Reviews (1) · Last reviewed commit: "fix(tracing): record cancellation in obs..."