Apply the specified status precedence in Span.set_status - #5662
WatchTree-19 wants to merge 3 commits into
Conversation
Fixes open-telemetry#5661. set_status returns early when the span is already OK and when the incoming status is UNSET, but nothing stops a second ERROR replacing a first one. A bare Status(StatusCode.ERROR) therefore overwrites Status(StatusCode.ERROR, "...") and the description is gone. The status code is unchanged, so the loss is silent. The assignment is now declined when the incoming status carries no description, the recorded one does, and the codes match. A described status still replaces a described status, a bare status still lands when there is nothing to keep, and a different status code still takes precedence as before. Assisted-by: Claude Opus 5 Signed-off-by: WatchTree-19 <119982314+WatchTree-19@users.noreply.github.com>
Assisted-by: Claude Opus 5 Signed-off-by: WatchTree-19 <119982314+WatchTree-19@users.noreply.github.com>
|
We should probably update this to be more in line with what the spec expects: Namely that Span statuses form a total order |
The specification gives the status codes a total order, Ok > Error > Unset, says an attempt to set Unset should be ignored, and says the value of the last call is the one recorded. The SDK already behaved that way, but the rule was spread across two ad-hoc conditions and one of them relied on Python's and/or precedence to read correctly, so it was neither obvious nor covered by tests. State the order explicitly in _STATUS_PRECEDENCE and move the decision into _accepts_status, then pin every transition in the lattice with a test. The enum's own values are Unset=0, Ok=1, Error=2, which is not the specified order, so the map cannot be replaced by a comparison on the enum. Within a single status code the last call still wins, with one exception kept from the original fix: a bare status carries no new information, so letting it through would drop a description already recorded in favour of nothing.
|
Thanks for laying the ordering out, I went through each point against the spec. Three of the four are in there: the total order I also wrote all twelve transitions up as tests and ran them against upstream What I've pushed now writes the order out explicitly and puts the decision in one method. It adds the twelve tests and keeps last call wins within the same rank, along with the exception for bare status. One thing worth mentioning: the enum is The main thing I wanted to check with you: should |
Shriprasad-P
left a comment
There was a problem hiding this comment.
Nice focused PR. Please make sure docs/changelog stay aligned if the repo requires it for this kind of change.
Description
Fixes #5661.
Span.set_statusguards two cases when it is handed aStatusinstance: it returns early if the span is alreadyOK, and it returns early if the incoming status isUNSET. Nothing guards a secondERRORoverwriting a first one, soself._status = statusruns unconditionally and a bareStatus(StatusCode.ERROR)with no description silently replacesStatus(StatusCode.ERROR, "..."). The status code does not change, so nothing looks wrong; only the description disappears.This is reachable without anyone doing anything odd. A library sets a specific error on a span, and later generic error handling, exception bookkeeping or an instrumentation helper sets a plain
ERRORon the same span. The specific message is the one that had diagnostic value, and it is the one that is lost.The assignment is now declined when the incoming status carries no description, the recorded one does, and the codes match. Nothing else changes: a described status still replaces a described status, a bare status still lands when there is nothing to keep, and a different status code still takes precedence as before.
Type of change
How Has This Been Tested?
opentelemetry-sdk/tests/trace/test_trace.pypasses in full: 118 passed, 9 subtests passed.Four cases were added to
TestSpan, all exercised through the publicset_status:test_bare_status_does_not_drop_existing_descriptionis the regression test. It fails on an unpatched tree withAssertionError: None != 'connection refused to db-1'.test_described_status_still_replaces_described_statuspins that a real re-description is unaffected.test_bare_status_lands_when_there_is_no_description_to_keeppins that the guard does not block a first bareERROR.test_bare_status_of_a_different_code_still_landspins that the guard is per status code.The last three pass with and without the fix, by design. Only the first discriminates.
The behaviour was also reproduced against released
opentelemetry-sdk1.44.0 on CPython 3.11 before anything was changed, using the snippet in #5661.Does This PR Require a Contrib Repo Change?
Checklist:
On the changelog:
.changelog/names fragments after the PR number, which does not exist until this is opened, so<pr>.fixedfollows as a second commit straight after.AI assistance was used and is disclosed on the commit with an
Assisted-by:trailer, per AGENTS.md.