Merge changes from dd-trace-java 0.40.0

https://github.com/DataDog/dd-trace-java/releases/tag/v0.40.0

# Conflicts:
#	dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java
#	dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpServerInstrumentationTest.groovy
#	dd-java-agent/instrumentation/dropwizard/src/test/groovy/DropwizardTest.groovy
#	dd-java-agent/instrumentation/glassfish/src/test/groovy/GlassFishServerTest.groovy
#	dd-java-agent/instrumentation/google-http-client/src/test/groovy/AbstractGoogleHttpClientTest.groovy
#	dd-java-agent/instrumentation/jetty-8/src/test/groovy/JettyHandlerTest.groovy
#	dd-java-agent/instrumentation/play-2.4/src/test/groovy/server/PlayServerTest.groovy
#	dd-java-agent/instrumentation/play-2.6/src/test/groovy/server/PlayServerTest.groovy
#	dd-java-agent/instrumentation/ratpack-1.4/src/test/groovy/server/RatpackHttpServerTest.groovy
#	dd-java-agent/instrumentation/servlet/request-2/src/test/groovy/JettyServlet2Test.groovy
#	dd-java-agent/instrumentation/servlet/request-3/src/test/groovy/AbstractServlet3Test.groovy
#	dd-java-agent/instrumentation/servlet/request-3/src/test/groovy/JettyServlet3Test.groovy
#	dd-java-agent/instrumentation/servlet/request-3/src/test/groovy/TomcatServlet3Test.groovy
#	dd-java-agent/instrumentation/spring-webmvc-3.1/src/test/groovy/test/SpringBootBasedTest.groovy
#	dd-java-agent/src/test/groovy/datadog/trace/agent/JMXFetchTest.groovy
#	dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy
#	dd-trace-api/src/main/java/datadog/trace/api/Config.java
#	dd-trace-ot/src/main/java/datadog/opentracing/DDSpan.java
#	dd-trace-ot/src/main/java/datadog/opentracing/decorators/DDDecoratorsFactory.java
#	dd-trace-ot/src/main/java/datadog/trace/common/writer/DDAgentWriter.java
#	dd-trace-ot/src/test/groovy/datadog/opentracing/decorators/SpanDecoratorTest.groovy
#	dd-trace-ot/src/test/groovy/datadog/trace/api/writer/DDAgentWriterTest.groovy
This commit is contained in:
Tyler Benson 2019-12-23 11:46:17 -08:00
commit e84e6eb25d
66 changed files with 1637 additions and 200 deletions

View File

