Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions packages/uipath-platform/src/uipath/platform/common/_span_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,26 @@ def otel_span_to_uipath_span(
]
attributes_dict["links"] = links_list

# Add process context attributes from environment variables
for env_key, attr_key in (
("UIPATH_PROCESS_UUID", "codedAgentId"),
("UIPATH_PROCESS_KEY", "codedAgentName"),
("UIPATH_PROCESS_VERSION", "codedAgentVersion"),
):
value = env.get(env_key)
if value:
attributes_dict[attr_key] = value

# Add custom trace attributes from UIPATH_TRACE_ATTR__ prefixed env vars
# Follows ASP.NET Core convention: env vars override config using __
_TRACE_ATTR_PREFIX = "UIPATH_TRACE_ATTR__"
_prefix_len = len(_TRACE_ATTR_PREFIX)
for key, value in env.items():
if key.startswith(_TRACE_ATTR_PREFIX):
attr_name = key[_prefix_len:]
if attr_name:
attributes_dict[attr_name] = value

span_type_value = attributes_dict.get("span_type", "OpenTelemetry")
span_type = str(span_type_value)

Expand Down
81 changes: 80 additions & 1 deletion packages/uipath-platform/tests/services/test_span_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ class TestSpanUtils:
"UIPATH_ORGANIZATION_ID": "test-org",
"UIPATH_TENANT_ID": "test-tenant",
"UIPATH_FOLDER_KEY": "test-folder",
"UIPATH_PROCESS_UUID": "test-process",
"UIPATH_PROCESS_UUID": "test-process-uuid",
"UIPATH_PROCESS_KEY": "test-process-key",
"UIPATH_PROCESS_VERSION": "1.2.3",
"UIPATH_JOB_KEY": "test-job",
},
)
Expand Down Expand Up @@ -122,6 +124,11 @@ def test_otel_span_to_uipath_span(self):
assert attributes["key1"] == "value1"
assert attributes["key2"] == 123

# Verify coded agent attributes from env vars
assert attributes["codedAgentId"] == "test-process-uuid"
assert attributes["codedAgentName"] == "test-process-key"
assert attributes["codedAgentVersion"] == "1.2.3"

# Test with error status
mock_span.status.description = "Test error description"
mock_span.status.status_code = StatusCode.ERROR
Expand Down Expand Up @@ -427,3 +434,75 @@ def test_uipath_span_source_override_with_uipath_source(self):
# String source still in Attributes JSON
attrs = json.loads(span_dict["Attributes"])
assert attrs["source"] == "runtime"

@patch.dict(
os.environ,
{
"UIPATH_ORGANIZATION_ID": "test-org",
"UIPATH_TRACE_ATTR__CUSTOMERID": "cust-123",
"UIPATH_TRACE_ATTR__ENVIRONMENT": "staging",
"UIPATH_TRACE_ATTR__TEAM": "platform",
},
)
def test_custom_trace_attributes_from_env(self):
"""Test that UIPATH_TRACE_ATTR__ prefixed env vars are added to attributes."""
mock_span = Mock(spec=OTelSpan)

trace_id = 0x123456789ABCDEF0123456789ABCDEF0
span_id = 0x0123456789ABCDEF
mock_context = SpanContext(trace_id=trace_id, span_id=span_id, is_remote=False)
mock_span.get_span_context.return_value = mock_context

mock_span.name = "test-span"
mock_span.parent = None
mock_span.status.status_code = StatusCode.OK
mock_span.attributes = {"existing": "value"}
mock_span.events = []
mock_span.links = []

current_time_ns = int(datetime.now().timestamp() * 1e9)
mock_span.start_time = current_time_ns
mock_span.end_time = current_time_ns + 1000000

uipath_span = _SpanUtils.otel_span_to_uipath_span(mock_span)
attrs = json.loads(uipath_span.attributes)

# On Windows, env var keys are uppercased; on Linux they preserve case.
# Use case-insensitive lookup to make the test cross-platform.
attrs_lower = {k.lower(): v for k, v in attrs.items()}
assert attrs_lower["customerid"] == "cust-123"
assert attrs_lower["environment"] == "staging"
assert attrs_lower["team"] == "platform"
assert attrs["existing"] == "value"

@patch.dict(
os.environ,
{
"UIPATH_ORGANIZATION_ID": "test-org",
"UIPATH_TRACE_ATTR__": "empty-key-ignored",
},
)
def test_custom_trace_attributes_ignores_empty_key(self):
"""Test that UIPATH_TRACE_ATTR__ with no suffix is ignored."""
mock_span = Mock(spec=OTelSpan)

trace_id = 0x123456789ABCDEF0123456789ABCDEF0
span_id = 0x0123456789ABCDEF
mock_context = SpanContext(trace_id=trace_id, span_id=span_id, is_remote=False)
mock_span.get_span_context.return_value = mock_context

mock_span.name = "test-span"
mock_span.parent = None
mock_span.status.status_code = StatusCode.OK
mock_span.attributes = {}
mock_span.events = []
mock_span.links = []

current_time_ns = int(datetime.now().timestamp() * 1e9)
mock_span.start_time = current_time_ns
mock_span.end_time = current_time_ns + 1000000

uipath_span = _SpanUtils.otel_span_to_uipath_span(mock_span)
attrs = json.loads(uipath_span.attributes)

assert "" not in attrs
Loading