Skip to content

Commit 50eca11

Browse files
committed
enable inferred proxys spans
1 parent 439604a commit 50eca11

File tree

3 files changed

+156
-4
lines changed

3 files changed

+156
-4
lines changed

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ public Context beforeFinish(Context context) {
544544
}
545545

546546
// Close Serverless Gateway Inferred Span if any
547-
// finishInferredProxySpan(context);
547+
finishInferredProxySpan(context);
548548

549549
return super.beforeFinish(context);
550550
}

dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/test/groovy/test/boot/SpringBootBasedTest.groovy

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import datadog.trace.agent.test.base.HttpServer
55
import datadog.trace.agent.test.base.HttpServerTest
66
import datadog.trace.api.DDSpanTypes
77
import datadog.trace.api.DDTags
8+
import datadog.trace.api.config.TracerConfig
89
import datadog.trace.api.iast.IastContext
910
import datadog.trace.api.iast.InstrumentationBridge
1011
import datadog.trace.api.iast.SourceTypes
@@ -61,6 +62,7 @@ class SpringBootBasedTest extends HttpServerTest<ConfigurableApplicationContext>
6162
protected void configurePreAgent() {
6263
super.configurePreAgent()
6364
injectSysConfig('dd.iast.enabled', 'true')
65+
injectSysConfig(TracerConfig.TRACE_INFERRED_PROXY_SERVICES_ENABLED, 'true')
6466
}
6567

6668
@Override
@@ -479,4 +481,121 @@ class SpringBootBasedTest extends HttpServerTest<ConfigurableApplicationContext>
479481
}
480482
}
481483
}
484+
485+
def "test inferred proxy span is finished"() {
486+
setup:
487+
def request = request(SUCCESS, "GET", null)
488+
.header("x-dd-proxy", "aws-apigateway")
489+
.header("x-dd-proxy-request-time-ms", "12345")
490+
.header("x-dd-proxy-path", "/success")
491+
.header("x-dd-proxy-httpmethod", "GET")
492+
.header("x-dd-proxy-domain-name", "api.example.com")
493+
.header("x-dd-proxy-stage", "test")
494+
.build()
495+
496+
when:
497+
def response = client.newCall(request).execute()
498+
499+
then:
500+
response.code() == SUCCESS.status
501+
502+
and:
503+
// Verify that inferred proxy span was created and finished
504+
// It should appear in the trace as an additional span
505+
assertTraces(1) {
506+
trace(spanCount(SUCCESS) + 1) {
507+
sortSpansByStart()
508+
// The inferred proxy span should be the first span (earliest start time)
509+
// Verify it exists and was finished (appears in trace)
510+
// Operation name is the proxy system name (aws.apigateway), not inferred_proxy
511+
span {
512+
operationName "aws.apigateway"
513+
serviceName "api.example.com"
514+
// Resource Name: httpmethod + " " + path
515+
resourceName "GET /success"
516+
spanType "web"
517+
parent()
518+
tags {
519+
"$Tags.COMPONENT" "aws-apigateway"
520+
"$Tags.HTTP_METHOD" "GET"
521+
"$Tags.HTTP_URL" "api.example.com/success"
522+
"$Tags.HTTP_ROUTE" "/success"
523+
"stage" "test"
524+
"_dd.inferred_span" 1
525+
// Standard tags that are automatically added
526+
"_dd.agent_psr" Number
527+
"_dd.base_service" String
528+
"_dd.dsm.enabled" Number
529+
"_dd.profiling.ctx" String
530+
"_dd.profiling.enabled" Number
531+
"_dd.trace_span_attribute_schema" Number
532+
"_dd.tracer_host" String
533+
"_sample_rate" Number
534+
"language" "jvm"
535+
"process_id" Number
536+
"runtime-id" String
537+
"thread.id" Number
538+
"thread.name" String
539+
}
540+
}
541+
// Server span should be a child of the inferred proxy span
542+
// When there's an inferred proxy span parent, the server span inherits the parent's service name
543+
span {
544+
// Service name is inherited from the inferred proxy span parent
545+
serviceName "api.example.com"
546+
operationName operation()
547+
resourceName expectedResourceName(SUCCESS, "GET", address)
548+
spanType DDSpanTypes.HTTP_SERVER
549+
errored false
550+
childOfPrevious()
551+
tags {
552+
"$Tags.COMPONENT" component
553+
"$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER
554+
"$Tags.PEER_HOST_IPV4" "127.0.0.1"
555+
"$Tags.PEER_PORT" Integer
556+
"$Tags.HTTP_CLIENT_IP" "127.0.0.1"
557+
"$Tags.HTTP_HOSTNAME" address.host
558+
"$Tags.HTTP_URL" String
559+
"$Tags.HTTP_METHOD" "GET"
560+
"$Tags.HTTP_STATUS" SUCCESS.status
561+
"$Tags.HTTP_USER_AGENT" String
562+
"$Tags.HTTP_ROUTE" "/success"
563+
"servlet.context" "/boot-context"
564+
"servlet.path" "/success"
565+
defaultTags()
566+
}
567+
}
568+
if (hasHandlerSpan()) {
569+
// Handler span inherits service name from inferred proxy span parent
570+
it.span {
571+
serviceName "api.example.com"
572+
operationName "spring.handler"
573+
resourceName "TestController.success"
574+
spanType DDSpanTypes.HTTP_SERVER
575+
errored false
576+
childOfPrevious()
577+
tags {
578+
"$Tags.COMPONENT" SpringWebHttpServerDecorator.DECORATE.component()
579+
"$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER
580+
defaultTags()
581+
}
582+
}
583+
}
584+
// Controller span also inherits service name
585+
it.span {
586+
serviceName "api.example.com"
587+
operationName "controller"
588+
resourceName "controller"
589+
errored false
590+
childOfPrevious()
591+
tags {
592+
defaultTags()
593+
}
594+
}
595+
if (hasResponseSpan(SUCCESS)) {
596+
responseSpan(it, SUCCESS)
597+
}
598+
}
599+
}
600+
}
482601
}