@ -103,15 +103,19 @@ class MuzzlePlugin implements Plugin<Project> {
Task runAfter = project.tasks.muzzle
for (MuzzleDirective muzzleDirective : project.muzzle.directives) {
project.getLogger().info("configured ${muzzleDirective.assertPass ? 'pass' : 'fail'} directive: ${muzzleDirective.group}:${muzzleDirective.module}:${muzzleDirective.versions}")
project.getLogger().info("configured $muzzleDirective")
muzzleDirectiveToArtifacts(muzzleDirective, system, session).collect() { Artifact singleVersion ->
runAfter = addMuzzleTask(muzzleDirective, singleVersion, project, runAfter, bootstrapProject, toolingProject)
}
if (muzzleDirective.assertInverse) {
inverseOf(muzzleDirective, system, session).collect() { MuzzleDirective inverseDirective ->
muzzleDirectiveToArtifacts(inverseDirective, system, session).collect() { Artifact singleVersion ->
runAfter = addMuzzleTask(inverseDirective, singleVersion, project, runAfter, bootstrapProject, toolingProject)
if (muzzleDirective.coreJdk) {
runAfter = addMuzzleTask(muzzleDirective, null, project, runAfter, bootstrapProject, toolingProject)
} else {
muzzleDirectiveToArtifacts(muzzleDirective, system, session).collect() { Artifact singleVersion ->
runAfter = addMuzzleTask(muzzleDirective, singleVersion, project, runAfter, bootstrapProject, toolingProject)
}
if (muzzleDirective.assertInverse) {
inverseOf(muzzleDirective, system, session).collect() { MuzzleDirective inverseDirective ->
muzzleDirectiveToArtifacts(inverseDirective, system, session).collect() { Artifact singleVersion ->
runAfter = addMuzzleTask(inverseDirective, singleVersion, project, runAfter, bootstrapProject, toolingProject)
}
}
}
}
@ -258,17 +262,25 @@ class MuzzlePlugin implements Plugin<Project> {
* @return The created muzzle task.
*/
private static Task addMuzzleTask(MuzzleDirective muzzleDirective, Artifact versionArtifact, Project instrumentationProject, Task runAfter, Project bootstrapProject, Project toolingProject) {
def taskName = "muzzle-Assert${muzzleDirective.assertPass ? "Pass" : "Fail"}-$versionArtifact.groupId-$versionArtifact.artifactId-$versionArtifact.version${muzzleDirective.name ? "-${muzzleDirective.getNameSlug()}" : ""}"
def config = instrumentationProject.configurations.create(taskName)
def dep = instrumentationProject.dependencies.create("$versionArtifact.groupId:$versionArtifact.artifactId:$versionArtifact.version") {
transitive = true
def taskName
if (muzzleDirective.coreJdk) {
taskName = "muzzle-Assert$muzzleDirective"
} else {
taskName = "muzzle-Assert${muzzleDirective.assertPass ? "Pass" : "Fail"}-$versionArtifact.groupId-$versionArtifact.artifactId-$versionArtifact.version${muzzleDirective.name ? "-${muzzleDirective.getNameSlug()}" : ""}"
}
// The following optional transitive dependencies are brought in by some legacy module such as log4j 1.x but are no
// longer bundled with the JVM and have to be excluded for the muzzle tests to be able to run.
dep.exclude group: 'com.sun.jdmk', module: 'jmxtools'
dep.exclude group: 'com.sun.jmx', module: 'jmxri'
def config = instrumentationProject.configurations.create(taskName)
config.dependencies.add(dep)
if (!muzzleDirective.coreJdk) {
def dep = instrumentationProject.dependencies.create("$versionArtifact.groupId:$versionArtifact.artifactId:$versionArtifact.version") {
transitive = true
}
// The following optional transitive dependencies are brought in by some legacy module such as log4j 1.x but are no
// longer bundled with the JVM and have to be excluded for the muzzle tests to be able to run.
dep.exclude group: 'com.sun.jdmk', module: 'jmxtools'
dep.exclude group: 'com.sun.jmx', module: 'jmxri'
config.dependencies.add(dep)
}
for (String additionalDependency : muzzleDirective.additionalDependencies) {
config.dependencies.add(instrumentationProject.dependencies.create(additionalDependency) {
transitive = true
@ -369,6 +381,11 @@ class MuzzleDirective {
List<String> additionalDependencies = new ArrayList<>()
boolean assertPass
boolean assertInverse = false
boolean coreJdk = false
void coreJdk() {
coreJdk = true
}
/**
* Adds extra dependencies to the current muzzle test.
@ -391,6 +408,14 @@ class MuzzleDirective {
return name.trim().replaceAll("[^a-zA-Z0-9]+", "-")
}
String toString() {
if (coreJdk) {
return "${assertPass ? 'Pass' : 'Fail'}-core-jdk"
} else {
return "${assertPass ? 'pass' : 'fail'} $group:$module:$versions"
}
}
}
/**

View File

@ -16,6 +16,7 @@ import org.slf4j.LoggerFactory;
* <p>The intention is for this class to be loaded by bootstrap classloader to make sure we have
* unimpeded access to the rest of Datadog's agent parts.
*/
// We cannot use lombok here because we need to configure logger first
public class Agent {
private static final String SIMPLE_LOGGER_SHOW_DATE_TIME_PROPERTY =
@ -28,18 +29,18 @@ public class Agent {
"datadog.slf4j.simpleLogger.defaultLogLevel";
// We cannot use lombok here because we need to configure logger first
private static final Logger LOGGER;
private static final Logger log;
static {
// We can configure logger here because datadog.trace.agent.AgentBootstrap doesn't touch it.
configureLogger();
LOGGER = LoggerFactory.getLogger(Agent.class);
log = LoggerFactory.getLogger(Agent.class);
}
// fields must be managed under class lock
private static ClassLoader AGENT_CLASSLOADER = null;
public static void start(final Instrumentation inst, final URL bootstrapURL) throws Exception {
public static void start(final Instrumentation inst, final URL bootstrapURL) {
startDatadogAgent(inst, bootstrapURL);
final boolean appUsingCustomLogManager = isAppUsingCustomLogManager();
@ -64,28 +65,30 @@ public class Agent {
* logging facility.
*/
if (isJavaBefore9WithJFR() && appUsingCustomLogManager) {
LOGGER.debug("Custom logger detected. Delaying Datadog Tracer initialization.");
registerLogManagerCallback(new InstallDatadogTracerCallback(inst, bootstrapURL));
log.debug("Custom logger detected. Delaying Datadog Tracer initialization.");
registerLogManagerCallback(new InstallDatadogTracerCallback(bootstrapURL));
} else {
installDatadogTracer(inst, bootstrapURL);
installDatadogTracer();
}
}
private static void registerLogManagerCallback(final Runnable callback) throws Exception {
final Class<?> agentInstallerClass =
AGENT_CLASSLOADER.loadClass("datadog.trace.agent.tooling.AgentInstaller");
final Method registerCallbackMethod =
agentInstallerClass.getMethod("registerClassLoadCallback", String.class, Runnable.class);
registerCallbackMethod.invoke(null, "java.util.logging.LogManager", callback);
private static void registerLogManagerCallback(final ClassLoadCallBack callback) {
try {
final Class<?> agentInstallerClass =
AGENT_CLASSLOADER.loadClass("datadog.trace.agent.tooling.AgentInstaller");
final Method registerCallbackMethod =
agentInstallerClass.getMethod("registerClassLoadCallback", String.class, Runnable.class);
registerCallbackMethod.invoke(null, "java.util.logging.LogManager", callback);
} catch (final Exception ex) {
log.error("Error registering callback for " + callback.getName(), ex);
}
}
protected abstract static class ClassLoadCallBack implements Runnable {
final Instrumentation inst;
final URL bootstrapURL;
ClassLoadCallBack(final Instrumentation inst, final URL bootstrapURL) {
this.inst = inst;
ClassLoadCallBack(final URL bootstrapURL) {
this.bootstrapURL = bootstrapURL;
}
@ -104,7 +107,7 @@ public class Agent {
try {
execute();
} catch (final Exception e) {
LOGGER.error("Failed to run class loader callback {}", getName(), e);
log.error("Failed to run class loader callback {}", getName(), e);
}
}
});
@ -115,12 +118,12 @@ public class Agent {
public abstract String getName();
public abstract void execute() throws Exception;
public abstract void execute();
}
protected static class InstallDatadogTracerCallback extends ClassLoadCallBack {
InstallDatadogTracerCallback(final Instrumentation inst, final URL bootstrapURL) {
super(inst, bootstrapURL);
InstallDatadogTracerCallback(final URL bootstrapURL) {
super(bootstrapURL);
}
@Override
@ -129,18 +132,18 @@ public class Agent {
}
@Override
public void execute() throws Exception {
installDatadogTracer(inst, bootstrapURL);
public void execute() {
installDatadogTracer();
}
}
private static synchronized void startDatadogAgent(
final Instrumentation inst, final URL bootstrapURL) throws Exception {
final Instrumentation inst, final URL bootstrapURL) {
if (AGENT_CLASSLOADER == null) {
final ClassLoader agentClassLoader =
createDatadogClassLoader("agent-tooling-and-instrumentation.isolated", bootstrapURL);
final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
try {
final ClassLoader agentClassLoader =
createDatadogClassLoader("agent-tooling-and-instrumentation.isolated", bootstrapURL);
Thread.currentThread().setContextClassLoader(agentClassLoader);
final Class<?> agentInstallerClass =
agentClassLoader.loadClass("datadog.trace.agent.tooling.AgentInstaller");
@ -148,14 +151,15 @@ public class Agent {
agentInstallerClass.getMethod("installBytebuddyAgent", Instrumentation.class);
agentInstallerMethod.invoke(null, inst);
AGENT_CLASSLOADER = agentClassLoader;
} catch (final Throwable ex) {
log.error("Throwable thrown while installing the Datadog Agent", ex);
} finally {
Thread.currentThread().setContextClassLoader(contextLoader);
}
}
}
private static synchronized void installDatadogTracer(
final Instrumentation inst, final URL bootstrapURL) throws Exception {
private static synchronized void installDatadogTracer() {
if (AGENT_CLASSLOADER == null) {
throw new IllegalStateException("Datadog agent should have been started already");
}
@ -171,6 +175,8 @@ public class Agent {
tracerInstallerMethod.invoke(null);
final Method logVersionInfoMethod = tracerInstallerClass.getMethod("logVersionInfo");
logVersionInfoMethod.invoke(null);
} catch (final Throwable ex) {
log.error("Throwable thrown while installing the Datadog Tracer", ex);
} finally {
Thread.currentThread().setContextClassLoader(contextLoader);
}
@ -263,8 +269,8 @@ public class Agent {
System.getenv(tracerCustomLogManSysprop.replace('.', '_').toUpperCase());
if (customLogManagerProp != null || customLogManagerEnv != null) {
LOGGER.debug("Prop - customlogmanager: " + customLogManagerProp);
LOGGER.debug("Env - customlogmanager: " + customLogManagerEnv);
log.debug("Prop - customlogmanager: " + customLogManagerProp);
log.debug("Env - customlogmanager: " + customLogManagerEnv);
// Allow setting to skip these automatic checks:
return Boolean.parseBoolean(customLogManagerProp)
|| Boolean.parseBoolean(customLogManagerEnv);
@ -272,7 +278,7 @@ public class Agent {
final String jbossHome = System.getenv("JBOSS_HOME");
if (jbossHome != null) {
LOGGER.debug("Env - jboss: " + jbossHome);
log.debug("Env - jboss: " + jbossHome);
// JBoss/Wildfly is known to set a custom log manager after startup.
// Originally we were checking for the presence of a jboss class,
// but it seems some non-jboss applications have jboss classes on the classpath.
@ -285,8 +291,8 @@ public class Agent {
if (logManagerProp != null) {
final boolean onSysClasspath =
ClassLoader.getSystemResource(logManagerProp.replaceAll("\\.", "/") + ".class") != null;
LOGGER.debug("Prop - logging.manager: " + logManagerProp);
LOGGER.debug("logging.manager on system classpath: " + onSysClasspath);
log.debug("Prop - logging.manager: " + logManagerProp);
log.debug("logging.manager on system classpath: " + onSysClasspath);
// Some applications set java.util.logging.manager but never actually initialize the logger.
// Check to see if the configured manager is on the system classpath.
// If so, it should be safe to initialize jmxfetch which will setup the log manager.

View File

@ -0,0 +1,24 @@
package datadog.trace.bootstrap.instrumentation.rmi;
import datadog.trace.instrumentation.api.AgentSpan;
public class ThreadLocalContext {
public static final ThreadLocalContext THREAD_LOCAL_CONTEXT = new ThreadLocalContext();
private final ThreadLocal<AgentSpan.Context> local;
public ThreadLocalContext() {
local = new ThreadLocal<>();
}
public void set(final AgentSpan.Context context) {
local.set(context);
}
public AgentSpan.Context getAndResetContext() {
final AgentSpan.Context context = local.get();
if (context != null) {
local.remove();
}
return context;
}
}

View File

@ -92,6 +92,7 @@ public class AgentInstaller {
not(
named("java.net.URL")
.or(named("java.net.HttpURLConnection"))
.or(nameStartsWith("java.rmi."))
.or(nameStartsWith("java.util.concurrent."))
.or(
nameStartsWith("java.util.logging.")
@ -111,6 +112,8 @@ public class AgentInstaller {
.and(
not(
nameStartsWith("sun.net.www.protocol.")
.or(nameStartsWith("sun.rmi.server"))
.or(nameStartsWith("sun.rmi.transport"))
.or(named("sun.net.www.http.HttpClient")))))
.or(nameStartsWith("jdk."))
.or(nameStartsWith("org.aspectj."))

View File

@ -294,7 +294,11 @@ public final class AgentTracerImpl implements TracerAPI {
private Extractor(final C carrier, final Getter<C> getter) {
extracted = new HashMap<>();
for (final String key : getter.keys(carrier)) {
extracted.put(key, getter.get(carrier, key));
// extracted header value
String s = getter.get(carrier, key);
// in case of multiple values in the header, need to parse
if (s != null) s = s.split(",")[0].trim();
extracted.put(key, s);
}
}

View File

@ -58,6 +58,9 @@ abstract class AkkaHttpServerInstrumentationTest extends HttpServerTest<Object,
"error.type" { it == null || it == Exception.name }
"error.stack" { it == null || it instanceof String }
}
if (endpoint.query) {
"$DDTags.HTTP_QUERY" endpoint.query
}
}
}
}

View File

@ -24,6 +24,7 @@ object AkkaHttpTestAsyncWebServer {
val resp = HttpResponse(status = endpoint.getStatus) //.withHeaders(headers.Type)resp.contentType = "text/plain"
endpoint match {
case SUCCESS => resp.withEntity(endpoint.getBody)
case QUERY_PARAM => resp.withEntity(uri.queryString().orNull)
case REDIRECT => resp.withHeaders(headers.Location(endpoint.getBody))
case ERROR => resp.withEntity(endpoint.getBody)
case EXCEPTION => throw new Exception(endpoint.getBody)

View File

@ -23,6 +23,7 @@ object AkkaHttpTestSyncWebServer {
val resp = HttpResponse(status = endpoint.getStatus)
endpoint match {
case SUCCESS => resp.withEntity(endpoint.getBody)
case QUERY_PARAM => resp.withEntity(uri.queryString().orNull)
case REDIRECT => resp.withHeaders(headers.Location(endpoint.getBody))
case ERROR => resp.withEntity(endpoint.getBody)
case EXCEPTION => throw new Exception(endpoint.getBody)

View File

@ -23,6 +23,8 @@ object AkkaHttpTestWebServer {
val route = { //handleExceptions(exceptionHandler) {
path(SUCCESS.rawPath) {
complete(HttpResponse(status = SUCCESS.getStatus).withEntity(SUCCESS.getBody))
} ~ path(QUERY_PARAM.rawPath) {
complete(HttpResponse(status = QUERY_PARAM.getStatus).withEntity(SUCCESS.getBody))
} ~ path(REDIRECT.rawPath) {
redirect(Uri(REDIRECT.getBody), StatusCodes.Found)
} ~ path(ERROR.rawPath) {

View File

@ -4,6 +4,7 @@ import io.dropwizard.setup.Bootstrap
import io.dropwizard.setup.Environment
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.QueryParam
import javax.ws.rs.container.AsyncResponse
import javax.ws.rs.container.Suspended
import javax.ws.rs.core.Response
@ -12,6 +13,7 @@ import java.util.concurrent.Executors
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -56,6 +58,14 @@ class DropwizardAsyncTest extends DropwizardTest {
}
}
@GET
@Path("query")
Response query_param(@QueryParam("some") String param) {
controller(QUERY_PARAM) {
Response.status(QUERY_PARAM.status).entity("some=$param".toString()).build()
}
}
@GET
@Path("redirect")
void redirect(@Suspended final AsyncResponse asyncResponse) {

View File

@ -12,14 +12,15 @@ import io.dropwizard.setup.Bootstrap
import io.dropwizard.setup.Environment
import io.dropwizard.testing.ConfigOverride
import io.dropwizard.testing.DropwizardTestSupport
import org.eclipse.jetty.servlet.ServletHandler
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.QueryParam
import javax.ws.rs.core.Response
import org.eclipse.jetty.servlet.ServletHandler
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -121,6 +122,9 @@ class DropwizardTest extends HttpServerTest<DropwizardTestSupport, Servlet3Decor
"error.type" { it == null || it == Exception.name }
"error.stack" { it == null || it instanceof String }
}
if (endpoint.query) {
"$DDTags.HTTP_QUERY" endpoint.query
}
}
}
}
@ -150,6 +154,14 @@ class DropwizardTest extends HttpServerTest<DropwizardTestSupport, Servlet3Decor
}
}
@GET
@Path("query")
Response query_param(@QueryParam("some") String param) {
controller(QUERY_PARAM) {
Response.status(QUERY_PARAM.status).entity("some=$param".toString()).build()
}
}
@GET
@Path("redirect")
Response redirect() {

View File

@ -110,6 +110,9 @@ class GlassFishServerTest extends HttpServerTest<GlassFish, Servlet3Decorator> {
"error.type" { it == null || it == Exception.name }
"error.stack" { it == null || it instanceof String }
}
if (endpoint.query) {
"$DDTags.HTTP_QUERY" endpoint.query
}
}
}
}

View File

@ -26,6 +26,25 @@ public class TestServlets {
}
}
@WebServlet("/query")
public static class Query extends HttpServlet {
@Override
protected void service(final HttpServletRequest req, final HttpServletResponse resp) {
final HttpServerTest.ServerEndpoint endpoint =
HttpServerTest.ServerEndpoint.forPath(req.getServletPath());
HttpServerTest.controller(
endpoint,
new Closure(null) {
public Object doCall() throws Exception {
resp.setContentType("text/plain");
resp.setStatus(endpoint.getStatus());
resp.getWriter().print(req.getQueryString());
return null;
}
});
}
}
@WebServlet("/redirect")
public static class Redirect extends HttpServlet {
@Override

View File

@ -16,7 +16,14 @@ public class GrizzlyDecorator extends HttpServerDecorator<Request, Request, Resp
@Override
protected URI url(final Request request) throws URISyntaxException {
return new URI(request.getRequestURL().toString());
return new URI(
request.getScheme(),
null,
request.getServerName(),
request.getServerPort(),
request.getRequestURI(),
request.getQueryString(),
null);
}
@Override

View File

@ -1,17 +1,19 @@
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.QueryParam
import javax.ws.rs.container.AsyncResponse
import javax.ws.rs.container.Suspended
import javax.ws.rs.core.Response
import org.glassfish.grizzly.http.server.HttpServer
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory
import org.glassfish.jersey.server.ResourceConfig
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.container.AsyncResponse
import javax.ws.rs.container.Suspended
import javax.ws.rs.core.Response
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -44,6 +46,14 @@ class GrizzlyAsyncTest extends GrizzlyTest {
}
}
@GET
@Path("query")
Response query_param(@QueryParam("some") String param, @Suspended AsyncResponse ar) {
controller(QUERY_PARAM) {
ar.resume(Response.status(QUERY_PARAM.status).entity("some=$param".toString()).build())
}
}
@GET
@Path("redirect")
void redirect(@Suspended AsyncResponse ar) {

View File

@ -1,17 +1,18 @@
import datadog.trace.agent.test.base.HttpServerTest
import datadog.trace.instrumentation.grizzly.GrizzlyDecorator
import javax.ws.rs.GET
import javax.ws.rs.NotFoundException
import javax.ws.rs.Path
import javax.ws.rs.QueryParam
import javax.ws.rs.core.Response
import javax.ws.rs.ext.ExceptionMapper
import org.glassfish.grizzly.http.server.HttpServer
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory
import org.glassfish.jersey.server.ResourceConfig
import javax.ws.rs.GET
import javax.ws.rs.NotFoundException
import javax.ws.rs.Path
import javax.ws.rs.core.Response
import javax.ws.rs.ext.ExceptionMapper
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -66,6 +67,14 @@ class GrizzlyTest extends HttpServerTest<HttpServer, GrizzlyDecorator> {
}
}
@GET
@Path("query")
Response query_param(@QueryParam("some") String param) {
controller(QUERY_PARAM) {
Response.status(QUERY_PARAM.status).entity("some=$param".toString()).build()
}
}
@GET
@Path("redirect")
Response redirect() {

View File

@ -1,3 +1,9 @@
muzzle {
pass {
coreJdk()
}
}
apply from: "${rootDir}/gradle/java.gradle"
dependencies {

View File

@ -1,3 +1,10 @@
// This won't work until the akka and scala integrations are split into separate projects.
//muzzle {
// pass {
// coreJdk()
// }
//}
apply from: "${rootDir}/gradle/java.gradle"
apply from: "${rootDir}/gradle/test-with-scala.gradle"
apply from: "${rootDir}/gradle/test-with-kotlin.gradle"

View File

@ -8,10 +8,11 @@ import static net.bytebuddy.matcher.ElementMatchers.returns;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import java.util.Map;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Client;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.matcher.ElementMatcher;
@AutoService(Instrumenter.class)
@ -48,9 +49,13 @@ public final class JaxRsClientInstrumentation extends Instrumenter.Default {
public static class ClientBuilderAdvice {
@Advice.OnMethodEnter
public static void registerFeature(@Advice.This final ClientBuilder builder) {
builder.register(ClientTracingFeature.class);
@Advice.OnMethodExit
public static void registerFeature(
@Advice.Return(typing = Assigner.Typing.DYNAMIC) final Client client) {
// Register on the generated client instead of the builder
// The build() can be called multiple times and is not thread safe
// A client is only created once
client.register(ClientTracingFeature.class);
}
}
}

View File

@ -0,0 +1,54 @@
import datadog.trace.agent.test.AgentTestRunner
import org.glassfish.jersey.client.JerseyClientBuilder
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.util.concurrent.AsyncConditions
import javax.ws.rs.client.Client
import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer
class JaxMultithreadedClientTest extends AgentTestRunner {
@AutoCleanup
@Shared
def server = httpServer {
handlers {
prefix("success") {
String msg = "Hello."
response.status(200).send(msg)
}
}
}
def "multiple threads using the same builder works"() {
given:
def conds = new AsyncConditions(10)
def uri = server.address.resolve("/success")
def builder = new JerseyClientBuilder()
// Start 10 threads and do 50 requests each
when:
(1..10).each {
Thread.start {
boolean hadErrors = (1..50).any {
try {
Client client = builder.build()
client.target(uri).request().get()
} catch (Exception e) {
e.printStackTrace()
return true
}
return false
}
conds.evaluate {
assert !hadErrors
}
}
}
then:
conds.await(10)
}
}

View File

@ -1,3 +1,9 @@
muzzle {
pass {
coreJdk()
}
}
apply from: "${rootDir}/gradle/java.gradle"
apply plugin: 'org.unbroken-dome.test-sets'

View File

@ -28,7 +28,14 @@ public class JettyDecorator
@Override
protected URI url(final HttpServletRequest httpServletRequest) throws URISyntaxException {
return new URI(httpServletRequest.getRequestURL().toString());
return new URI(
httpServletRequest.getScheme(),
null,
httpServletRequest.getServerName(),
httpServletRequest.getServerPort(),
httpServletRequest.getRequestURI(),
httpServletRequest.getQueryString(),
null);
}
@Override

View File

@ -4,19 +4,19 @@ import datadog.trace.api.DDSpanTypes
import datadog.trace.api.DDTags
import datadog.trace.instrumentation.api.Tags
import datadog.trace.instrumentation.jetty8.JettyDecorator
import javax.servlet.DispatcherType
import javax.servlet.ServletException
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import org.eclipse.jetty.server.Request
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.handler.AbstractHandler
import org.eclipse.jetty.server.handler.ErrorHandler
import javax.servlet.DispatcherType
import javax.servlet.ServletException
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -79,6 +79,10 @@ class JettyHandlerTest extends HttpServerTest<Server, JettyDecorator> {
response.status = endpoint.status
response.writer.print(endpoint.body)
break
case QUERY_PARAM:
response.status = endpoint.status
response.writer.print(request.queryString)
break
case REDIRECT:
response.sendRedirect(endpoint.body)
break
@ -138,6 +142,9 @@ class JettyHandlerTest extends HttpServerTest<Server, JettyDecorator> {
"error.type" { it == null || it == Exception.name }
"error.stack" { it == null || it instanceof String }
}
if (endpoint.query) {
"$DDTags.HTTP_QUERY" endpoint.query
}
}
}
}

View File

@ -59,6 +59,7 @@ public class TracingIterator implements Iterator<ConsumerRecord> {
decorator.afterStart(span);
decorator.onConsume(span, next);
currentScope = activateSpan(span, true);
currentScope.setAsyncPropagation(true);
}
} catch (final Exception e) {
log.debug("Error during decoration", e);

View File

@ -81,7 +81,7 @@ public class KafkaStreamsProcessorInstrumentation {
CONSUMER_DECORATE.afterStart(span);
CONSUMER_DECORATE.onConsume(span, record);
activateSpan(span, true);
activateSpan(span, true).setAsyncPropagation(true);
}
}
}
@ -119,6 +119,7 @@ public class KafkaStreamsProcessorInstrumentation {
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(@Advice.Thrown final Throwable throwable) {
// This is dangerous... we assume the span/scope is the one we expect, but it may not be.
final AgentSpan span = activeSpan();
if (span != null) {
CONSUMER_DECORATE.onError(span, throwable);

View File

@ -24,6 +24,7 @@ import io.netty.util.CharsetUtil
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH
@ -48,7 +49,8 @@ class Netty40ServerTest extends HttpServerTest<EventLoopGroup, NettyHttpServerDe
pipeline.addLast([
channelRead0 : { ctx, msg ->
if (msg instanceof HttpRequest) {
ServerEndpoint endpoint = ServerEndpoint.forPath((msg as HttpRequest).uri)
def uri = URI.create((msg as HttpRequest).uri)
ServerEndpoint endpoint = ServerEndpoint.forPath(uri.path)
ctx.write controller(endpoint) {
ByteBuf content = null
FullHttpResponse response = null
@ -58,6 +60,10 @@ class Netty40ServerTest extends HttpServerTest<EventLoopGroup, NettyHttpServerDe
content = Unpooled.copiedBuffer(endpoint.body, CharsetUtil.UTF_8)
response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status), content)
break
case QUERY_PARAM:
content = Unpooled.copiedBuffer(uri.query, CharsetUtil.UTF_8)
response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status), content)
break
case REDIRECT:
response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status))
response.headers().set(HttpHeaders.Names.LOCATION, endpoint.body)

View File

@ -23,6 +23,7 @@ import io.netty.util.CharsetUtil
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH
@ -47,7 +48,8 @@ class Netty41ServerTest extends HttpServerTest<EventLoopGroup, NettyHttpServerDe
pipeline.addLast([
channelRead0 : { ctx, msg ->
if (msg instanceof HttpRequest) {
ServerEndpoint endpoint = ServerEndpoint.forPath((msg as HttpRequest).uri)
def uri = URI.create((msg as HttpRequest).uri)
ServerEndpoint endpoint = ServerEndpoint.forPath(uri.path)
ctx.write controller(endpoint) {
ByteBuf content = null
FullHttpResponse response = null
@ -57,6 +59,10 @@ class Netty41ServerTest extends HttpServerTest<EventLoopGroup, NettyHttpServerDe
content = Unpooled.copiedBuffer(endpoint.body, CharsetUtil.UTF_8)
response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status), content)
break
case QUERY_PARAM:
content = Unpooled.copiedBuffer(uri.query, CharsetUtil.UTF_8)
response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status), content)
break
case REDIRECT:
response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status))
response.headers().set(HttpHeaderNames.LOCATION, endpoint.body)

View File

@ -10,6 +10,7 @@ import java.util.function.Supplier
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -24,6 +25,13 @@ class PlayAsyncServerTest extends PlayServerTest {
Results.status(SUCCESS.getStatus(), SUCCESS.getBody())
}
}, HttpExecution.defaultContext())
} as Supplier)
.GET(QUERY_PARAM.getPath()).routeAsync({
CompletableFuture.supplyAsync({
controller(QUERY_PARAM) {
Results.status(QUERY_PARAM.getStatus(), QUERY_PARAM.getBody())
}
}, HttpExecution.defaultContext())
} as Supplier)
.GET(REDIRECT.getPath()).routeAsync({
CompletableFuture.supplyAsync({

View File

@ -16,6 +16,7 @@ import java.util.function.Supplier
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -28,6 +29,11 @@ class PlayServerTest extends HttpServerTest<Server, NettyHttpServerDecorator> {
controller(SUCCESS) {
Results.status(SUCCESS.getStatus(), SUCCESS.getBody())
}
} as Supplier)
.GET(QUERY_PARAM.getPath()).routeTo({
controller(QUERY_PARAM) {
Results.status(QUERY_PARAM.getStatus(), QUERY_PARAM.getBody())
}
} as Supplier)
.GET(REDIRECT.getPath()).routeTo({
controller(REDIRECT) {
@ -96,6 +102,9 @@ class PlayServerTest extends HttpServerTest<Server, NettyHttpServerDecorator> {
if (endpoint == EXCEPTION) {
errorTags(Exception, EXCEPTION.body)
}
if (endpoint.query) {
"$DDTags.HTTP_QUERY" endpoint.query
}
}
}
}

View File

@ -14,6 +14,7 @@ import java.util.function.Supplier
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -36,6 +37,13 @@ class PlayAsyncServerTest extends PlayServerTest {
Results.status(SUCCESS.getStatus(), SUCCESS.getBody())
}
}, execContext)
} as Supplier)
.GET(QUERY_PARAM.getPath()).routeAsync({
CompletableFuture.supplyAsync({
controller(QUERY_PARAM) {
Results.status(QUERY_PARAM.getStatus(), QUERY_PARAM.getBody())
}
}, execContext)
} as Supplier)
.GET(REDIRECT.getPath()).routeAsync({
CompletableFuture.supplyAsync({

View File

@ -18,6 +18,7 @@ import java.util.function.Supplier
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -30,6 +31,11 @@ class PlayServerTest extends HttpServerTest<Server, AkkaHttpServerDecorator> {
controller(SUCCESS) {
Results.status(SUCCESS.getStatus(), SUCCESS.getBody())
}
} as Supplier)
.GET(QUERY_PARAM.getPath()).routeTo({
controller(QUERY_PARAM) {
Results.status(QUERY_PARAM.getStatus(), QUERY_PARAM.getBody())
}
} as Supplier)
.GET(REDIRECT.getPath()).routeTo({
controller(REDIRECT) {
@ -98,6 +104,9 @@ class PlayServerTest extends HttpServerTest<Server, AkkaHttpServerDecorator> {
if (endpoint == EXCEPTION) {
errorTags(Exception, EXCEPTION.body)
}
if (endpoint.query) {
"$DDTags.HTTP_QUERY" endpoint.query
}
}
}
}
@ -124,6 +133,9 @@ class PlayServerTest extends HttpServerTest<Server, AkkaHttpServerDecorator> {
"error.type" { it == null || it == Exception.name }
"error.stack" { it == null || it instanceof String }
}
if (endpoint.query) {
"$DDTags.HTTP_QUERY" endpoint.query
}
}
}
}

View File

@ -7,6 +7,7 @@ import datadog.trace.instrumentation.api.AgentSpan;
import java.net.URI;
import lombok.extern.slf4j.Slf4j;
import ratpack.handling.Context;
import ratpack.http.HttpUrlBuilder;
import ratpack.http.Request;
import ratpack.http.Response;
import ratpack.http.Status;
@ -37,7 +38,9 @@ public class RatpackServerDecorator extends HttpServerDecorator<Request, Request
// This call implicitly uses request via a threadlocal provided by ratpack.
final PublicAddress publicAddress =
PublicAddress.inferred(address.getPort() == 443 ? "https" : "http");
return publicAddress.get(request.getPath());
final HttpUrlBuilder url =
publicAddress.builder().path(request.getPath()).params(request.getQueryParams());
return url.build();
}
@Override

View File

@ -6,6 +6,7 @@ import ratpack.test.embed.EmbeddedApp
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -32,6 +33,17 @@ class RatpackAsyncHttpServerTest extends RatpackHttpServerTest {
}
}
}
prefix(QUERY_PARAM.rawPath()) {
all {
Promise.sync {
QUERY_PARAM
} then { ServerEndpoint endpoint ->
controller(endpoint) {
context.response.status(endpoint.status).send(request.query)
}
}
}
}
prefix(REDIRECT.rawPath()) {
all {
Promise.sync {

View File

@ -6,6 +6,7 @@ import ratpack.test.embed.EmbeddedApp
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -32,6 +33,17 @@ class RatpackForkedHttpServerTest extends RatpackHttpServerTest {
}
}
}
prefix(QUERY_PARAM.rawPath()) {
all {
Promise.sync {
QUERY_PARAM
}.fork().then { ServerEndpoint endpoint ->
controller(endpoint) {
context.response.status(endpoint.status).send(request.query)
}
}
}
}
prefix(REDIRECT.rawPath()) {
all {
Promise.sync {

View File

@ -15,6 +15,7 @@ import ratpack.test.embed.EmbeddedApp
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -37,6 +38,13 @@ class RatpackHttpServerTest extends HttpServerTest<EmbeddedApp, NettyHttpServerD
}
}
}
prefix(QUERY_PARAM.rawPath()) {
all {
controller(QUERY_PARAM) {
context.response.status(QUERY_PARAM.status).send(request.query)
}
}
}
prefix(REDIRECT.rawPath()) {
all {
controller(REDIRECT) {
@ -118,6 +126,9 @@ class RatpackHttpServerTest extends HttpServerTest<EmbeddedApp, NettyHttpServerD
if (endpoint == EXCEPTION) {
errorTags(Exception, EXCEPTION.body)
}
if (endpoint.query) {
"$DDTags.HTTP_QUERY" endpoint.query
}
}
}
}
@ -143,6 +154,9 @@ class RatpackHttpServerTest extends HttpServerTest<EmbeddedApp, NettyHttpServerD
"$Tags.HTTP_URL" "${endpoint.resolve(address)}"
"$Tags.HTTP_METHOD" method
"$Tags.HTTP_STATUS" endpoint.status
if (endpoint.query) {
"$DDTags.HTTP_QUERY" endpoint.query
}
}
}
}

View File

@ -0,0 +1,15 @@
muzzle {
pass {
coreJdk()
}
}
apply from: "${rootDir}/gradle/java.gradle"
task "rmic", dependsOn: testClasses {
def clazz = 'rmi.app.ServerLegacy'
String command = """rmic -g -keep -classpath ${sourceSets.test.output.classesDirs.asPath} -d ${buildDir}/classes/java/test ${clazz}"""
command.execute().text
}
test.dependsOn "rmic"

View File

@ -0,0 +1,28 @@
package datadog.trace.instrumentation.rmi.client;
import datadog.trace.agent.decorator.ClientDecorator;
import datadog.trace.api.DDSpanTypes;
public class RmiClientDecorator extends ClientDecorator {
public static final RmiClientDecorator DECORATE = new RmiClientDecorator();
@Override
protected String[] instrumentationNames() {
return new String[] {"rmi"};
}
@Override
protected String spanType() {
return DDSpanTypes.RPC;
}
@Override
protected String component() {
return "rmi-client";
}
@Override
protected String service() {
return "rmi";
}
}

View File

@ -0,0 +1,83 @@
package datadog.trace.instrumentation.rmi.client;
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static datadog.trace.instrumentation.api.AgentTracer.activateSpan;
import static datadog.trace.instrumentation.api.AgentTracer.activeSpan;
import static datadog.trace.instrumentation.api.AgentTracer.startSpan;
import static datadog.trace.instrumentation.rmi.client.RmiClientDecorator.DECORATE;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.api.DDTags;
import datadog.trace.instrumentation.api.AgentScope;
import datadog.trace.instrumentation.api.AgentSpan;
import java.lang.reflect.Method;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@AutoService(Instrumenter.class)
public final class RmiClientInstrumentation extends Instrumenter.Default {
public RmiClientInstrumentation() {
super("rmi", "rmi-client");
}
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return not(isInterface()).and(safeHasSuperType(named("sun.rmi.server.UnicastRef")));
}
@Override
public String[] helperClassNames() {
return new String[] {
"datadog.trace.agent.decorator.BaseDecorator",
"datadog.trace.agent.decorator.ClientDecorator",
packageName + ".RmiClientDecorator"
};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
isMethod()
.and(named("invoke"))
.and(takesArgument(0, named("java.rmi.Remote")))
.and(takesArgument(1, named("java.lang.reflect.Method"))),
getClass().getName() + "$RmiClientAdvice");
}
public static class RmiClientAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static AgentScope onEnter(@Advice.Argument(value = 1) final Method method) {
if (activeSpan() == null) {
return null;
}
final AgentSpan span =
startSpan("rmi.invoke")
.setTag(DDTags.RESOURCE_NAME, DECORATE.spanNameForMethod(method))
.setTag("span.origin.type", method.getDeclaringClass().getCanonicalName());
DECORATE.afterStart(span);
return activateSpan(span, true);
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) {
if (scope == null) {
return;
}
DECORATE.onError(scope, throwable);
scope.close();
}
}
}

View File

@ -0,0 +1,71 @@
package datadog.trace.instrumentation.rmi.context;
import static datadog.trace.instrumentation.api.AgentTracer.propagate;
import datadog.trace.instrumentation.api.AgentPropagation;
import datadog.trace.instrumentation.api.AgentSpan;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.HashMap;
import java.util.Map;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
/** ContextPayload wraps context information shared between client and server */
@Slf4j
public class ContextPayload {
@Getter private final Map<String, String> context;
public static final ExtractAdapter GETTER = new ExtractAdapter();
public static final InjectAdapter SETTER = new InjectAdapter();
public ContextPayload() {
context = new HashMap<>();
}
public ContextPayload(final Map<String, String> context) {
this.context = context;
}
public static ContextPayload from(final AgentSpan span) {
final ContextPayload payload = new ContextPayload();
propagate().inject(span, payload, SETTER);
return payload;
}
public static ContextPayload read(final ObjectInput oi) throws IOException {
try {
final Object object = oi.readObject();
if (object instanceof Map) {
return new ContextPayload((Map<String, String>) object);
}
} catch (final ClassCastException | ClassNotFoundException ex) {
log.debug("Error reading object", ex);
}
return null;
}
public void write(final ObjectOutput out) throws IOException {
out.writeObject(context);
}
public static class ExtractAdapter implements AgentPropagation.Getter<ContextPayload> {
@Override
public Iterable<String> keys(final ContextPayload carrier) {
return carrier.getContext().keySet();
}
@Override
public String get(final ContextPayload carrier, final String key) {
return carrier.getContext().get(key);
}
}
public static class InjectAdapter implements AgentPropagation.Setter<ContextPayload> {
@Override
public void set(final ContextPayload carrier, final String key, final String value) {
carrier.getContext().put(key, value);
}
}
}

View File

@ -0,0 +1,112 @@
package datadog.trace.instrumentation.rmi.context;
import datadog.trace.bootstrap.ContextStore;
import datadog.trace.instrumentation.api.AgentSpan;
import java.io.IOException;
import java.io.ObjectOutput;
import java.rmi.NoSuchObjectException;
import java.rmi.server.ObjID;
import lombok.extern.slf4j.Slf4j;
import sun.rmi.transport.Connection;
import sun.rmi.transport.StreamRemoteCall;
import sun.rmi.transport.TransportConstants;
@Slf4j
public class ContextPropagator {
// Internal RMI object ids that we don't want to trace
private static final ObjID ACTIVATOR_ID = new ObjID(ObjID.ACTIVATOR_ID);
private static final ObjID DGC_ID = new ObjID(ObjID.DGC_ID);
private static final ObjID REGISTRY_ID = new ObjID(ObjID.REGISTRY_ID);
// RMI object id used to identify DataDog instrumentation
public static final ObjID DD_CONTEXT_CALL_ID = new ObjID("Datadog.v1.context_call".hashCode());
// Operation id used for checking context propagation is possible
// RMI expects these operations to have negative identifier, as positive ones mean legacy
// precompiled Stubs would be used instead
private static final int CONTEXT_CHECK_CALL_OPERATION_ID = -1;
// Seconds step of context propagation which contains actual payload
private static final int CONTEXT_PAYLOAD_OPERATION_ID = -2;
public static final ContextPropagator PROPAGATOR = new ContextPropagator();
public boolean isRMIInternalObject(final ObjID id) {
return ACTIVATOR_ID.equals(id) || DGC_ID.equals(id) || REGISTRY_ID.equals(id);
}
public boolean isOperationWithPayload(final int operationId) {
return operationId == CONTEXT_PAYLOAD_OPERATION_ID;
}
public void attemptToPropagateContext(
final ContextStore<Connection, Boolean> knownConnections,
final Connection c,
final AgentSpan span) {
if (checkIfContextCanBePassed(knownConnections, c)) {
if (!syntheticCall(c, ContextPayload.from(span), CONTEXT_PAYLOAD_OPERATION_ID)) {
log.debug("Couldn't send context payload");
}
}
}
private boolean checkIfContextCanBePassed(
final ContextStore<Connection, Boolean> knownConnections, final Connection c) {
final Boolean storedResult = knownConnections.get(c);
if (storedResult != null) {
return storedResult;
}
final boolean result = syntheticCall(c, null, CONTEXT_CHECK_CALL_OPERATION_ID);
knownConnections.put(c, result);
return result;
}
/** @returns true when no error happened during call */
private boolean syntheticCall(
final Connection c, final ContextPayload payload, final int operationId) {
final StreamRemoteCall shareContextCall = new StreamRemoteCall(c);
try {
c.getOutputStream().write(TransportConstants.Call);
final ObjectOutput out = shareContextCall.getOutputStream();
DD_CONTEXT_CALL_ID.write(out);
// call header, part 2 (read by Dispatcher)
out.writeInt(operationId); // in normal call this is method number (operation index)
out.writeLong(operationId); // in normal RMI call this holds stub/skeleton hash
// Payload should be sent only after we make sure we're connected to instrumented server
//
// if method is not found by un-instrumented code then writing payload will cause an exception
// in RMI server - as the payload will be interpreted as another call
// but it will not be parsed correctly - closing connection
if (payload != null) {
payload.write(out);
}
try {
shareContextCall.executeCall();
} catch (final Exception e) {
final Exception ex = shareContextCall.getServerException();
if (ex != null) {
if (ex instanceof NoSuchObjectException) {
return false;
} else {
log.debug("Server error when executing synthetic call", ex);
}
} else {
log.debug("Error executing synthetic call", e);
}
return false;
} finally {
shareContextCall.done();
}
} catch (final IOException e) {
log.debug("Communication error executing synthetic call", e);
return false;
}
return true;
}
}

View File

@ -0,0 +1,106 @@
package datadog.trace.instrumentation.rmi.context.client;
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static datadog.trace.instrumentation.api.AgentTracer.activeSpan;
import static datadog.trace.instrumentation.rmi.context.ContextPropagator.PROPAGATOR;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.bootstrap.ContextStore;
import datadog.trace.bootstrap.InstrumentationContext;
import datadog.trace.instrumentation.api.AgentSpan;
import java.rmi.server.ObjID;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import sun.rmi.transport.Connection;
/**
* Main entry point for transferring context between RMI service.
*
* <p>It injects into StreamRemoteCall constructor used for invoking remote tasks and performs a
* backwards compatible check to ensure if the other side is prepared to receive context propagation
* messages then if successful sends a context propagation message
*
* <p>Context propagation consist of a Serialized HashMap with all data set by usual context
* injection, which includes things like sampling priority, trace and parent id
*
* <p>As well as optional baggage items
*
* <p>On the other side of the communication a special Dispatcher is created when a message with
* DD_CONTEXT_CALL_ID is received.
*
* <p>If the server is not instrumented first call will gracefully fail just like any other unknown
* call. With small caveat that this first call needs to *not* have any parameters, since those will
* not be read from connection and instead will be interpreted as another remote instruction, but
* that instruction will essentially be garbage data and will cause the parsing loop to throw
* exception and shutdown the connection which we do not want
*/
@AutoService(Instrumenter.class)
public class RmiClientContextInstrumentation extends Instrumenter.Default {
public RmiClientContextInstrumentation() {
super("rmi", "rmi-context-propagator", "rmi-client-context-propagator");
}
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return not(isInterface()).and(safeHasSuperType(named("sun.rmi.transport.StreamRemoteCall")));
}
@Override
public Map<String, String> contextStore() {
// caching if a connection can support enhanced format
return singletonMap("sun.rmi.transport.Connection", "java.lang.Boolean");
}
@Override
public String[] helperClassNames() {
return new String[] {
"datadog.trace.instrumentation.rmi.context.ContextPayload$InjectAdapter",
"datadog.trace.instrumentation.rmi.context.ContextPayload$ExtractAdapter",
"datadog.trace.instrumentation.rmi.context.ContextPayload",
"datadog.trace.instrumentation.rmi.context.ContextPropagator"
};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
isConstructor()
.and(takesArgument(0, named("sun.rmi.transport.Connection")))
.and(takesArgument(1, named("java.rmi.server.ObjID"))),
getClass().getName() + "$StreamRemoteCallConstructorAdvice");
}
public static class StreamRemoteCallConstructorAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(value = 0) final Connection c,
@Advice.Argument(value = 1) final ObjID id) {
if (!c.isReusable()) {
return;
}
if (PROPAGATOR.isRMIInternalObject(id)) {
return;
}
final AgentSpan activeSpan = activeSpan();
if (activeSpan == null) {
return;
}
final ContextStore<Connection, Boolean> knownConnections =
InstrumentationContext.get(Connection.class, Boolean.class);
PROPAGATOR.attemptToPropagateContext(knownConnections, c, activeSpan);
}
}
}

View File

@ -0,0 +1,61 @@
package datadog.trace.instrumentation.rmi.context.server;
import static datadog.trace.bootstrap.instrumentation.rmi.ThreadLocalContext.THREAD_LOCAL_CONTEXT;
import static datadog.trace.instrumentation.api.AgentTracer.propagate;
import static datadog.trace.instrumentation.rmi.context.ContextPayload.GETTER;
import static datadog.trace.instrumentation.rmi.context.ContextPropagator.DD_CONTEXT_CALL_ID;
import static datadog.trace.instrumentation.rmi.context.ContextPropagator.PROPAGATOR;
import datadog.trace.instrumentation.api.AgentSpan;
import datadog.trace.instrumentation.rmi.context.ContextPayload;
import java.io.IOException;
import java.io.ObjectInput;
import java.rmi.Remote;
import java.rmi.server.RemoteCall;
import sun.rmi.server.Dispatcher;
import sun.rmi.transport.Target;
/**
* ContextDispatcher is responsible for handling both initial context propagation check call and
* following call which carries payload
*
* <p>Context propagation check is only expected not to throw any exception, hinting to the client
* that its communicating with an instrumented server. Non instrumented server would've thrown
* UnknownObjectException
*
* <p>Because caching of the result after first call on a connection, only payload calls are
* expected
*/
public class ContextDispatcher implements Dispatcher {
private static final ContextDispatcher CONTEXT_DISPATCHER = new ContextDispatcher();
private static final NoopRemote NOOP_REMOTE = new NoopRemote();
public static Target newDispatcherTarget() {
return new Target(NOOP_REMOTE, CONTEXT_DISPATCHER, NOOP_REMOTE, DD_CONTEXT_CALL_ID, false);
}
@Override
public void dispatch(final Remote obj, final RemoteCall call) throws IOException {
final ObjectInput in = call.getInputStream();
final int operationId = in.readInt();
in.readLong(); // skip 8 bytes
if (PROPAGATOR.isOperationWithPayload(operationId)) {
final ContextPayload payload = ContextPayload.read(in);
if (payload != null) {
final AgentSpan.Context context = propagate().extract(payload, GETTER);
THREAD_LOCAL_CONTEXT.set(context);
}
}
// send result stream the client is expecting
call.getResultStream(true);
// release held streams to allow next call to continue
call.releaseInputStream();
call.releaseOutputStream();
call.done();
}
public static class NoopRemote implements Remote {}
}

View File

@ -0,0 +1,71 @@
package datadog.trace.instrumentation.rmi.context.server;
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static datadog.trace.instrumentation.rmi.context.ContextPropagator.DD_CONTEXT_CALL_ID;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import sun.rmi.transport.Target;
@AutoService(Instrumenter.class)
public class RmiServerContextInstrumentation extends Instrumenter.Default {
public RmiServerContextInstrumentation() {
super("rmi", "rmi-context-propagator", "rmi-server-context-propagator");
}
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return not(isInterface()).and(safeHasSuperType(named("sun.rmi.transport.ObjectTable")));
}
@Override
public String[] helperClassNames() {
return new String[] {
"datadog.trace.instrumentation.rmi.context.ContextPayload$InjectAdapter",
"datadog.trace.instrumentation.rmi.context.ContextPayload$ExtractAdapter",
"datadog.trace.instrumentation.rmi.context.ContextPayload",
"datadog.trace.instrumentation.rmi.context.ContextPropagator",
packageName + ".ContextDispatcher",
packageName + ".ContextDispatcher$NoopRemote"
};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
isMethod()
.and(isStatic())
.and(named("getTarget"))
.and((takesArgument(0, named("sun.rmi.transport.ObjectEndpoint")))),
getClass().getName() + "$ObjectTableAdvice");
}
public static class ObjectTableAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void methodExit(
@Advice.Argument(0) final Object oe, @Advice.Return(readOnly = false) Target result) {
// comparing toString() output allows us to avoid using reflection to be able to compare
// ObjID and ObjectEndpoint objects
// ObjectEndpoint#toString() only returns this.objId.toString() value which is exactly
// what we're interested in here.
if (!DD_CONTEXT_CALL_ID.toString().equals(oe.toString())) {
return;
}
result = ContextDispatcher.newDispatcherTarget();
}
}
}

View File

@ -0,0 +1,23 @@
package datadog.trace.instrumentation.rmi.server;
import datadog.trace.agent.decorator.ServerDecorator;
import datadog.trace.api.DDSpanTypes;
public class RmiServerDecorator extends ServerDecorator {
public static final RmiServerDecorator DECORATE = new RmiServerDecorator();
@Override
protected String[] instrumentationNames() {
return new String[] {"rmi"};
}
@Override
protected String spanType() {
return DDSpanTypes.RPC;
}
@Override
protected String component() {
return "rmi-server";
}
}

View File

@ -0,0 +1,87 @@
package datadog.trace.instrumentation.rmi.server;
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static datadog.trace.bootstrap.instrumentation.rmi.ThreadLocalContext.THREAD_LOCAL_CONTEXT;
import static datadog.trace.instrumentation.api.AgentTracer.activateSpan;
import static datadog.trace.instrumentation.api.AgentTracer.startSpan;
import static datadog.trace.instrumentation.rmi.server.RmiServerDecorator.DECORATE;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.api.DDTags;
import datadog.trace.instrumentation.api.AgentScope;
import datadog.trace.instrumentation.api.AgentSpan;
import java.lang.reflect.Method;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@AutoService(Instrumenter.class)
public final class RmiServerInstrumentation extends Instrumenter.Default {
public RmiServerInstrumentation() {
super("rmi", "rmi-server");
}
@Override
public String[] helperClassNames() {
return new String[] {
"datadog.trace.agent.decorator.ServerDecorator",
"datadog.trace.agent.decorator.BaseDecorator",
packageName + ".RmiServerDecorator"
};
}
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return not(isInterface()).and(safeHasSuperType(named("java.rmi.server.RemoteServer")));
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
isMethod().and(isPublic()).and(not(isStatic())), getClass().getName() + "$ServerAdvice");
}
public static class ServerAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class, inline = true)
public static AgentScope onEnter(
@Advice.This final Object thiz, @Advice.Origin final Method method) {
final AgentSpan.Context context = THREAD_LOCAL_CONTEXT.getAndResetContext();
final AgentSpan span;
if (context == null) {
span = startSpan("rmi.request");
} else {
span = startSpan("rmi.request", context);
}
span.setTag(DDTags.RESOURCE_NAME, DECORATE.spanNameForMethod(method))
.setTag("span.origin.type", thiz.getClass().getCanonicalName());
DECORATE.afterStart(span);
return activateSpan(span, true);
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) {
if (scope == null) {
return;
}
DECORATE.onError(scope, throwable);
scope.close();
}
}
}

View File

@ -0,0 +1,202 @@
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.utils.PortUtils
import datadog.trace.api.DDSpanTypes
import datadog.trace.api.DDTags
import datadog.trace.instrumentation.api.Tags
import rmi.app.Greeter
import rmi.app.Server
import rmi.app.ServerLegacy
import java.rmi.registry.LocateRegistry
import java.rmi.server.UnicastRemoteObject
import static datadog.trace.agent.test.asserts.ListWriterAssert.assertTraces
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
class RmiTest extends AgentTestRunner {
def registryPort = PortUtils.randomOpenPort()
def serverRegistry = LocateRegistry.createRegistry(registryPort)
def clientRegistry = LocateRegistry.getRegistry("localhost", registryPort)
def cleanup() {
UnicastRemoteObject.unexportObject(serverRegistry, true)
}
def "Client call creates spans"() {
setup:
def server = new Server()
serverRegistry.rebind(Server.RMI_ID, server)
when:
def response = runUnderTrace("parent") {
def client = (Greeter) clientRegistry.lookup(Server.RMI_ID)
return client.hello("you")
}
then:
response.contains("Hello you")
assertTraces(TEST_WRITER, 2) {
trace(1, 2) {
basicSpan(it, 0, "parent")
span(1) {
operationName "rmi.invoke"
childOf span(0)
tags {
"$DDTags.SERVICE_NAME" "rmi"
"$DDTags.RESOURCE_NAME" "Greeter.hello"
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
"$Tags.COMPONENT" "rmi-client"
"span.origin.type" Greeter.canonicalName
}
}
}
trace(0, 2) {
span(0) {
operationName "rmi.request"
tags {
"$DDTags.RESOURCE_NAME" "Server.hello"
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
"$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER
"$Tags.COMPONENT" "rmi-server"
"span.origin.type" server.class.canonicalName
}
}
span(1) {
operationName "rmi.request"
tags {
"$DDTags.RESOURCE_NAME" "Server.someMethod"
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
"$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER
"$Tags.COMPONENT" "rmi-server"
"span.origin.type" server.class.canonicalName
}
}
}
}
cleanup:
serverRegistry.unbind("Server")
}
def "Calling server builtin methods doesn't create server spans"() {
setup:
def server = new Server()
serverRegistry.rebind(Server.RMI_ID, server)
when:
server.equals(new Server())
server.getRef()
server.hashCode()
server.toString()
server.getClass()
then:
assertTraces(TEST_WRITER, 0) {}
cleanup:
serverRegistry.unbind("Server")
}
def "Service throws exception and its propagated to spans"() {
setup:
def server = new Server()
serverRegistry.rebind(Server.RMI_ID, server)
when:
runUnderTrace("parent") {
def client = (Greeter) clientRegistry.lookup(Server.RMI_ID)
client.exceptional()
}
then:
def thrownException = thrown(RuntimeException)
assertTraces(TEST_WRITER, 2) {
trace(1, 2) {
basicSpan(it, 0, "parent", null, null, thrownException)
span(1) {
operationName "rmi.invoke"
childOf span(0)
errored true
tags {
"$DDTags.SERVICE_NAME" "rmi"
"$DDTags.RESOURCE_NAME" "Greeter.exceptional"
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
"$Tags.COMPONENT" "rmi-client"
"span.origin.type" Greeter.canonicalName
errorTags(RuntimeException, String)
}
}
}
trace(0, 1) {
span(0) {
operationName "rmi.request"
errored true
tags {
"$DDTags.RESOURCE_NAME" "Server.exceptional"
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
"$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER
"$Tags.COMPONENT" "rmi-server"
"span.origin.type" server.class.canonicalName
errorTags(RuntimeException, String)
}
}
}
}
cleanup:
serverRegistry.unbind("Server")
}
def "Client call using ServerLegacy_stub creates spans"() {
setup:
def server = new ServerLegacy()
serverRegistry.rebind(ServerLegacy.RMI_ID, server)
when:
def response = runUnderTrace("parent") {
def client = (Greeter) clientRegistry.lookup(ServerLegacy.RMI_ID)
return client.hello("you")
}
then:
response.contains("Hello you")
assertTraces(TEST_WRITER, 2) {
def parentSpan = TEST_WRITER[1][1]
trace(1, 2) {
basicSpan(it, 0, "parent")
span(1) {
operationName "rmi.invoke"
childOf span(0)
tags {
"$DDTags.SERVICE_NAME" "rmi"
"$DDTags.RESOURCE_NAME" "Greeter.hello"
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
"$Tags.COMPONENT" "rmi-client"
"span.origin.type" Greeter.canonicalName
}
}
}
trace(0, 1) {
span(0) {
childOf parentSpan
operationName "rmi.request"
tags {
"$DDTags.RESOURCE_NAME" "ServerLegacy.hello"
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
"$Tags.COMPONENT" "rmi-server"
"$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER
"span.origin.type" server.class.canonicalName
}
}
}
}
cleanup:
serverRegistry.unbind(ServerLegacy.RMI_ID)
}
}

View File

@ -0,0 +1,10 @@
package rmi.app;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Greeter extends Remote {
String hello(String name) throws RemoteException;
void exceptional() throws RemoteException, RuntimeException;
}

View File

@ -0,0 +1,28 @@
package rmi.app;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class Server extends UnicastRemoteObject implements Greeter {
public static String RMI_ID = Server.class.getSimpleName();
private static final long serialVersionUID = 1L;
public Server() throws RemoteException {
super();
}
@Override
public String hello(final String name) {
return someMethod(name);
}
public String someMethod(final String name) {
return "Hello " + name;
}
@Override
public void exceptional() throws RuntimeException {
throw new RuntimeException("expected");
}
}

View File

@ -0,0 +1,24 @@
package rmi.app;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class ServerLegacy extends UnicastRemoteObject implements Greeter {
static String RMI_ID = ServerLegacy.class.getSimpleName();
private static final long serialVersionUID = 1L;
public ServerLegacy() throws RemoteException {
super();
}
@Override
public String hello(final String name) {
return "Hello " + name;
}
@Override
public void exceptional() throws RuntimeException {
throw new RuntimeException("expected");
}
}

View File

@ -4,15 +4,15 @@ import datadog.trace.api.DDSpanTypes
import datadog.trace.api.DDTags
import datadog.trace.instrumentation.api.Tags
import datadog.trace.instrumentation.servlet2.Servlet2Decorator
import javax.servlet.http.HttpServletRequest
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.handler.ErrorHandler
import org.eclipse.jetty.servlet.ServletContextHandler
import javax.servlet.http.HttpServletRequest
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.AUTH_REQUIRED
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -37,9 +37,10 @@ class JettyServlet2Test extends HttpServerTest<Server, Servlet2Decorator> {
// servletContext.setSecurityHandler(security)
servletContext.addServlet(TestServlet2.Sync, SUCCESS.path)
servletContext.addServlet(TestServlet2.Sync, QUERY_PARAM.path)
servletContext.addServlet(TestServlet2.Sync, REDIRECT.path)
servletContext.addServlet(TestServlet2.Sync, ERROR.path)
servletContext.addServlet(TestServlet2.Sync, EXCEPTION.path)
servletContext.addServlet(TestServlet2.Sync, REDIRECT.path)
servletContext.addServlet(TestServlet2.Sync, AUTH_REQUIRED.path)
jettyServer.setHandler(servletContext)
@ -103,6 +104,9 @@ class JettyServlet2Test extends HttpServerTest<Server, Servlet2Decorator> {
"error.type" { it == null || it == Exception.name }
"error.stack" { it == null || it instanceof String }
}
if (endpoint.query) {
"$DDTags.HTTP_QUERY" endpoint.query
}
}
}
}

View File

@ -1,11 +1,11 @@
import datadog.trace.agent.test.base.HttpServerTest
import groovy.servlet.AbstractHttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -23,6 +23,10 @@ class TestServlet2 {
resp.status = endpoint.status
resp.writer.print(endpoint.body)
break
case QUERY_PARAM:
resp.status = endpoint.status
resp.writer.print(req.queryString)
break
case REDIRECT:
resp.sendRedirect(endpoint.body)
break

View File

@ -4,14 +4,14 @@ import datadog.trace.api.DDSpanTypes
import datadog.trace.api.DDTags
import datadog.trace.instrumentation.api.Tags
import datadog.trace.instrumentation.servlet3.Servlet3Decorator
import javax.servlet.Servlet
import okhttp3.Request
import org.apache.catalina.core.ApplicationFilterChain
import javax.servlet.Servlet
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.AUTH_REQUIRED
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -49,6 +49,7 @@ abstract class AbstractServlet3Test<SERVER, CONTEXT> extends HttpServerTest<SERV
def servlet = servlet()
addServlet(context, SUCCESS.path, servlet)
addServlet(context, QUERY_PARAM.path, servlet)
addServlet(context, ERROR.path, servlet)
addServlet(context, EXCEPTION.path, servlet)
addServlet(context, REDIRECT.path, servlet)
@ -92,6 +93,9 @@ abstract class AbstractServlet3Test<SERVER, CONTEXT> extends HttpServerTest<SERV
"error.type" { it == null || it == Exception.name }
"error.stack" { it == null || it instanceof String }
}
if (endpoint.query) {
"$DDTags.HTTP_QUERY" endpoint.query
}
}
}
}

