Skip to content

Commit 3bca1f0

Browse files
zarirhamzarithikanarayandevflow.devflow-routing-intake
authored
fix(aws-lambda): force placeholder resource so the Lambda Extension drops dd-tracer-serverless-span (#11313)
fix(aws-lambda): force placeholder resource so the Lambda Extension drops dd-tracer-serverless-span The Datadog Lambda Extension filters the placeholder invocation span by comparing `span.resource == "dd-tracer-serverless-span"` (see `bottlecap/src/traces/trace_processor.rs::filter_span_from_lambda_library_or_runtime`). The intent is that the extension drops the placeholder so the inferred `aws.lambda` span (which the end-invocation handshake gives the same `span_id`) is the surviving record under that key in the trace store and remains parented to the inferred apigateway root. In practice the HTTP/JAX-RS instrumentation overwrites the placeholder's resource with the request route (e.g. "POST /") at `HTTP_FRAMEWORK_ROUTE` priority during the invocation, so the extension's filter no longer matches at end-of-invocation. Both records (the placeholder with `parent_id=0`, and the inferred `aws.lambda` with `parent_id=apigateway.span_id`) reach the backend under the same `(trace_id, span_id)` key. The trace store keeps the placeholder, and the rest of the trace is detached from the inferred apigateway root. Fix: in `LambdaHandlerInstrumentation.exit`, force the resource name back to the literal placeholder marker right before `span.finish()`, using `ResourceNamePriorities.MANUAL_INSTRUMENTATION` so the override beats whatever HTTP-framework priority the in-flight instrumentation has written. Verified end-to-end on Quarkus 3.15.4 / Java 21 / Lambda Extension v96: pre-fix the placeholder leaks to the backend with `parent_id=0` and the trace store reports orphans; post-fix zero `dd_tracer_serverless_span` rows reach the backend and the trace store reports a single 11-span chunk with no orphans. Refs: SLES-2837 use tag interceptor priority to avoid clobbering user-set resource names Add unit test comment Co-authored-by: rithikanarayan <rithika.narayan@datadoghq.com> Co-authored-by: devflow.devflow-routing-intake <devflow.devflow-routing-intake@kubernetes.us1.ddbuild.io>
1 parent 19fa98a commit 3bca1f0

3 files changed

Lines changed: 64 additions & 0 deletions

File tree

dd-java-agent/instrumentation/aws-java/aws-java-lambda-handler-1.2/src/main/java/datadog/trace/instrumentation/aws/v1/lambda/LambdaHandlerInstrumentation.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext;
2525
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
2626
import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes;
27+
import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities;
2728
import datadog.trace.config.inversion.ConfigHelper;
2829
import net.bytebuddy.asm.Advice;
2930
import net.bytebuddy.description.type.TypeDescription;
@@ -126,6 +127,21 @@ static void exit(
126127
String lambdaRequestId = awsContext.getAwsRequestId();
127128

128129
AgentTracer.get().notifyAppSecEnd(span);
130+
// Force the resource name back to the literal placeholder marker right
131+
// before finish so that the Datadog Lambda Extension's filter
132+
// (filter_span_from_lambda_library_or_runtime in
133+
// bottlecap/src/traces/trace_processor.rs, which compares
134+
// span.resource == "dd-tracer-serverless-span") drops the placeholder.
135+
// Other instrumentation (HTTP/JAX-RS) may have overwritten it with the
136+
// route ("POST /") during the invocation, in which case the extension
137+
// would fail to dedup, leading to the placeholder leaking to the backend
138+
// with parent_id=0 and detaching the inferred apigateway root from the
139+
// rest of the trace.
140+
// Use TAG_INTERCEPTOR priority because DDSpanContext.setResourceName
141+
// ignores writes whose priority is below the current resource priority,
142+
// and the HTTP/JAX-RS instrumentation will already have written
143+
// HTTP_FRAMEWORK_ROUTE (3) by this point.
144+
span.setResourceName(INVOCATION_SPAN_NAME, ResourceNamePriorities.TAG_INTERCEPTOR);
129145
span.finish();
130146
AgentTracer.get().notifyExtensionEnd(span, result, null != throwable, lambdaRequestId);
131147
} finally {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import com.amazonaws.services.lambda.runtime.Context;
2+
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
3+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
4+
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
5+
import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities;
6+
import java.io.IOException;
7+
import java.io.InputStream;
8+
import java.io.OutputStream;
9+
10+
/**
11+
* Simulates HTTP server instrumentation updating the local root span resource (e.g. route) while
12+
* the Lambda invocation span is active.
13+
*/
14+
public class HandlerStreamingSimulatesHttpFrameworkResource implements RequestStreamHandler {
15+
16+
@Override
17+
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
18+
throws IOException {
19+
AgentSpan span = AgentTracer.activeSpan();
20+
if (span != null) {
21+
span.setResourceName("POST /api/simulated", ResourceNamePriorities.HTTP_FRAMEWORK_ROUTE);
22+
}
23+
outputStream.write('O');
24+
outputStream.write('K');
25+
}
26+
}

dd-java-agent/instrumentation/aws-java/aws-java-lambda-handler-1.2/src/test/groovy/LambdaHandlerInstrumentationTest.groovy

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,28 @@ abstract class LambdaHandlerInstrumentationTest extends VersionedNamingTestBase
9898
}
9999
}
100100

101+
def "serverless invocation span resource reset after simulated HTTP framework overwrite"() {
102+
when:
103+
def input = new ByteArrayInputStream(StandardCharsets.UTF_8.encode("Hello").array())
104+
def output = new ByteArrayOutputStream()
105+
def ctx = Stub(Context) {
106+
getAwsRequestId() >> requestId
107+
}
108+
new HandlerStreamingSimulatesHttpFrameworkResource().handleRequest(input, output, ctx)
109+
110+
then:
111+
assertTraces(1) {
112+
trace(1) {
113+
span {
114+
operationName operation()
115+
resourceName operation()
116+
spanType DDSpanTypes.SERVERLESS
117+
errored false
118+
}
119+
}
120+
}
121+
}
122+
101123
def "test streaming handler with error"() {
102124
when:
103125
def input = new ByteArrayInputStream(StandardCharsets.UTF_8.encode("Hello").array())

0 commit comments

Comments
 (0)