internal-api/src/main/java/datadog/trace/api/gateway/InferredProxySpan.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
import static datadog.context.ContextKey.named;
44
import static datadog.trace.api.DDTags.SPAN_TYPE;
5+
import static datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities.MANUAL_INSTRUMENTATION;
56
import static datadog.trace.bootstrap.instrumentation.api.Tags.COMPONENT;
67
import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_METHOD;
8+
import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_ROUTE;
79
import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_URL;
810

911
import datadog.context.Context;
1012
import datadog.context.ContextKey;
1113
import datadog.context.ImplicitContextKeyed;
14+
import datadog.trace.api.Config;
1215
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
1316
import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext;
1417
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
@@ -69,15 +72,45 @@ public AgentSpanContext start(AgentSpanContext extracted) {
6972

7073
String proxySystem = header(PROXY_SYSTEM);
7174
String proxy = SUPPORTED_PROXIES.get(proxySystem);
75+
String httpMethod = header(PROXY_HTTP_METHOD);
76+
String path = header(PROXY_PATH);
77+
String domainName = header(PROXY_DOMAIN_NAME);
78+
7279
AgentSpan span = AgentTracer.get().startSpan(INSTRUMENTATION_NAME, proxy, extracted, startTime);
73-
span.setServiceName(header(PROXY_DOMAIN_NAME));
80+
81+
// Service: value of x-dd-proxy-domain-name or global config if not found
82+
String serviceName =
83+
domainName != null && !domainName.isEmpty() ? domainName : Config.get().getServiceName();
84+
span.setServiceName(serviceName);
85+
86+
// Component: aws-apigateway
7487
span.setTag(COMPONENT, proxySystem);
88+
89+
// SpanType: web
7590
span.setTag(SPAN_TYPE, "web");
76-
span.setTag(HTTP_METHOD, header(PROXY_HTTP_METHOD));
77-
span.setTag(HTTP_URL, header(PROXY_DOMAIN_NAME) + header(PROXY_PATH));
91+
92+
// Http.method - value of x-dd-proxy-httpmethod
93+
span.setTag(HTTP_METHOD, httpMethod);
94+
95+
// Http.url - value of x-dd-proxy-domain-name + x-dd-proxy-path
96+
span.setTag(HTTP_URL, domainName != null ? domainName + path : path);
97+
98+
// Http.route - value of x-dd-proxy-path
99+
span.setTag(HTTP_ROUTE, path);
100+
101+
// "stage" - value of x-dd-proxy-stage
78102
span.setTag("stage", header(STAGE));
103+
104+
// _dd.inferred_span = 1 (indicates that this is an inferred span)
79105
span.setTag("_dd.inferred_span", 1);
80106

107+
// Resource Name: value of x-dd-proxy-httpmethod + " " + value of x-dd-proxy-path
108+
// Use MANUAL_INSTRUMENTATION priority to prevent TagInterceptor from overriding
109+
String resourceName = httpMethod != null && path != null ? httpMethod + " " + path : null;
110+
if (resourceName != null) {
111+
span.setResourceName(resourceName, MANUAL_INSTRUMENTATION);
112+
}
113+
81114
// Free collected headers
82115
this.headers.clear();
83116
// Store inferred span

0 commit comments

Comments
 (0)