Skip to content

Commit 5d3a728

Browse files
authored
feat(client): allow propagating trace name (#1478)
1 parent 9d67ded commit 5d3a728

File tree

2 files changed

+234
-0
lines changed

2 files changed

+234
-0
lines changed

langfuse/_client/propagation.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"metadata",
3333
"version",
3434
"tags",
35+
"trace_name",
3536
]
3637

3738
InternalPropagatedKeys = Literal[
@@ -50,6 +51,7 @@
5051
"metadata",
5152
"version",
5253
"tags",
54+
"trace_name",
5355
"experiment_id",
5456
"experiment_name",
5557
"experiment_metadata",
@@ -77,6 +79,7 @@ def propagate_attributes(
7779
metadata: Optional[Dict[str, str]] = None,
7880
version: Optional[str] = None,
7981
tags: Optional[List[str]] = None,
82+
trace_name: Optional[str] = None,
8083
as_baggage: bool = False,
8184
) -> _AgnosticContextManager[Any]:
8285
"""Propagate trace-level attributes to all spans created within this context.
@@ -109,6 +112,8 @@ def propagate_attributes(
109112
- AVOID: large payloads, sensitive data, non-string values (will be dropped with warning)
110113
version: Version identfier for parts of your application that are independently versioned, e.g. agents
111114
tags: List of tags to categorize the group of observations
115+
trace_name: Name to assign to the trace. Must be US-ASCII string, ≤200 characters.
116+
Use this to set a consistent trace name for all spans created within this context.
112117
as_baggage: If True, propagates attributes using OpenTelemetry baggage for
113118
cross-process/service propagation. **Security warning**: When enabled,
114119
attribute values are added to HTTP headers on ALL outbound requests.
@@ -195,6 +200,7 @@ def propagate_attributes(
195200
metadata=metadata,
196201
version=version,
197202
tags=tags,
203+
trace_name=trace_name,
198204
as_baggage=as_baggage,
199205
)
200206

@@ -207,6 +213,7 @@ def _propagate_attributes(
207213
metadata: Optional[Dict[str, str]] = None,
208214
version: Optional[str] = None,
209215
tags: Optional[List[str]] = None,
216+
trace_name: Optional[str] = None,
210217
as_baggage: bool = False,
211218
experiment: Optional[PropagatedExperimentAttributes] = None,
212219
) -> Generator[Any, Any, Any]:
@@ -218,6 +225,7 @@ def _propagate_attributes(
218225
"session_id": session_id,
219226
"version": version,
220227
"tags": tags,
228+
"trace_name": trace_name,
221229
}
222230

223231
propagated_string_attributes = propagated_string_attributes | (
@@ -456,6 +464,7 @@ def _get_propagated_span_key(key: str) -> str:
456464
"user_id": LangfuseOtelSpanAttributes.TRACE_USER_ID,
457465
"version": LangfuseOtelSpanAttributes.VERSION,
458466
"tags": LangfuseOtelSpanAttributes.TRACE_TAGS,
467+
"trace_name": LangfuseOtelSpanAttributes.TRACE_NAME,
459468
"experiment_id": LangfuseOtelSpanAttributes.EXPERIMENT_ID,
460469
"experiment_name": LangfuseOtelSpanAttributes.EXPERIMENT_NAME,
461470
"experiment_metadata": LangfuseOtelSpanAttributes.EXPERIMENT_METADATA,

tests/test_propagate_attributes.py

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2769,3 +2769,228 @@ def task_with_child(*, item, **kwargs):
27692769
LangfuseOtelSpanAttributes.ENVIRONMENT,
27702770
LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT,
27712771
)
2772+
2773+
2774+
class TestPropagateAttributesTraceName(TestPropagateAttributesBase):
2775+
"""Tests for trace_name parameter propagation."""
2776+
2777+
def test_trace_name_propagates_to_child_spans(
2778+
self, langfuse_client, memory_exporter
2779+
):
2780+
"""Verify trace_name propagates to all child spans within context."""
2781+
with langfuse_client.start_as_current_span(name="parent-span"):
2782+
with propagate_attributes(trace_name="my-trace-name"):
2783+
child1 = langfuse_client.start_span(name="child-span-1")
2784+
child1.end()
2785+
2786+
child2 = langfuse_client.start_span(name="child-span-2")
2787+
child2.end()
2788+
2789+
# Verify both children have trace_name
2790+
child1_span = self.get_span_by_name(memory_exporter, "child-span-1")
2791+
self.verify_span_attribute(
2792+
child1_span,
2793+
LangfuseOtelSpanAttributes.TRACE_NAME,
2794+
"my-trace-name",
2795+
)
2796+
2797+
child2_span = self.get_span_by_name(memory_exporter, "child-span-2")
2798+
self.verify_span_attribute(
2799+
child2_span,
2800+
LangfuseOtelSpanAttributes.TRACE_NAME,
2801+
"my-trace-name",
2802+
)
2803+
2804+
def test_trace_name_propagates_to_grandchildren(
2805+
self, langfuse_client, memory_exporter
2806+
):
2807+
"""Verify trace_name propagates through multiple levels of nesting."""
2808+
with langfuse_client.start_as_current_span(name="parent-span"):
2809+
with propagate_attributes(trace_name="nested-trace"):
2810+
with langfuse_client.start_as_current_span(name="child-span"):
2811+
grandchild = langfuse_client.start_span(name="grandchild-span")
2812+
grandchild.end()
2813+
2814+
# Verify all three levels have trace_name
2815+
parent_span = self.get_span_by_name(memory_exporter, "parent-span")
2816+
child_span = self.get_span_by_name(memory_exporter, "child-span")
2817+
grandchild_span = self.get_span_by_name(memory_exporter, "grandchild-span")
2818+
2819+
for span in [parent_span, child_span, grandchild_span]:
2820+
self.verify_span_attribute(
2821+
span, LangfuseOtelSpanAttributes.TRACE_NAME, "nested-trace"
2822+
)
2823+
2824+
def test_trace_name_with_user_and_session(self, langfuse_client, memory_exporter):
2825+
"""Verify trace_name works together with user_id and session_id."""
2826+
with langfuse_client.start_as_current_span(name="parent-span"):
2827+
with propagate_attributes(
2828+
user_id="user_123",
2829+
session_id="session_abc",
2830+
trace_name="combined-trace",
2831+
):
2832+
child = langfuse_client.start_span(name="child-span")
2833+
child.end()
2834+
2835+
# Verify child has all attributes
2836+
child_span = self.get_span_by_name(memory_exporter, "child-span")
2837+
self.verify_span_attribute(
2838+
child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_123"
2839+
)
2840+
self.verify_span_attribute(
2841+
child_span, LangfuseOtelSpanAttributes.TRACE_SESSION_ID, "session_abc"
2842+
)
2843+
self.verify_span_attribute(
2844+
child_span, LangfuseOtelSpanAttributes.TRACE_NAME, "combined-trace"
2845+
)
2846+
2847+
def test_trace_name_with_version(self, langfuse_client, memory_exporter):
2848+
"""Verify trace_name works together with version."""
2849+
with langfuse_client.start_as_current_span(name="parent-span"):
2850+
with propagate_attributes(
2851+
trace_name="versioned-trace",
2852+
version="1.0.0",
2853+
):
2854+
child = langfuse_client.start_span(name="child-span")
2855+
child.end()
2856+
2857+
child_span = self.get_span_by_name(memory_exporter, "child-span")
2858+
self.verify_span_attribute(
2859+
child_span, LangfuseOtelSpanAttributes.TRACE_NAME, "versioned-trace"
2860+
)
2861+
self.verify_span_attribute(
2862+
child_span, LangfuseOtelSpanAttributes.VERSION, "1.0.0"
2863+
)
2864+
2865+
def test_trace_name_with_metadata(self, langfuse_client, memory_exporter):
2866+
"""Verify trace_name works together with metadata."""
2867+
with langfuse_client.start_as_current_span(name="parent-span"):
2868+
with propagate_attributes(
2869+
trace_name="metadata-trace",
2870+
metadata={"env": "production", "region": "us-east"},
2871+
):
2872+
child = langfuse_client.start_span(name="child-span")
2873+
child.end()
2874+
2875+
child_span = self.get_span_by_name(memory_exporter, "child-span")
2876+
self.verify_span_attribute(
2877+
child_span, LangfuseOtelSpanAttributes.TRACE_NAME, "metadata-trace"
2878+
)
2879+
self.verify_span_attribute(
2880+
child_span,
2881+
f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.env",
2882+
"production",
2883+
)
2884+
self.verify_span_attribute(
2885+
child_span,
2886+
f"{LangfuseOtelSpanAttributes.TRACE_METADATA}.region",
2887+
"us-east",
2888+
)
2889+
2890+
def test_trace_name_validation_over_200_chars(
2891+
self, langfuse_client, memory_exporter
2892+
):
2893+
"""Verify trace_name over 200 characters is dropped with warning."""
2894+
long_name = "trace-" + "a" * 200 # Create a very long trace name
2895+
2896+
with langfuse_client.start_as_current_span(name="parent-span"):
2897+
with propagate_attributes(trace_name=long_name):
2898+
child = langfuse_client.start_span(name="child-span")
2899+
child.end()
2900+
2901+
# Verify child does NOT have trace_name
2902+
child_span = self.get_span_by_name(memory_exporter, "child-span")
2903+
self.verify_missing_attribute(child_span, LangfuseOtelSpanAttributes.TRACE_NAME)
2904+
2905+
def test_trace_name_exactly_200_chars(self, langfuse_client, memory_exporter):
2906+
"""Verify exactly 200 character trace_name is accepted."""
2907+
trace_name_200 = "t" * 200
2908+
2909+
with langfuse_client.start_as_current_span(name="parent-span"):
2910+
with propagate_attributes(trace_name=trace_name_200):
2911+
child = langfuse_client.start_span(name="child-span")
2912+
child.end()
2913+
2914+
# Verify child HAS trace_name
2915+
child_span = self.get_span_by_name(memory_exporter, "child-span")
2916+
self.verify_span_attribute(
2917+
child_span, LangfuseOtelSpanAttributes.TRACE_NAME, trace_name_200
2918+
)
2919+
2920+
def test_trace_name_nested_contexts_inner_overwrites(
2921+
self, langfuse_client, memory_exporter
2922+
):
2923+
"""Verify inner context overwrites outer trace_name."""
2924+
with langfuse_client.start_as_current_span(name="parent-span"):
2925+
with propagate_attributes(trace_name="outer-trace"):
2926+
# Create span in outer context
2927+
span1 = langfuse_client.start_span(name="span-1")
2928+
span1.end()
2929+
2930+
# Inner context with different trace_name
2931+
with propagate_attributes(trace_name="inner-trace"):
2932+
span2 = langfuse_client.start_span(name="span-2")
2933+
span2.end()
2934+
2935+
# Back to outer context
2936+
span3 = langfuse_client.start_span(name="span-3")
2937+
span3.end()
2938+
2939+
# Verify: span1 and span3 have outer-trace, span2 has inner-trace
2940+
span1_data = self.get_span_by_name(memory_exporter, "span-1")
2941+
self.verify_span_attribute(
2942+
span1_data, LangfuseOtelSpanAttributes.TRACE_NAME, "outer-trace"
2943+
)
2944+
2945+
span2_data = self.get_span_by_name(memory_exporter, "span-2")
2946+
self.verify_span_attribute(
2947+
span2_data, LangfuseOtelSpanAttributes.TRACE_NAME, "inner-trace"
2948+
)
2949+
2950+
span3_data = self.get_span_by_name(memory_exporter, "span-3")
2951+
self.verify_span_attribute(
2952+
span3_data, LangfuseOtelSpanAttributes.TRACE_NAME, "outer-trace"
2953+
)
2954+
2955+
def test_trace_name_sets_on_current_span(self, langfuse_client, memory_exporter):
2956+
"""Verify trace_name is set on the current span when entering context."""
2957+
with langfuse_client.start_as_current_span(name="parent-span"):
2958+
with propagate_attributes(trace_name="current-trace"):
2959+
pass # Just enter and exit context
2960+
2961+
# Verify parent span has trace_name set
2962+
parent_span = self.get_span_by_name(memory_exporter, "parent-span")
2963+
self.verify_span_attribute(
2964+
parent_span, LangfuseOtelSpanAttributes.TRACE_NAME, "current-trace"
2965+
)
2966+
2967+
def test_trace_name_non_string_dropped(self, langfuse_client, memory_exporter):
2968+
"""Verify non-string trace_name is dropped with warning."""
2969+
with langfuse_client.start_as_current_span(name="parent-span"):
2970+
with propagate_attributes(trace_name=123): # type: ignore
2971+
child = langfuse_client.start_span(name="child-span")
2972+
child.end()
2973+
2974+
# Verify child does NOT have trace_name
2975+
child_span = self.get_span_by_name(memory_exporter, "child-span")
2976+
self.verify_missing_attribute(child_span, LangfuseOtelSpanAttributes.TRACE_NAME)
2977+
2978+
def test_trace_name_with_baggage(self, langfuse_client, memory_exporter):
2979+
"""Verify trace_name propagates through baggage."""
2980+
with langfuse_client.start_as_current_span(name="parent-span"):
2981+
with propagate_attributes(
2982+
trace_name="baggage-trace",
2983+
user_id="user_123",
2984+
as_baggage=True,
2985+
):
2986+
child = langfuse_client.start_span(name="child-span")
2987+
child.end()
2988+
2989+
# Verify child has trace_name
2990+
child_span = self.get_span_by_name(memory_exporter, "child-span")
2991+
self.verify_span_attribute(
2992+
child_span, LangfuseOtelSpanAttributes.TRACE_NAME, "baggage-trace"
2993+
)
2994+
self.verify_span_attribute(
2995+
child_span, LangfuseOtelSpanAttributes.TRACE_USER_ID, "user_123"
2996+
)

0 commit comments

Comments
 (0)