View File

@ -5,17 +5,17 @@ import datadog.trace.api.DDTags
import datadog.trace.instrumentation.api.Tags
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType
import javax.servlet.Servlet
import javax.servlet.http.HttpServletRequest
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.handler.ErrorHandler
import org.eclipse.jetty.servlet.ServletContextHandler
import javax.servlet.Servlet
import javax.servlet.http.HttpServletRequest
import static datadog.trace.agent.test.asserts.TraceAssert.assertTrace
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.AUTH_REQUIRED
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
@ -117,51 +117,55 @@ class JettyServlet3TestFakeAsync extends JettyServlet3Test {
}
}
class JettyServlet3TestForward extends JettyServlet3Test {
@Override
Class<Servlet> servlet() {
TestServlet3.Sync // dispatch to sync servlet
}
// FIXME: not working right now...
//class JettyServlet3TestForward extends JettyDispatchTest {
// @Override
// Class<Servlet> servlet() {
// TestServlet3.Sync // dispatch to sync servlet
// }
//
// @Override
// boolean testNotFound() {
// false
// }
//
// @Override
// protected void setupServlets(ServletContextHandler context) {
// super.setupServlets(context)
//
// addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Forward)
// addServlet(context, "/dispatch" + QUERY_PARAM.path, RequestDispatcherServlet.Forward)
// addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Forward)
// addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Forward)
// addServlet(context, "/dispatch" + EXCEPTION.path, RequestDispatcherServlet.Forward)
// addServlet(context, "/dispatch" + AUTH_REQUIRED.path, RequestDispatcherServlet.Forward)
// }
//}
@Override
boolean testNotFound() {
false
}
@Override
protected void setupServlets(ServletContextHandler context) {
super.setupServlets(context)
addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Forward)
addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Forward)
addServlet(context, "/dispatch" + EXCEPTION.path, RequestDispatcherServlet.Forward)
addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Forward)
addServlet(context, "/dispatch" + AUTH_REQUIRED.path, RequestDispatcherServlet.Forward)
}
}
class JettyServlet3TestInclude extends JettyServlet3Test {
@Override
Class<Servlet> servlet() {
TestServlet3.Sync // dispatch to sync servlet
}
@Override
boolean testNotFound() {
false
}
@Override
protected void setupServlets(ServletContextHandler context) {
super.setupServlets(context)
addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Include)
addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Include)
addServlet(context, "/dispatch" + EXCEPTION.path, RequestDispatcherServlet.Include)
addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Include)
addServlet(context, "/dispatch" + AUTH_REQUIRED.path, RequestDispatcherServlet.Include)
}
}
// FIXME: not working right now...
//class JettyServlet3TestInclude extends JettyDispatchTest {
// @Override
// Class<Servlet> servlet() {
// TestServlet3.Sync // dispatch to sync servlet
// }
//
// @Override
// boolean testNotFound() {
// false
// }
//
// @Override
// protected void setupServlets(ServletContextHandler context) {
// super.setupServlets(context)
//
// addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Include)
// addServlet(context, "/dispatch" + QUERY_PARAM.path, RequestDispatcherServlet.Include)
// addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Include)
// addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Include)
// addServlet(context, "/dispatch" + EXCEPTION.path, RequestDispatcherServlet.Include)
// addServlet(context, "/dispatch" + AUTH_REQUIRED.path, RequestDispatcherServlet.Include)
// }
//}
class JettyServlet3TestDispatchImmediate extends JettyDispatchTest {
@ -175,6 +179,7 @@ class JettyServlet3TestDispatchImmediate extends JettyDispatchTest {
super.setupServlets(context)
addServlet(context, "/dispatch" + SUCCESS.path, TestServlet3.DispatchImmediate)
addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet3.DispatchImmediate)
addServlet(context, "/dispatch" + ERROR.path, TestServlet3.DispatchImmediate)
addServlet(context, "/dispatch" + EXCEPTION.path, TestServlet3.DispatchImmediate)
addServlet(context, "/dispatch" + REDIRECT.path, TestServlet3.DispatchImmediate)
@ -194,6 +199,7 @@ class JettyServlet3TestDispatchAsync extends JettyDispatchTest {
super.setupServlets(context)
addServlet(context, "/dispatch" + SUCCESS.path, TestServlet3.DispatchAsync)
addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet3.DispatchAsync)
addServlet(context, "/dispatch" + ERROR.path, TestServlet3.DispatchAsync)
addServlet(context, "/dispatch" + EXCEPTION.path, TestServlet3.DispatchAsync)
addServlet(context, "/dispatch" + REDIRECT.path, TestServlet3.DispatchAsync)
@ -262,6 +268,9 @@ abstract class JettyDispatchTest extends JettyServlet3Test {
"error.type" { it == null || it == Exception.name }
"error.stack" { it == null || it instanceof String }
}
if (endpoint.query) {
"$DDTags.HTTP_QUERY" endpoint.query
}
}
}
}

