@@ -17,11 +17,36 @@ internal static class ResourceAttributeProcessorHelper
1717 {
1818 private static Func < object , object > ? _getResourceDelegate ;
1919
20+ /// <summary>
21+ /// Cached OTel resource, populated on the first <see cref="OnStart"/> call from
22+ /// the dynamic ResourceAttributeProcessor. Used by the CallTarget Activity
23+ /// interception path to apply resource attributes after Activity.Start() returns,
24+ /// because the processor's OnStart fires *during* Activity.Start() — before
25+ /// the interception integration has had a chance to create the Datadog span.
26+ /// </summary>
27+ private static volatile IResource ? _cachedResource ;
28+
2029 static ResourceAttributeProcessorHelper ( )
2130 {
2231 _getResourceDelegate = CreateGetResourceDelegate ( ) ;
2332 }
2433
34+ /// <summary>
35+ /// Applies the cached OTel resource attributes (service.name, service.version, etc.)
36+ /// to the given <paramref name="span"/>. Called from <c>ActivityStartIntegration.OnMethodEnd</c>
37+ /// after the span has been created.
38+ /// </summary>
39+ internal static void ApplyCachedResourceAttributes ( Span span )
40+ {
41+ var resource = _cachedResource ;
42+ if ( resource is null )
43+ {
44+ return ;
45+ }
46+
47+ ApplyResourceToSpan ( span , resource ) ;
48+ }
49+
2550 public static void OnStart ( object processor , object activityData )
2651 {
2752 if ( _getResourceDelegate is null
@@ -31,75 +56,83 @@ public static void OnStart(object processor, object activityData)
3156 return ;
3257 }
3358
34- // When CallTarget-based Activity interception is enabled, look up the span via the
35- // custom property stored directly on the Activity object (no ConcurrentDictionary lookup).
36- Span ? span = null ;
37- if ( Tracer . Instance . Settings . IsActivityInterceptionEnabled )
59+ // Cache the resource from the TracerProvider on first call so the interception path can use it later
60+ if ( _cachedResource is null && baseProcessor . ParentProvider is not null )
3861 {
39- if ( activityData . TryDuckCast < IActivity5 > ( out var activity5ForInterception ) )
62+ var resourceObject = _getResourceDelegate ( baseProcessor . ParentProvider ) ;
63+ if ( resourceObject . TryDuckCast < IResource > ( out var resource ) )
4064 {
41- span = ( activity5ForInterception . GetCustomProperty ( "__dd_span__" ) as Scope ) ? . Span ;
65+ _cachedResource = resource ;
4266 }
4367 }
4468
45- if ( span is null )
69+ // When CallTarget-based Activity interception is enabled, the span is set on the Activity
70+ // custom property. However, the processor's OnStart fires during Activity.Start() — before
71+ // our OnMethodEnd integration runs. So the custom property will be null at this point.
72+ // The interception path applies resource attributes itself via ApplyCachedResourceAttributes().
73+ if ( Tracer . Instance . Settings . IsActivityInterceptionEnabled )
4674 {
47- // Fallback: legacy ConcurrentDictionary lookup for the managed ActivityListener path
48- ActivityKey key ;
49- if ( activityData . TryDuckCast < IW3CActivity > ( out var w3cActivity ) && w3cActivity . TraceId is { } traceId && w3cActivity . SpanId is { } spanId )
50- {
51- key = new ( traceId , spanId ) ;
52- }
53- else
54- {
55- key = new ( activity . Id ) ;
56- }
75+ return ;
76+ }
77+
78+ // Managed ActivityListener path: look up the span via ConcurrentDictionary
79+ Span ? span ;
80+ ActivityKey key ;
81+ if ( activityData . TryDuckCast < IW3CActivity > ( out var w3cActivity ) && w3cActivity . TraceId is { } traceId && w3cActivity . SpanId is { } spanId )
82+ {
83+ key = new ( traceId , spanId ) ;
84+ }
85+ else
86+ {
87+ key = new ( activity . Id ) ;
88+ }
5789
58- if ( ! key . IsValid ( ) || ! ActivityHandlerCommon . ActivityMappingById . TryGetValue ( key , out var activityMapping ) )
90+ if ( ! key . IsValid ( ) || ! ActivityHandlerCommon . ActivityMappingById . TryGetValue ( key , out var activityMapping ) )
91+ {
92+ return ;
93+ }
94+
95+ span = activityMapping . Scope ? . Span ;
96+
97+ if ( span is not null && baseProcessor . ParentProvider is not null )
98+ {
99+ var resourceObject = _getResourceDelegate ( baseProcessor . ParentProvider ) ;
100+ if ( resourceObject . TryDuckCast < IResource > ( out var resource ) )
59101 {
60- return ;
102+ ApplyResourceToSpan ( span , resource ) ;
61103 }
62-
63- span = activityMapping . Scope ? . Span ;
64104 }
105+ }
65106
66- if ( span is not null )
107+ private static void ApplyResourceToSpan ( Span span , IResource resource )
108+ {
109+ foreach ( var attribute in resource . Attributes )
67110 {
68- if ( baseProcessor . ParentProvider is not null )
111+ span . SetTag ( attribute . Key , attribute . Value ? . ToString ( ) ) ;
112+
113+ // In addition to copying the attribute as a tag, update span fields for specific keys
114+ if ( attribute . Value is not null )
69115 {
70- var resourceObject = _getResourceDelegate ( baseProcessor . ParentProvider ) ;
71- if ( resourceObject . TryDuckCast < IResource > ( out var resource ) )
116+ if ( attribute . Key == "service.name" )
72117 {
73- foreach ( var attribute in resource . Attributes )
118+ var resourceServiceName = attribute . Value . ToString ( ) ;
119+
120+ // if OTEL_SERVICE_NAME isn't set, OpenTelemetry will set "service.name" to:
121+ // "unknown_service" or "unknown_service:ProcessName"
122+ if ( string . IsNullOrEmpty ( resourceServiceName )
123+ || string . Equals ( resourceServiceName , "unknown_service" , StringComparison . Ordinal )
124+ || resourceServiceName . StartsWith ( "unknown_service:" , StringComparison . Ordinal ) )
74125 {
75- span . SetTag ( attribute . Key , attribute . Value ? . ToString ( ) ) ;
76-
77- // In addition to copying the attribute as a tag, update span fields for specific keys
78- if ( attribute . Value is not null )
79- {
80- if ( attribute . Key == "service.name" )
81- {
82- var resourceServiceName = attribute . Value . ToString ( ) ;
83-
84- // if OTEL_SERVICE_NAME isn't set, OpenTelemetry will set "service.name" to:
85- // "unknown_service" or "unknown_service:ProcessName"
86- if ( string . IsNullOrEmpty ( resourceServiceName )
87- || string . Equals ( resourceServiceName , "unknown_service" , StringComparison . Ordinal )
88- || resourceServiceName . StartsWith ( "unknown_service:" , StringComparison . Ordinal ) )
89- {
90- resourceServiceName = Tracer . Instance . DefaultServiceName ;
91-
92- span . SetTag ( attribute . Key , resourceServiceName ) ;
93- }
94-
95- span . SetService ( resourceServiceName , null ) ;
96- }
97- else if ( attribute . Key == "service.version" )
98- {
99- span . SetTag ( Tags . Version , attribute . Value . ToString ( ) ) ;
100- }
101- }
126+ resourceServiceName = Tracer . Instance . DefaultServiceName ;
127+
128+ span . SetTag ( attribute . Key , resourceServiceName ) ;
102129 }
130+
131+ span . SetService ( resourceServiceName , null ) ;
132+ }
133+ else if ( attribute . Key == "service.version" )
134+ {
135+ span . SetTag ( Tags . Version , attribute . Value . ToString ( ) ) ;
103136 }
104137 }
105138 }
0 commit comments