feat(telemetry): add execution UUID to telemetry spans

Enhance the `operation` function to include the execution UUID in span attributes, allowing for better tracking of execution contexts. If an execution UUID is present, it is added to the span attributes unless explicitly provided. Additionally, introduce tests to verify that the execution UUID is correctly stamped from the context and that explicit UUIDs are preserved. This improves traceability in telemetry data.
This commit is contained in:
lorenzejay
2026-08-14 10:31:47 -07:00
parent 19236698ef
commit ebaf02ffac
2 changed files with 42 additions and 2 deletions

View File

@@ -21,6 +21,8 @@ from opentelemetry.trace import (
TraceFlags,
)
from crewai.execution import get_execution_uuid
_TRACER_NAME = "crewai"
@@ -70,9 +72,14 @@ def operation(
The active :class:`Span`. Callers may attach additional
attributes or events to it as the operation progresses.
"""
attrs: dict[str, Any] = dict(attributes or {})
execution_uuid = get_execution_uuid()
if execution_uuid and "crewai.execution_uuid" not in attrs:
attrs["crewai.execution_uuid"] = execution_uuid
with _tracer().start_as_current_span(
name,
attributes=attributes or {},
attributes=attrs,
links=links or [],
record_exception=False,
set_status_on_exception=False,
@@ -85,6 +92,8 @@ def operation(
span.record_exception(exc, escaped=True)
span.set_status(Status(StatusCode.ERROR, f"{type(exc).__name__}: {exc}"))
raise
else:
span.set_status(Status(StatusCode.OK))
def follows_from(

View File

@@ -210,7 +210,37 @@ class TestOperation:
finished = span_exporter.get_finished_spans()
assert [s.name for s in finished] == ["sample op"]
assert finished[0].attributes["crewai.test.key"] == "value"
assert finished[0].status.status_code == StatusCode.UNSET
assert finished[0].status.status_code == StatusCode.OK
def test_stamps_execution_uuid_from_contextvar(
self, span_exporter: InMemorySpanExporter
) -> None:
from crewai.execution import clear_execution_uuid, set_execution_uuid
token = set_execution_uuid("kickoff-uuid-1")
try:
with operation("sample op"):
pass
finally:
clear_execution_uuid(token)
finished = span_exporter.get_finished_spans()
assert finished[0].attributes["crewai.execution_uuid"] == "kickoff-uuid-1"
def test_leaves_explicit_execution_uuid_in_place(
self, span_exporter: InMemorySpanExporter
) -> None:
from crewai.execution import clear_execution_uuid, set_execution_uuid
token = set_execution_uuid("contextvar-id")
try:
with operation("sample op", {"crewai.execution_uuid": "explicit-id"}):
pass
finally:
clear_execution_uuid(token)
finished = span_exporter.get_finished_spans()
assert finished[0].attributes["crewai.execution_uuid"] == "explicit-id"
def test_exception_marks_span_error(
self, span_exporter: InMemorySpanExporter
@@ -310,6 +340,7 @@ class TestHotPathSpans:
]
assert len(crew_spans) == 1
assert crew_spans[0].attributes["crewai.crew.id"] == str(crew.id)
assert crew_spans[0].attributes.get("crewai.execution_uuid")
def test_nested_spans_share_trace_id(
self, span_exporter: InMemorySpanExporter