View File

@ -1,13 +1,14 @@
import datadog.trace.agent.test.base.HttpServerTest
import groovy.servlet.AbstractHttpServlet
import javax.servlet.annotation.WebServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import java.util.concurrent.Phaser
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -25,6 +26,10 @@ class TestServlet3 {
resp.status = endpoint.status
resp.writer.print(endpoint.body)
break
case QUERY_PARAM:
resp.status = endpoint.status
resp.writer.print(req.queryString)
break
case REDIRECT:
resp.sendRedirect(endpoint.body)
break
@ -52,15 +57,25 @@ class TestServlet3 {
resp.contentType = "text/plain"
switch (endpoint) {
case SUCCESS:
case ERROR:
resp.status = endpoint.status
resp.writer.print(endpoint.body)
context.complete()
break
case QUERY_PARAM:
resp.status = endpoint.status
resp.writer.print(req.queryString)
context.complete()
break
case REDIRECT:
resp.sendRedirect(endpoint.body)
context.complete()
break
case ERROR:
resp.status = endpoint.status
resp.writer.print(endpoint.body)
// resp.sendError(endpoint.status, endpoint.body)
context.complete()
break
case EXCEPTION:
resp.status = endpoint.status
resp.writer.print(endpoint.body)
@ -88,13 +103,19 @@ class TestServlet3 {
resp.contentType = "text/plain"
switch (endpoint) {
case SUCCESS:
case ERROR:
resp.status = endpoint.status
resp.writer.print(endpoint.body)
break
case QUERY_PARAM:
resp.status = endpoint.status
resp.writer.print(req.queryString)
break
case REDIRECT:
resp.sendRedirect(endpoint.body)
break
case ERROR:
resp.sendError(endpoint.status, endpoint.body)
break
case EXCEPTION:
throw new Exception(endpoint.body)
}

View File

@ -6,6 +6,7 @@ import datadog.trace.api.DDTags
import datadog.trace.instrumentation.api.Tags
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType
import javax.servlet.Servlet
import org.apache.catalina.Context
import org.apache.catalina.connector.Request
import org.apache.catalina.connector.Response
@ -16,13 +17,12 @@ import org.apache.catalina.valves.ErrorReportValve
import org.apache.tomcat.JarScanFilter
import org.apache.tomcat.JarScanType
import javax.servlet.Servlet
import static datadog.trace.agent.test.asserts.TraceAssert.assertTrace
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.AUTH_REQUIRED
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
@ -154,51 +154,55 @@ class TomcatServlet3TestFakeAsync extends TomcatServlet3Test {
}
}
class TomcatServlet3TestForward extends TomcatServlet3Test {
@Override
Class<Servlet> servlet() {
TestServlet3.Sync // dispatch to sync servlet
}
// FIXME: not working right now...
//class TomcatServlet3TestForward extends TomcatDispatchTest {
// @Override
// Class<Servlet> servlet() {
// TestServlet3.Sync // dispatch to sync servlet
// }
//
// @Override
// boolean testNotFound() {
// false
// }
//
// @Override
// protected void setupServlets(Context context) {
// super.setupServlets(context)
//
// addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Forward)
// addServlet(context, "/dispatch" + QUERY_PARAM.path, RequestDispatcherServlet.Forward)
// addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Forward)
// addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Forward)
// addServlet(context, "/dispatch" + EXCEPTION.path, RequestDispatcherServlet.Forward)
// addServlet(context, "/dispatch" + AUTH_REQUIRED.path, RequestDispatcherServlet.Forward)
// }
//}
@Override
boolean testNotFound() {
false
}
@Override
protected void setupServlets(Context context) {
super.setupServlets(context)
addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Forward)
addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Forward)
addServlet(context, "/dispatch" + EXCEPTION.path, RequestDispatcherServlet.Forward)
addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Forward)
addServlet(context, "/dispatch" + AUTH_REQUIRED.path, RequestDispatcherServlet.Forward)
}
}
class TomcatServlet3TestInclude extends TomcatServlet3Test {
@Override
Class<Servlet> servlet() {
TestServlet3.Sync // dispatch to sync servlet
}
@Override
boolean testNotFound() {
false
}
@Override
protected void setupServlets(Context context) {
super.setupServlets(context)
addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Include)
addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Include)
addServlet(context, "/dispatch" + EXCEPTION.path, RequestDispatcherServlet.Include)
addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Include)
addServlet(context, "/dispatch" + AUTH_REQUIRED.path, RequestDispatcherServlet.Include)
}
}
// FIXME: not working right now...
//class TomcatServlet3TestInclude extends TomcatDispatchTest {
// @Override
// Class<Servlet> servlet() {
// TestServlet3.Sync // dispatch to sync servlet
// }
//
// @Override
// boolean testNotFound() {
// false
// }
//
// @Override
// protected void setupServlets(Context context) {
// super.setupServlets(context)
//
// addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Include)
// addServlet(context, "/dispatch" + QUERY_PARAM.path, RequestDispatcherServlet.Include)
// addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Include)
// addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Include)
// addServlet(context, "/dispatch" + EXCEPTION.path, RequestDispatcherServlet.Include)
// addServlet(context, "/dispatch" + AUTH_REQUIRED.path, RequestDispatcherServlet.Include)
// }
//}
class TomcatServlet3TestDispatchImmediate extends TomcatDispatchTest {
@Override
@ -216,6 +220,7 @@ class TomcatServlet3TestDispatchImmediate extends TomcatDispatchTest {
super.setupServlets(context)
addServlet(context, "/dispatch" + SUCCESS.path, TestServlet3.DispatchImmediate)
addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet3.DispatchImmediate)
addServlet(context, "/dispatch" + ERROR.path, TestServlet3.DispatchImmediate)
addServlet(context, "/dispatch" + EXCEPTION.path, TestServlet3.DispatchImmediate)
addServlet(context, "/dispatch" + REDIRECT.path, TestServlet3.DispatchImmediate)
@ -235,6 +240,7 @@ class TomcatServlet3TestDispatchAsync extends TomcatDispatchTest {
super.setupServlets(context)
addServlet(context, "/dispatch" + SUCCESS.path, TestServlet3.DispatchAsync)
addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet3.DispatchAsync)
addServlet(context, "/dispatch" + ERROR.path, TestServlet3.DispatchAsync)
addServlet(context, "/dispatch" + EXCEPTION.path, TestServlet3.DispatchAsync)
addServlet(context, "/dispatch" + REDIRECT.path, TestServlet3.DispatchAsync)
@ -296,7 +302,7 @@ abstract class TomcatDispatchTest extends TomcatServlet3Test {
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_SERVER
"$Tags.COMPONENT" serverDecorator.component()
"$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER
"$Tags.PEER_HOSTNAME" "localhost"
"$Tags.PEER_HOSTNAME" { it == "localhost" || it == "127.0.0.1" }
"$Tags.PEER_HOST_IPV4" { it == null || it == "127.0.0.1" } // Optional
"$Tags.PEER_PORT" Integer
"$Tags.HTTP_URL" "${endpoint.resolve(address)}"
@ -313,6 +319,9 @@ abstract class TomcatDispatchTest extends TomcatServlet3Test {
"error.type" { it == null || it == Exception.name }
"error.stack" { it == null || it instanceof String }
}
if (endpoint.query) {
"$DDTags.HTTP_QUERY" endpoint.query
}
}
}
}

