@@ -945,3 +945,208 @@ def worker_raises_exception():
945945 self .verify_span_attribute (
946946 span_after , LangfuseOtelSpanAttributes .TRACE_USER_ID , "main_user"
947947 )
948+
949+
950+ class TestPropagateAttributesCrossTracer (TestPropagateAttributesBase ):
951+ """Tests for propagate_attributes with different OpenTelemetry tracers."""
952+
953+ def test_different_tracer_spans_get_attributes (
954+ self , langfuse_client , memory_exporter , tracer_provider
955+ ):
956+ """Verify spans from different tracers get propagated attributes."""
957+ # Get a different tracer (not the Langfuse tracer)
958+ other_tracer = tracer_provider .get_tracer ("other-library" , "1.0.0" )
959+
960+ with langfuse_client .start_as_current_span (name = "langfuse-parent" ):
961+ with propagate_attributes (user_id = "user_123" , session_id = "session_abc" ):
962+ # Create span with Langfuse tracer
963+ langfuse_span = langfuse_client .start_span (name = "langfuse-child" )
964+ langfuse_span .end ()
965+
966+ # Create span with different tracer
967+ with other_tracer .start_as_current_span (name = "other-library-span" ):
968+ pass
969+
970+ # Verify both spans have the propagated attributes
971+ langfuse_span_data = self .get_span_by_name (memory_exporter , "langfuse-child" )
972+ self .verify_span_attribute (
973+ langfuse_span_data ,
974+ LangfuseOtelSpanAttributes .TRACE_USER_ID ,
975+ "user_123" ,
976+ )
977+ self .verify_span_attribute (
978+ langfuse_span_data ,
979+ LangfuseOtelSpanAttributes .TRACE_SESSION_ID ,
980+ "session_abc" ,
981+ )
982+
983+ other_span_data = self .get_span_by_name (memory_exporter , "other-library-span" )
984+ self .verify_span_attribute (
985+ other_span_data ,
986+ LangfuseOtelSpanAttributes .TRACE_USER_ID ,
987+ "user_123" ,
988+ )
989+ self .verify_span_attribute (
990+ other_span_data ,
991+ LangfuseOtelSpanAttributes .TRACE_SESSION_ID ,
992+ "session_abc" ,
993+ )
994+
995+ def test_nested_spans_from_multiple_tracers (
996+ self , langfuse_client , memory_exporter , tracer_provider
997+ ):
998+ """Verify nested spans from multiple tracers all get propagated attributes."""
999+ tracer_a = tracer_provider .get_tracer ("library-a" , "1.0.0" )
1000+ tracer_b = tracer_provider .get_tracer ("library-b" , "2.0.0" )
1001+
1002+ with langfuse_client .start_as_current_span (name = "root" ):
1003+ with propagate_attributes (
1004+ user_id = "user_123" , metadata = {"experiment" : "cross_tracer" }
1005+ ):
1006+ # Create nested spans from different tracers
1007+ with tracer_a .start_as_current_span (name = "library-a-span" ):
1008+ with tracer_b .start_as_current_span (name = "library-b-span" ):
1009+ langfuse_leaf = langfuse_client .start_span (name = "langfuse-leaf" )
1010+ langfuse_leaf .end ()
1011+
1012+ # Verify all spans have the attributes
1013+ for span_name in ["library-a-span" , "library-b-span" , "langfuse-leaf" ]:
1014+ span_data = self .get_span_by_name (memory_exporter , span_name )
1015+ self .verify_span_attribute (
1016+ span_data ,
1017+ LangfuseOtelSpanAttributes .TRACE_USER_ID ,
1018+ "user_123" ,
1019+ )
1020+ self .verify_span_attribute (
1021+ span_data ,
1022+ f"{ LangfuseOtelSpanAttributes .TRACE_METADATA } .experiment" ,
1023+ "cross_tracer" ,
1024+ )
1025+
1026+ def test_other_tracer_span_before_propagate_context (
1027+ self , langfuse_client , memory_exporter , tracer_provider
1028+ ):
1029+ """Verify spans created before propagate_attributes don't get attributes."""
1030+ other_tracer = tracer_provider .get_tracer ("other-library" , "1.0.0" )
1031+
1032+ with langfuse_client .start_as_current_span (name = "root" ):
1033+ # Create span BEFORE propagate_attributes
1034+ with other_tracer .start_as_current_span (name = "span-before" ):
1035+ pass
1036+
1037+ # NOW set attributes
1038+ with propagate_attributes (user_id = "user_123" ):
1039+ # Create span AFTER propagate_attributes
1040+ with other_tracer .start_as_current_span (name = "span-after" ):
1041+ pass
1042+
1043+ # Verify: span-before does NOT have user_id, span-after DOES
1044+ span_before = self .get_span_by_name (memory_exporter , "span-before" )
1045+ self .verify_missing_attribute (
1046+ span_before , LangfuseOtelSpanAttributes .TRACE_USER_ID
1047+ )
1048+
1049+ span_after = self .get_span_by_name (memory_exporter , "span-after" )
1050+ self .verify_span_attribute (
1051+ span_after , LangfuseOtelSpanAttributes .TRACE_USER_ID , "user_123"
1052+ )
1053+
1054+ def test_mixed_tracers_with_metadata (
1055+ self , langfuse_client , memory_exporter , tracer_provider
1056+ ):
1057+ """Verify metadata propagates correctly to spans from different tracers."""
1058+ other_tracer = tracer_provider .get_tracer ("instrumented-library" , "1.0.0" )
1059+
1060+ with langfuse_client .start_as_current_span (name = "main" ):
1061+ with propagate_attributes (
1062+ metadata = {
1063+ "env" : "production" ,
1064+ "version" : "2.0" ,
1065+ "feature_flag" : "enabled" ,
1066+ }
1067+ ):
1068+ # Create spans from both tracers
1069+ langfuse_span = langfuse_client .start_span (name = "langfuse-operation" )
1070+ langfuse_span .end ()
1071+
1072+ with other_tracer .start_as_current_span (name = "library-operation" ):
1073+ pass
1074+
1075+ # Verify both spans have all metadata
1076+ for span_name in ["langfuse-operation" , "library-operation" ]:
1077+ span_data = self .get_span_by_name (memory_exporter , span_name )
1078+ self .verify_span_attribute (
1079+ span_data ,
1080+ f"{ LangfuseOtelSpanAttributes .TRACE_METADATA } .env" ,
1081+ "production" ,
1082+ )
1083+ self .verify_span_attribute (
1084+ span_data ,
1085+ f"{ LangfuseOtelSpanAttributes .TRACE_METADATA } .version" ,
1086+ "2.0" ,
1087+ )
1088+ self .verify_span_attribute (
1089+ span_data ,
1090+ f"{ LangfuseOtelSpanAttributes .TRACE_METADATA } .feature_flag" ,
1091+ "enabled" ,
1092+ )
1093+
1094+ def test_propagate_without_langfuse_parent (
1095+ self , langfuse_client , memory_exporter , tracer_provider
1096+ ):
1097+ """Verify propagate_attributes works even when parent span is from different tracer."""
1098+ other_tracer = tracer_provider .get_tracer ("other-library" , "1.0.0" )
1099+
1100+ # Parent span is from different tracer
1101+ with other_tracer .start_as_current_span (name = "other-parent" ):
1102+ with propagate_attributes (user_id = "user_123" , session_id = "session_xyz" ):
1103+ # Create children from both tracers
1104+ with other_tracer .start_as_current_span (name = "other-child" ):
1105+ pass
1106+
1107+ langfuse_child = langfuse_client .start_span (name = "langfuse-child" )
1108+ langfuse_child .end ()
1109+
1110+ # Verify all spans have attributes (including non-Langfuse parent)
1111+ for span_name in ["other-parent" , "other-child" , "langfuse-child" ]:
1112+ span_data = self .get_span_by_name (memory_exporter , span_name )
1113+ self .verify_span_attribute (
1114+ span_data ,
1115+ LangfuseOtelSpanAttributes .TRACE_USER_ID ,
1116+ "user_123" ,
1117+ )
1118+ self .verify_span_attribute (
1119+ span_data ,
1120+ LangfuseOtelSpanAttributes .TRACE_SESSION_ID ,
1121+ "session_xyz" ,
1122+ )
1123+
1124+ def test_attributes_persist_across_tracer_changes (
1125+ self , langfuse_client , memory_exporter , tracer_provider
1126+ ):
1127+ """Verify attributes persist as execution moves between different tracers."""
1128+ tracer_1 = tracer_provider .get_tracer ("library-1" , "1.0.0" )
1129+ tracer_2 = tracer_provider .get_tracer ("library-2" , "1.0.0" )
1130+ tracer_3 = tracer_provider .get_tracer ("library-3" , "1.0.0" )
1131+
1132+ with langfuse_client .start_as_current_span (name = "root" ):
1133+ with propagate_attributes (user_id = "persistent_user" ):
1134+ # Bounce between different tracers
1135+ with tracer_1 .start_as_current_span (name = "step-1" ):
1136+ pass
1137+
1138+ with tracer_2 .start_as_current_span (name = "step-2" ):
1139+ with tracer_3 .start_as_current_span (name = "step-3" ):
1140+ pass
1141+
1142+ langfuse_span = langfuse_client .start_span (name = "step-4" )
1143+ langfuse_span .end ()
1144+
1145+ # Verify all steps have the user_id
1146+ for step_name in ["step-1" , "step-2" , "step-3" , "step-4" ]:
1147+ span_data = self .get_span_by_name (memory_exporter , step_name )
1148+ self .verify_span_attribute (
1149+ span_data ,
1150+ LangfuseOtelSpanAttributes .TRACE_USER_ID ,
1151+ "persistent_user" ,
1152+ )
0 commit comments