@@ -177,7 +177,9 @@ public void startTrace(TraceContext traceContext, Traceable traceable, String sp
177177
178178 // A span can have a parent span, which here is modelled though a parent span context.
179179 // Setting this is important for seeing a complete trace in the APM UI.
180- final Context parentContext = getParentContext (traceContext );
180+ // Attempt to fetch a local parent context first, otherwise look for a remote parent
181+ final Context localParentContext = traceContext .getTransient (Task .PARENT_APM_TRACE_CONTEXT );
182+ final Context parentContext = localParentContext != null ? localParentContext : getRemoteParentContext (traceContext );
181183 if (parentContext != null ) {
182184 spanBuilder .setParent (parentContext );
183185 }
@@ -188,21 +190,21 @@ public void startTrace(TraceContext traceContext, Traceable traceable, String sp
188190 if (startTime != null ) {
189191 spanBuilder .setStartTimestamp (startTime );
190192 }
193+
191194 final Span span = spanBuilder .startSpan ();
192- // If the agent decided not to record this span (e.g., due to transaction_max_spans), isRecording() will be false.
193- if (span .isRecording () == false ) {
194- logger .trace ("Span [{}] [{}] will not be recorded, e.g. due to transaction_max_spans reached" , spanId , spanName );
195- // It's good practice to end the no-op span immediately to release any resources.
196- span .end ();
197- // Returning null from computeIfAbsent means no value will be inserted into the map.
198- return null ;
195+ // If not a root span (meaning a local parent exists) and the agent decided not to record the span, discard it immediately.
196+ // Root spans (transactions), however, have to be kept to correctly report their duration.
197+ if (localParentContext != null && span .isRecording () == false ) {
198+ logger .trace ("Span [{}] [{}] will not be recorded due to transaction_max_spans reached" , spanId , spanName );
199+ span .end (); // end span immediately to release any resources.
200+ return null ; // return null to discard and not record in map of spans
199201 }
200202
201- // If we are here, the span is real and being recorded.
202- logger .trace ("Successfully started tracing [{}] [{}]" , spanId , spanName );
203203 final Context contextForNewSpan = Context .current ().with (span );
204-
205- updateThreadContext (traceContext , services , contextForNewSpan );
204+ if (span .isRecording ()) {
205+ logger .trace ("Recording trace [{}] [{}]" , spanId , spanName );
206+ updateThreadContext (traceContext , services , contextForNewSpan );
207+ }
206208
207209 return contextForNewSpan ;
208210 });
@@ -240,30 +242,26 @@ private static void updateThreadContext(TraceContext traceContext, APMServices s
240242 });
241243 }
242244
243- private Context getParentContext (TraceContext traceContext ) {
245+ private Context getRemoteParentContext (TraceContext traceContext ) {
244246 // https://github.com/open-telemetry/opentelemetry-java/discussions/2884#discussioncomment-381870
245247 // If you just want to propagate across threads within the same process, you don't need context propagators (extract/inject).
246248 // You can just pass the Context object directly to another thread (it is immutable and thus thread-safe).
247249
248- // Attempt to fetch a local parent context first, otherwise look for a remote parent
249- Context parentContext = traceContext .getTransient (Task .PARENT_APM_TRACE_CONTEXT );
250- if (parentContext == null ) {
251- final String traceParentHeader = traceContext .getTransient (Task .PARENT_TRACE_PARENT_HEADER );
252- final String traceStateHeader = traceContext .getTransient (Task .PARENT_TRACE_STATE );
253-
254- if (traceParentHeader != null ) {
255- final Map <String , String > traceContextMap = Maps .newMapWithExpectedSize (2 );
256- // traceparent and tracestate should match the keys used by W3CTraceContextPropagator
257- traceContextMap .put (Task .TRACE_PARENT_HTTP_HEADER , traceParentHeader );
258- if (traceStateHeader != null ) {
259- traceContextMap .put (Task .TRACE_STATE , traceStateHeader );
260- }
261- parentContext = services .openTelemetry .getPropagators ()
262- .getTextMapPropagator ()
263- .extract (Context .current (), traceContextMap , new MapKeyGetter ());
250+ final String traceParentHeader = traceContext .getTransient (Task .PARENT_TRACE_PARENT_HEADER );
251+ final String traceStateHeader = traceContext .getTransient (Task .PARENT_TRACE_STATE );
252+
253+ if (traceParentHeader != null ) {
254+ final Map <String , String > traceContextMap = Maps .newMapWithExpectedSize (2 );
255+ // traceparent and tracestate should match the keys used by W3CTraceContextPropagator
256+ traceContextMap .put (Task .TRACE_PARENT_HTTP_HEADER , traceParentHeader );
257+ if (traceStateHeader != null ) {
258+ traceContextMap .put (Task .TRACE_STATE , traceStateHeader );
264259 }
260+ return services .openTelemetry .getPropagators ()
261+ .getTextMapPropagator ()
262+ .extract (Context .current (), traceContextMap , new MapKeyGetter ());
265263 }
266- return parentContext ;
264+ return null ;
267265 }
268266
269267 /**
@@ -288,7 +286,7 @@ private Context getParentContext(TraceContext traceContext) {
288286 @ Override
289287 public Releasable withScope (Traceable traceable ) {
290288 final Context context = spans .get (traceable .getSpanId ());
291- if (context != null ) {
289+ if (context != null && Span . fromContextOrNull ( context ). isRecording () ) {
292290 return context .makeCurrent ()::close ;
293291 }
294292 return () -> {};
@@ -385,9 +383,10 @@ public void setAttribute(Traceable traceable, String key, String value) {
385383
386384 @ Override
387385 public void stopTrace (Traceable traceable ) {
388- final var span = Span .fromContextOrNull (spans .remove (traceable .getSpanId ()));
386+ final String spanId = traceable .getSpanId ();
387+ final var span = Span .fromContextOrNull (spans .remove (spanId ));
389388 if (span != null ) {
390- logger .trace ("Finishing trace [{}]" , traceable );
389+ logger .trace ("Finishing trace [{}]" , spanId );
391390 span .end ();
392391 }
393392 }
0 commit comments