View File

@ -45,7 +45,14 @@ public class SpringWebHttpServerDecorator
@Override
protected URI url(final HttpServletRequest httpServletRequest) throws URISyntaxException {
return new URI(httpServletRequest.getRequestURL().toString());
return new URI(
httpServletRequest.getScheme(),
null,
httpServletRequest.getServerName(),
httpServletRequest.getServerPort(),
httpServletRequest.getRequestURI(),
httpServletRequest.getQueryString(),
null);
}
@Override

View File

@ -136,6 +136,9 @@ class SpringBootBasedTest extends HttpServerTest<ConfigurableApplicationContext,
"error.type" { it == null || it == Exception.name }
"error.stack" { it == null || it instanceof String }
}
if (endpoint.query) {
"$DDTags.HTTP_QUERY" endpoint.query
}
}
}
}

View File

@ -6,11 +6,13 @@ import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.servlet.view.RedirectView
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -25,6 +27,14 @@ class TestController {
}
}
@RequestMapping("/query")
@ResponseBody
String query_param(@RequestParam("some") String param) {
HttpServerTest.controller(QUERY_PARAM) {
"some=$param"
}
}
@RequestMapping("/redirect")
@ResponseBody
RedirectView redirect() {

View File

@ -15,6 +15,7 @@ import java.util.concurrent.CompletableFuture
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -83,6 +84,11 @@ class VertxHttpServerTest extends HttpServerTest<Vertx, NettyHttpServerDecorator
ctx.response().setStatusCode(SUCCESS.status).end(SUCCESS.body)
}
}
router.route(QUERY_PARAM.path).handler { ctx ->
controller(QUERY_PARAM) {
ctx.response().setStatusCode(QUERY_PARAM.status).end(ctx.request().query())
}
}
router.route(REDIRECT.path).handler { ctx ->
controller(REDIRECT) {
ctx.response().setStatusCode(REDIRECT.status).putHeader("location", REDIRECT.body).end()

View File

@ -8,6 +8,7 @@ import io.vertx.reactivex.ext.web.Router
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -45,6 +46,19 @@ class VertxRxCircuitBreakerHttpServerTest extends VertxHttpServerTest {
}
})
}
router.route(QUERY_PARAM.path).handler { ctx ->
breaker.executeCommand({ future ->
future.complete(QUERY_PARAM)
}, { it ->
if (it.failed()) {
throw it.cause()
}
HttpServerTest.ServerEndpoint endpoint = it.result()
controller(endpoint) {
ctx.response().setStatusCode(endpoint.status).end(ctx.request().query())
}
})
}
router.route(REDIRECT.path).handler { ctx ->
breaker.executeCommand({ future ->
future.complete(REDIRECT)

View File

@ -7,6 +7,7 @@ import io.vertx.reactivex.ext.web.Router
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
@ -29,6 +30,11 @@ class VertxRxHttpServerTest extends VertxHttpServerTest {
ctx.response().setStatusCode(SUCCESS.status).end(SUCCESS.body)
}
}
router.route(QUERY_PARAM.path).handler { ctx ->
controller(QUERY_PARAM) {
ctx.response().setStatusCode(QUERY_PARAM.status).end(ctx.request().query())
}
}
router.route(REDIRECT.path).handler { ctx ->
controller(REDIRECT) {
ctx.response().setStatusCode(REDIRECT.status).putHeader("location", REDIRECT.body).end()

View File

@ -41,19 +41,23 @@ import java.util.regex.Pattern;
*/
public class AgentBootstrap {
public static void premain(final String agentArgs, final Instrumentation inst) throws Exception {
public static void premain(final String agentArgs, final Instrumentation inst) {
agentmain(agentArgs, inst);
}
public static void agentmain(final String agentArgs, final Instrumentation inst)
throws Exception {
public static void agentmain(final String agentArgs, final Instrumentation inst) {
try {
final URL bootstrapURL = installBootstrapJar(inst);
final URL bootstrapURL = installBootstrapJar(inst);
final Class<?> agentClass =
ClassLoader.getSystemClassLoader().loadClass("datadog.trace.bootstrap.Agent");
final Method startMethod = agentClass.getMethod("start", Instrumentation.class, URL.class);
startMethod.invoke(null, inst, bootstrapURL);
final Class<?> agentClass =
ClassLoader.getSystemClassLoader().loadClass("datadog.trace.bootstrap.Agent");
final Method startMethod = agentClass.getMethod("start", Instrumentation.class, URL.class);
startMethod.invoke(null, inst, bootstrapURL);
} catch (final Throwable ex) {
// Don't rethrow. We don't have a log manager here, so just print.
ex.printStackTrace();
}
}
private static synchronized URL installBootstrapJar(final Instrumentation inst)
@ -106,8 +110,12 @@ public class AgentBootstrap {
throw new RuntimeException("Unable to parse javaagent parameter: " + agentArgument);
}
bootstrapURL = new URL("file:" + matcher.group(1));
inst.appendToBootstrapClassLoaderSearch(new JarFile(new File(bootstrapURL.toURI())));
final File javaagentFile = new File(matcher.group(1));
if (!(javaagentFile.exists() || javaagentFile.isFile())) {
throw new RuntimeException("Unable to find javaagent file: " + javaagentFile);
}
bootstrapURL = javaagentFile.toURI().toURL();
inst.appendToBootstrapClassLoaderSearch(new JarFile(javaagentFile));
return bootstrapURL;
}

View File

@ -5,7 +5,7 @@ import jvmbootstraptest.AgentLoadedChecker
import spock.lang.Specification
class AgentLoadedIntoBootstrapTest extends Specification {
def "Agent loads in when separate jvm is launched"() {
expect:
IntegrationTestUtils.runOnSeparateJvm(AgentLoadedChecker.getName()

View File

@ -25,8 +25,10 @@ import static datadog.trace.agent.test.asserts.TraceAssert.assertTrace
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
import static datadog.trace.agent.test.utils.ConfigUtils.withConfigOverride
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
import static datadog.trace.instrumentation.api.AgentTracer.activeScope
@ -108,16 +110,25 @@ abstract class HttpServerTest<SERVER, DECORATOR extends HttpServerDecorator> ext
NOT_FOUND("notFound", 404, "not found"),
// TODO: add tests for the following cases:
QUERY_PARAM("query?some=query", 200, "some=query"),
// OkHttp never sends the fragment in the request, so these cases don't work.
// FRAGMENT_PARAM("fragment#some-fragment", 200, "some-fragment"),
// QUERY_FRAGMENT_PARAM("query/fragment?some=query#some-fragment", 200, "some=query#some-fragment"),
PATH_PARAM("path/123/param", 200, "123"),
AUTH_REQUIRED("authRequired", 200, null),
private final String path
final String query
final String fragment
final int status
final String body
final Boolean errored
ServerEndpoint(String path, int status, String body) {
this.path = path
ServerEndpoint(String uri, int status, String body) {
def uriObj = URI.create(uri)
this.path = uriObj.path
this.query = uriObj.query
this.fragment = uriObj.fragment
this.status = status
this.body = body
this.errored = status >= 500
@ -143,8 +154,12 @@ abstract class HttpServerTest<SERVER, DECORATOR extends HttpServerDecorator> ext
}
Request.Builder request(ServerEndpoint uri, String method, String body) {
def url = HttpUrl.get(uri.resolve(address)).newBuilder()
.query(uri.query)
.fragment(uri.fragment)
.build()
return new Request.Builder()
.url(HttpUrl.get(uri.resolve(address)))
.url(url)
.method(method, body)
}
@ -229,6 +244,75 @@ abstract class HttpServerTest<SERVER, DECORATOR extends HttpServerDecorator> ext
body = null
}
def "test tag query string for #endpoint"() {
setup:
def request = request(endpoint, method, body).build()
Response response = withConfigOverride("http.server.tag.query-string", "true") {
client.newCall(request).execute()
}
expect:
response.code() == endpoint.status
response.body().string() == endpoint.body
and:
cleanAndAssertTraces(1) {
if (hasHandlerSpan()) {
trace(0, 3) {
serverSpan(it, 0, null, null, method, endpoint)
handlerSpan(it, 1, span(0), method, endpoint)
controllerSpan(it, 2, span(1))
}
} else {
trace(0, 2) {
serverSpan(it, 0, null, null, method, endpoint)
controllerSpan(it, 1, span(0))
}
}
}
where:
method = "GET"
body = null
endpoint << [SUCCESS, QUERY_PARAM]
}
def "test success with multiple header attached parent"() {
setup:
def traceId = 123G
def parentId = 456G
def request = request(SUCCESS, method, body)
.header("x-datadog-trace-id", traceId.toString() + ", " + traceId.toString())
.header("x-datadog-parent-id", parentId.toString() + ", " + parentId.toString())
.header("x-datadog-sampling-priority", "1, 1")
.build()
def response = client.newCall(request).execute()
expect:
response.code() == SUCCESS.status
response.body().string() == SUCCESS.body
and:
cleanAndAssertTraces(1) {
if (hasHandlerSpan()) {
trace(0, 3) {
serverSpan(it, 0, traceId, parentId)
handlerSpan(it, 1, span(0))
controllerSpan(it, 2, span(1))
}
} else {
trace(0, 2) {
serverSpan(it, 0, traceId, parentId)
controllerSpan(it, 1, span(0))
}
}
}
where:
method = "GET"
body = null
}
def "test redirect"() {
setup:
def request = request(REDIRECT, method, body).build()
@ -439,9 +523,12 @@ abstract class HttpServerTest<SERVER, DECORATOR extends HttpServerDecorator> ext
"$Tags.HTTP_URL" "${endpoint.resolve(address)}"
"$Tags.HTTP_METHOD" method
"$Tags.HTTP_STATUS" endpoint.status
// if (tagQueryString) {
// "$DDTags.HTTP_QUERY" uri.query
// "$DDTags.HTTP_FRAGMENT" { it == null || it == uri.fragment } // Optional
if (endpoint.query) {
"$DDTags.HTTP_QUERY" endpoint.query
}
// OkHttp never sends the fragment in the request.
// if (endpoint.fragment) {
// "$DDTags.HTTP_FRAGMENT" endpoint.fragment
// }
}
}

View File

@ -16,7 +16,7 @@ def isCI = System.getenv("CI") != null
allprojects {
group = 'com.datadoghq'
version = '0.40.0-SNAPSHOT'
version = '0.41.0-SNAPSHOT'
if (isCI) {
buildDir = "${rootDir}/workspace/${projectDir.path.replace(rootDir.path, '')}/build/"

View File

@ -109,8 +109,9 @@ include ':dd-java-agent:instrumentation:play-ws-2'
include ':dd-java-agent:instrumentation:play-ws-2.1'
include ':dd-java-agent:instrumentation:rabbitmq-amqp-2.7'
include ':dd-java-agent:instrumentation:ratpack-1.4'
include ':dd-java-agent:instrumentation:rxjava-1'
include ':dd-java-agent:instrumentation:reactor-core-3.1'
include ':dd-java-agent:instrumentation:rmi'
include ':dd-java-agent:instrumentation:rxjava-1'
include ':dd-java-agent:instrumentation:servlet'
include ':dd-java-agent:instrumentation:servlet:request-2'
include ':dd-java-agent:instrumentation:servlet:request-3'