@@ -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