fix(observability): emit user_id as span attribute in SpanExporter - #2152
fix(observability): emit user_id as span attribute in SpanExporter#2152Christian-Sidak wants to merge 1 commit into
Conversation
ContextState.user_id was stored but never read by the span exporter. Add nat.user.id to the initial attributes dict and set user.id on each span (mirroring the existing session.id/conversation_id pattern) so Langfuse and other OTLP backends can attribute traces to the correct user. Fixes NVIDIA#2151 Signed-off-by: Christian-Sidak <61099993+Christian-Sidak@users.noreply.github.com>
WalkthroughThe span exporter now records ChangesSpan user attribution
Estimated code review effort: 1 (Trivial) | ~5 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/nvidia_nat_core/tests/nat/observability/exporter/test_span_exporter.py (1)
665-665: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winQuote code entities in the new docstrings.
Wrap
user_id,ContextState,nat.user.id, anduser.idin backticks. This prevents Vale false positives and keeps the new documentation consistent with repository rules.As per coding guidelines, surround code entities with backticks to avoid Vale false positives in docstrings.
Proposed docstring fix
- """user_id from ContextState is emitted as nat.user.id and user.id span attributes.""" + """Emit `user_id` from `ContextState` as `nat.user.id` and `user.id` span attributes.""" ... - """When user_id is not set, user.id is not added as a span attribute.""" + """Verify that `user.id` is absent when `user_id` is not set."""Also applies to: 687-687
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/nvidia_nat_core/tests/nat/observability/exporter/test_span_exporter.py` at line 665, Update the new docstrings in the span exporter tests around the user ID assertions to wrap the code entities `user_id`, `ContextState`, `nat.user.id`, and `user.id` in backticks, including the corresponding docstring at the additionally referenced location.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/nvidia_nat_core/tests/nat/observability/exporter/test_span_exporter.py`:
- Around line 664-684: Update test_user_id_emitted_as_span_attribute and the
related test at lines 686-706 so assertions derive the attribute key from
span_exporter’s configured prefix rather than hard-coding "nat.user.id", or
configure the fixture exporter with span_prefix="nat". Keep the user.id
assertion unchanged.
---
Nitpick comments:
In
`@packages/nvidia_nat_core/tests/nat/observability/exporter/test_span_exporter.py`:
- Line 665: Update the new docstrings in the span exporter tests around the user
ID assertions to wrap the code entities `user_id`, `ContextState`,
`nat.user.id`, and `user.id` in backticks, including the corresponding docstring
at the additionally referenced location.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: bb2f4151-ba02-45c8-9f0d-d48ce81ba6dd
📒 Files selected for processing (2)
packages/nvidia_nat_core/src/nat/observability/exporter/span_exporter.pypackages/nvidia_nat_core/tests/nat/observability/exporter/test_span_exporter.py
| def test_user_id_emitted_as_span_attribute(self, span_exporter): | ||
| """user_id from ContextState is emitted as nat.user.id and user.id span attributes.""" | ||
| from nat.builder.context import ContextState | ||
|
|
||
| ctx_state = ContextState.get() | ||
| token = ctx_state.user_id.set("USER-42") | ||
| try: | ||
| event = create_intermediate_step(event_type=IntermediateStepType.LLM_START, | ||
| framework=LLMFrameworkEnum.LANGCHAIN, | ||
| name="test_llm", | ||
| event_timestamp=datetime.now().timestamp(), | ||
| data=StreamEventData(input="hi"), | ||
| metadata={}) | ||
|
|
||
| span_exporter.export(event) | ||
| span = span_exporter._outstanding_spans[event.payload.UUID] | ||
|
|
||
| assert span.attributes.get("nat.user.id") == "USER-42" | ||
| assert span.attributes.get("user.id") == "USER-42" | ||
| finally: | ||
| ctx_state.user_id.reset(token) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Make the user-attribute assertions independent of NAT_SPAN_PREFIX.
The span_exporter fixture creates ConcreteSpanExporter() without a fixed span_prefix. The exporter then uses NAT_SPAN_PREFIX when it is set, but both tests require the literal nat.user.id. A non-default environment value makes these tests fail.
Use the exporter’s configured prefix or create the exporter with span_prefix="nat".
Proposed assertion fix
- assert span.attributes.get("nat.user.id") == "USER-42"
+ assert span.attributes.get(f"{span_exporter._span_prefix}.user.id") == "USER-42"
...
- assert span.attributes.get("nat.user.id") == "unknown"
+ assert span.attributes.get(f"{span_exporter._span_prefix}.user.id") == "unknown"Also applies to: 686-706
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/nvidia_nat_core/tests/nat/observability/exporter/test_span_exporter.py`
around lines 664 - 684, Update test_user_id_emitted_as_span_attribute and the
related test at lines 686-706 so assertions derive the attribute key from
span_exporter’s configured prefix rather than hard-coding "nat.user.id", or
configure the fixture exporter with span_prefix="nat". Keep the user.id
assertion unchanged.
Summary
ContextState.user_idwas resolved and stored by the runtime but never read bySpanExporter._process_start_event, so no span ever carried user identity.nat.user.idto the initialattributesdict alongsidenat.conversation.id.user.idon each span after creation, mirroring the existingsession.id/conversation_idpattern, so Langfuse and other OTLP backends can attribute traces to the correct user.Fixes #2151
Test plan
test_user_id_emitted_as_span_attribute: setsContextState.user_idto"USER-42", exports a span, and asserts bothnat.user.idanduser.idattributes equal"USER-42".test_user_id_absent_when_not_set: setsContextState.user_idtoNone, exports a span, and assertsnat.user.idis"unknown"anduser.idis absent (matching thesession.idbehavior).test_span_exporter.pycontinues to pass (39 pass, 6 pre-existing fixture errors unchanged).Summary by CodeRabbit
New Features
Tests