Skip to content

Commit e5e0581

Browse files
Use the local root span for the http.route tag in Play
1 parent 88aa5b2 commit e5e0581

File tree

23 files changed

+651
-3
lines changed

23 files changed

+651
-3
lines changed

dd-java-agent/instrumentation/play-2.3/src/main/java/datadog/trace/instrumentation/play23/PlayHttpServerDecorator.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,17 @@ public AgentSpan onRequest(
8787
final Option pathOption = request.tags().get(Routes.ROUTE_PATTERN());
8888
if (!pathOption.isEmpty()) {
8989
final String path = (String) pathOption.get();
90-
HTTP_RESOURCE_DECORATOR.withRoute(span, request.method(), path);
90+
HTTP_RESOURCE_DECORATOR.withRoute(spanForHttpRoute(span), request.method(), path);
9191
}
9292
}
9393
return span;
9494
}
9595

96+
private AgentSpan spanForHttpRoute(final AgentSpan span) {
97+
final AgentSpan rootSpan = span.getLocalRootSpan();
98+
return rootSpan == null ? span : rootSpan;
99+
}
100+
96101
@Override
97102
public AgentSpan onError(final AgentSpan span, Throwable throwable) {
98103
if (REPORT_HTTP_STATUS) {

dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayHttpServerDecorator.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,17 @@ public AgentSpan onRequest(
8787
final Option pathOption = request.tags().get("ROUTE_PATTERN");
8888
if (!pathOption.isEmpty()) {
8989
final String path = (String) pathOption.get();
90-
HTTP_RESOURCE_DECORATOR.withRoute(span, request.method(), path);
90+
HTTP_RESOURCE_DECORATOR.withRoute(spanForHttpRoute(span), request.method(), path);
9191
}
9292
}
9393
return span;
9494
}
9595

96+
private AgentSpan spanForHttpRoute(final AgentSpan span) {
97+
final AgentSpan rootSpan = span.getLocalRootSpan();
98+
return rootSpan == null ? span : rootSpan;
99+
}
100+
96101
@Override
97102
public AgentSpan onError(final AgentSpan span, Throwable throwable) {
98103
if (REPORT_HTTP_STATUS) {

dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayHttpServerDecorator.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,17 @@ public AgentSpan onRequest(
141141
CharSequence path =
142142
PATH_CACHE.computeIfAbsent(
143143
defOption.get().path(), p -> addMissingSlash(p, request.path()));
144-
HTTP_RESOURCE_DECORATOR.withRoute(span, request.method(), path, true);
144+
HTTP_RESOURCE_DECORATOR.withRoute(spanForHttpRoute(span), request.method(), path, true);
145145
}
146146
}
147147
return span;
148148
}
149149

150+
private AgentSpan spanForHttpRoute(final AgentSpan span) {
151+
final AgentSpan rootSpan = span.getLocalRootSpan();
152+
return rootSpan == null ? span : rootSpan;
153+
}
154+
150155
/*
151156
This is a workaround to add a `/` if it is missing when using split routes.
152157
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package controllers
2+
3+
import play.api.mvc.{Action, AnyContent, Controller}
4+
5+
class AppSecController extends Controller {
6+
7+
def apiSecuritySampling(statusCode: Int, test: String): Action[AnyContent] = Action {
8+
Status(statusCode)("EXECUTED")
9+
}
10+
11+
}

dd-smoke-tests/play-2.4/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ dependencies {
6363
implementation group: 'io.opentracing', name: 'opentracing-util', version: '0.32.0'
6464

6565
testImplementation project(':dd-smoke-tests')
66+
testImplementation project(':dd-smoke-tests:appsec')
6667
}
6768

6869
configurations.testImplementation {

dd-smoke-tests/play-2.4/conf/routes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@
66
# An example controller showing a sample home page
77
GET /welcomej controllers.JController.doGet(id: Int ?= 0)
88
GET /welcomes controllers.SController.doGet(id: Option[Int])
9+
10+
# AppSec endpoints for testing
11+
GET /api_security/sampling/:statusCode controllers.AppSecController.apiSecuritySampling(statusCode: Int, test: String)
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package datadog.smoketest
2+
3+
import datadog.smoketest.appsec.AbstractAppSecServerSmokeTest
4+
import datadog.trace.agent.test.utils.OkHttpUtils
5+
import okhttp3.Request
6+
import okhttp3.Response
7+
import spock.lang.Shared
8+
9+
import java.nio.file.Files
10+
11+
import static java.util.concurrent.TimeUnit.SECONDS
12+
13+
class AppSecPlayNettySmokeTest extends AbstractAppSecServerSmokeTest {
14+
15+
@Shared
16+
File playDirectory = new File("${buildDirectory}/stage/main")
17+
18+
@Override
19+
ProcessBuilder createProcessBuilder() {
20+
// If the server is not shut down correctly, this file can be left there and will block
21+
// the start of a new test
22+
def runningPid = new File(playDirectory.getPath(), "RUNNING_PID")
23+
if (runningPid.exists()) {
24+
runningPid.delete()
25+
}
26+
def command = isWindows() ? 'main.bat' : 'main'
27+
ProcessBuilder processBuilder = new ProcessBuilder("${playDirectory}/bin/${command}")
28+
processBuilder.directory(playDirectory)
29+
processBuilder.environment().put("JAVA_OPTS",
30+
(defaultAppSecProperties + defaultJavaProperties).collect({ it.replace(' ', '\\ ')}).join(" ")
31+
+ " -Dconfig.file=${playDirectory}/conf/application.conf"
32+
+ " -Dhttp.port=${httpPort}"
33+
+ " -Dhttp.address=127.0.0.1"
34+
+ " -Dplay.server.provider=play.core.server.NettyServerProvider"
35+
+ " -Ddd.writer.type=MultiWriter:TraceStructureWriter:${output.getAbsolutePath()},DDAgentWriter")
36+
return processBuilder
37+
}
38+
39+
@Override
40+
File createTemporaryFile() {
41+
return new File("${buildDirectory}/tmp/trace-structure-play-2.4-appsec-netty.out")
42+
}
43+
44+
void 'API Security samples only one request per endpoint'() {
45+
given:
46+
def url = "http://localhost:${httpPort}/api_security/sampling/200?test=value"
47+
def client = OkHttpUtils.clientBuilder().build()
48+
def request = new Request.Builder()
49+
.url(url)
50+
.addHeader('X-My-Header', "value")
51+
.get()
52+
.build()
53+
54+
when:
55+
List<Response> responses = (1..3).collect {
56+
client.newCall(request).execute()
57+
}
58+
59+
then:
60+
responses.each {
61+
assert it.code() == 200
62+
}
63+
waitForTraceCount(3)
64+
def spans = rootSpans.toList().toSorted { it.span.duration }
65+
spans.size() == 3
66+
def sampledSpans = spans.findAll { it.meta.keySet().any { it.startsWith('_dd.appsec.s.req.') } }
67+
sampledSpans.size() == 1
68+
def span = sampledSpans[0]
69+
span.meta.containsKey('_dd.appsec.s.req.query')
70+
span.meta.containsKey('_dd.appsec.s.req.headers')
71+
}
72+
73+
// Ensure to clean up server and not only the shell script that starts it
74+
def cleanupSpec() {
75+
def pid = runningServerPid()
76+
if (pid) {
77+
def commands = isWindows() ? ['taskkill', '/PID', pid, '/T', '/F'] : ['kill', '-9', pid]
78+
new ProcessBuilder(commands).start().waitFor(10, SECONDS)
79+
}
80+
}
81+
82+
def runningServerPid() {
83+
def runningPid = new File(playDirectory.getPath(), 'RUNNING_PID')
84+
if (runningPid.exists()) {
85+
return Files.lines(runningPid.toPath()).findAny().orElse(null)
86+
}
87+
}
88+
89+
static isWindows() {
90+
return System.getProperty('os.name').toLowerCase().contains('win')
91+
}
92+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package controllers
2+
3+
import play.api.mvc.{Action, AnyContent, Controller}
4+
5+
class AppSecController extends Controller {
6+
7+
def apiSecuritySampling(statusCode: Int, test: String): Action[AnyContent] = Action {
8+
Status(statusCode)("EXECUTED")
9+
}
10+
11+
}

dd-smoke-tests/play-2.5/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ dependencies {
6565
implementation group: 'io.opentracing', name: 'opentracing-util', version: '0.32.0'
6666

6767
testImplementation project(':dd-smoke-tests')
68+
testImplementation project(':dd-smoke-tests:appsec')
6869
}
6970

7071
configurations.testImplementation {

dd-smoke-tests/play-2.5/conf/routes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@
66
# An example controller showing a sample home page
77
GET /welcomej controllers.JController.doGet(id: Int ?= 0)
88
GET /welcomes controllers.SController.doGet(id: Option[Int])
9+
10+
# AppSec endpoints for testing
11+
GET /api_security/sampling/:statusCode controllers.AppSecController.apiSecuritySampling(statusCode: Int, test: String)

0 commit comments

Comments
 (0)