Elasticsearch Instrumentation
This commit is contained in:
parent
164689eeb3
commit
0cf8fef8d8
|
@ -0,0 +1,58 @@
|
|||
//apply plugin: 'version-scan'
|
||||
//
|
||||
//versionScan {
|
||||
// group = "org.elasticsearch.client"
|
||||
//// module = "transport"
|
||||
// module = "rest"
|
||||
// versions = "[5.0,)"
|
||||
// legacyGroup = "org.elasticsearch"
|
||||
// legacyModule = "elasticsearch"
|
||||
// scanDependencies = true
|
||||
// verifyPresent = [
|
||||
// "org.elasticsearch.percolator.TransportMultiPercolateAction": null,
|
||||
// ]
|
||||
//}
|
||||
|
||||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
|
||||
apply plugin: 'org.unbroken-dome.test-sets'
|
||||
|
||||
testSets {
|
||||
latestDepTest
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly group: 'org.elasticsearch.client', name: 'rest', version: '5.0.0'
|
||||
|
||||
compile project(':dd-java-agent:agent-tooling')
|
||||
|
||||
compile deps.bytebuddy
|
||||
compile deps.opentracing
|
||||
annotationProcessor deps.autoservice
|
||||
implementation deps.autoservice
|
||||
|
||||
testCompile project(':dd-java-agent:testing')
|
||||
// Ensure no cross interference
|
||||
testCompile project(':dd-java-agent:instrumentation:elasticsearch-transport-5')
|
||||
testCompile project(':dd-java-agent:instrumentation:elasticsearch-transport-6')
|
||||
// Include httpclient instrumentation for testing because it is a dependency for elasticsearch-rest-client.
|
||||
// It doesn't actually work though. They use HttpAsyncClient, which isn't currently instrumented.
|
||||
// TODO: add HttpAsyncClient instrumentation when that is complete.
|
||||
testCompile project(':dd-java-agent:instrumentation:apache-httpclient-4.3')
|
||||
// TODO: add netty instrumentation when that is complete.
|
||||
|
||||
testCompile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.0'
|
||||
testCompile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.0'
|
||||
|
||||
testCompile group: 'org.elasticsearch.client', name: 'rest', version: '5.0.0'
|
||||
testCompile group: 'org.elasticsearch', name: 'elasticsearch', version: '5.0.0'
|
||||
testCompile group: 'org.elasticsearch.plugin', name: 'transport-netty3-client', version: '5.0.0'
|
||||
|
||||
latestDepTestCompile group: 'org.elasticsearch.client', name: 'elasticsearch-rest-client', version: '+'
|
||||
latestDepTestCompile group: 'org.elasticsearch', name: 'elasticsearch', version: '6.+'
|
||||
latestDepTestCompile group: 'org.elasticsearch.plugin', name: 'transport-netty4-client', version: '+'
|
||||
}
|
||||
|
||||
configurations.latestDepTestCompile {
|
||||
exclude group: "org.elasticsearch.client", module: "rest"
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import datadog.trace.agent.test.TestUtils
|
||||
import groovy.json.JsonSlurper
|
||||
import io.opentracing.tag.Tags
|
||||
import org.apache.http.HttpHost
|
||||
import org.apache.http.client.config.RequestConfig
|
||||
import org.apache.http.util.EntityUtils
|
||||
import org.elasticsearch.client.Response
|
||||
import org.elasticsearch.client.RestClient
|
||||
import org.elasticsearch.client.RestClientBuilder
|
||||
import org.elasticsearch.common.io.FileSystemUtils
|
||||
import org.elasticsearch.common.settings.Settings
|
||||
import org.elasticsearch.node.InternalSettingsPreparer
|
||||
import org.elasticsearch.node.Node
|
||||
import org.elasticsearch.transport.Netty4Plugin
|
||||
import spock.lang.Shared
|
||||
|
||||
import static datadog.trace.agent.test.ListWriterAssert.assertTraces
|
||||
|
||||
class Elasticsearch6RestClientTest extends AgentTestRunner {
|
||||
static {
|
||||
System.setProperty("dd.integration.elasticsearch.enabled", "true")
|
||||
}
|
||||
|
||||
static final int HTTP_PORT = TestUtils.randomOpenPort()
|
||||
static final int TCP_PORT = TestUtils.randomOpenPort()
|
||||
|
||||
@Shared
|
||||
static Node testNode
|
||||
static File esWorkingDir
|
||||
|
||||
@Shared
|
||||
static RestClient client
|
||||
|
||||
def setupSpec() {
|
||||
esWorkingDir = File.createTempFile("test-es-working-dir-", "")
|
||||
esWorkingDir.delete()
|
||||
esWorkingDir.mkdir()
|
||||
esWorkingDir.deleteOnExit()
|
||||
println "ES work dir: $esWorkingDir"
|
||||
|
||||
def settings = Settings.builder()
|
||||
.put("path.home", esWorkingDir.path)
|
||||
.put("http.port", HTTP_PORT)
|
||||
.put("transport.tcp.port", TCP_PORT)
|
||||
.put("cluster.name", "test-cluster")
|
||||
.build()
|
||||
testNode = new Node(InternalSettingsPreparer.prepareEnvironment(settings, null), [Netty4Plugin])
|
||||
testNode.start()
|
||||
|
||||
client = RestClient.builder(new HttpHost("localhost", HTTP_PORT))
|
||||
.setMaxRetryTimeoutMillis(Integer.MAX_VALUE)
|
||||
.setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() {
|
||||
@Override
|
||||
RequestConfig.Builder customizeRequestConfig(RequestConfig.Builder builder) {
|
||||
return builder.setConnectTimeout(Integer.MAX_VALUE).setSocketTimeout(Integer.MAX_VALUE)
|
||||
}
|
||||
})
|
||||
.build()
|
||||
|
||||
}
|
||||
|
||||
def cleanupSpec() {
|
||||
testNode?.close()
|
||||
if (esWorkingDir != null) {
|
||||
FileSystemUtils.deleteSubDirectories(esWorkingDir.toPath())
|
||||
esWorkingDir.delete()
|
||||
}
|
||||
}
|
||||
|
||||
def "test elasticsearch status"() {
|
||||
setup:
|
||||
Response response = client.performRequest("GET", "_cluster/health")
|
||||
|
||||
Map result = new JsonSlurper().parseText(EntityUtils.toString(response.entity))
|
||||
|
||||
expect:
|
||||
result.status == "green"
|
||||
|
||||
assertTraces(TEST_WRITER, 2) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "elasticsearch"
|
||||
resourceName "ClusterHealthAction"
|
||||
operationName "elasticsearch.query"
|
||||
spanType null
|
||||
parent()
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "elasticsearch-java"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"elasticsearch.action" "ClusterHealthAction"
|
||||
"elasticsearch.request" "ClusterHealthRequest"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
trace(1, 1) {
|
||||
span(0) {
|
||||
serviceName "elasticsearch"
|
||||
resourceName "GET _cluster/health"
|
||||
operationName "elasticsearch.rest.query"
|
||||
spanType null
|
||||
parent()
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "elasticsearch-java"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_URL.key" "_cluster/health"
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_PORT.key" HTTP_PORT
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package datadog.trace.instrumentation.elasticsearch5;
|
||||
|
||||
import static io.opentracing.log.Fields.ERROR_OBJECT;
|
||||
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.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.not;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import datadog.trace.agent.tooling.DDAdvice;
|
||||
import datadog.trace.agent.tooling.DDTransformers;
|
||||
import datadog.trace.agent.tooling.Instrumenter;
|
||||
import datadog.trace.api.DDTags;
|
||||
import io.opentracing.Scope;
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.tag.Tags;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
import java.util.Collections;
|
||||
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import org.elasticsearch.client.ResponseListener;
|
||||
|
||||
@AutoService(Instrumenter.class)
|
||||
public class Elasticsearch5RestClientInstrumentation extends Instrumenter.Configurable {
|
||||
|
||||
public Elasticsearch5RestClientInstrumentation() {
|
||||
super("elasticsearch", "elasticsearch-rest", "elasticsearch-rest-5");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgentBuilder apply(final AgentBuilder agentBuilder) {
|
||||
return agentBuilder
|
||||
.type(not(isInterface()).and(named("org.elasticsearch.client.RestClient")))
|
||||
.transform(DDTransformers.defaultTransformers())
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
isMethod()
|
||||
.and(isPublic())
|
||||
.and(named("performRequestAsync"))
|
||||
.and(takesArguments(7))
|
||||
.and(takesArgument(0, named("java.lang.String"))) // method
|
||||
.and(takesArgument(1, named("java.lang.String"))) // endpoint
|
||||
.and(takesArgument(5, named("org.elasticsearch.client.ResponseListener"))),
|
||||
ElasticsearchRestClientAdvice.class.getName()))
|
||||
.asDecorator();
|
||||
}
|
||||
|
||||
public static class ElasticsearchRestClientAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static Scope startSpan(
|
||||
@Advice.Argument(0) final String method,
|
||||
@Advice.Argument(1) final String endpoint,
|
||||
@Advice.Argument(value = 5, readOnly = false) ResponseListener responseListener) {
|
||||
|
||||
final Scope scope =
|
||||
GlobalTracer.get()
|
||||
.buildSpan("elasticsearch.rest.query")
|
||||
.withTag(DDTags.SERVICE_NAME, "elasticsearch")
|
||||
.withTag(Tags.HTTP_METHOD.getKey(), method)
|
||||
.withTag(Tags.HTTP_URL.getKey(), endpoint)
|
||||
.withTag(Tags.COMPONENT.getKey(), "elasticsearch-java")
|
||||
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
|
||||
.startActive(false);
|
||||
|
||||
responseListener = new RestResponseListener(responseListener, scope.span());
|
||||
return scope;
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void stopSpan(
|
||||
@Advice.Enter final Scope scope, @Advice.Thrown final Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
final Span span = scope.span();
|
||||
Tags.ERROR.set(span, true);
|
||||
span.log(Collections.singletonMap(ERROR_OBJECT, throwable));
|
||||
span.finish();
|
||||
}
|
||||
scope.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package datadog.trace.instrumentation.elasticsearch5;
|
||||
|
||||
import static io.opentracing.log.Fields.ERROR_OBJECT;
|
||||
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.tag.Tags;
|
||||
import java.util.Collections;
|
||||
import org.elasticsearch.client.Response;
|
||||
import org.elasticsearch.client.ResponseListener;
|
||||
|
||||
public class RestResponseListener implements ResponseListener {
|
||||
|
||||
private final ResponseListener listener;
|
||||
private final Span span;
|
||||
|
||||
public RestResponseListener(final ResponseListener listener, final Span span) {
|
||||
this.listener = listener;
|
||||
this.span = span;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(final Response response) {
|
||||
if (response.getHost() != null) {
|
||||
Tags.PEER_HOSTNAME.set(span, response.getHost().getHostName());
|
||||
Tags.PEER_PORT.set(span, response.getHost().getPort());
|
||||
}
|
||||
|
||||
try {
|
||||
listener.onSuccess(response);
|
||||
} finally {
|
||||
span.finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Exception e) {
|
||||
span.log(Collections.singletonMap(ERROR_OBJECT, e));
|
||||
|
||||
try {
|
||||
listener.onFailure(e);
|
||||
} finally {
|
||||
span.finish();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import datadog.trace.agent.test.TestUtils
|
||||
import groovy.json.JsonSlurper
|
||||
import io.opentracing.tag.Tags
|
||||
import org.apache.http.HttpHost
|
||||
import org.apache.http.client.config.RequestConfig
|
||||
import org.apache.http.util.EntityUtils
|
||||
import org.elasticsearch.client.Response
|
||||
import org.elasticsearch.client.RestClient
|
||||
import org.elasticsearch.client.RestClientBuilder
|
||||
import org.elasticsearch.common.io.FileSystemUtils
|
||||
import org.elasticsearch.common.settings.Settings
|
||||
import org.elasticsearch.env.Environment
|
||||
import org.elasticsearch.node.Node
|
||||
import org.elasticsearch.node.internal.InternalSettingsPreparer
|
||||
import org.elasticsearch.transport.Netty3Plugin
|
||||
import spock.lang.Shared
|
||||
|
||||
import static datadog.trace.agent.test.ListWriterAssert.assertTraces
|
||||
import static org.elasticsearch.cluster.ClusterName.CLUSTER_NAME_SETTING
|
||||
|
||||
class Elasticsearch5RestClientTest extends AgentTestRunner {
|
||||
static {
|
||||
System.setProperty("dd.integration.elasticsearch.enabled", "true")
|
||||
}
|
||||
|
||||
static final int HTTP_PORT = TestUtils.randomOpenPort()
|
||||
static final int TCP_PORT = TestUtils.randomOpenPort()
|
||||
|
||||
@Shared
|
||||
static Node testNode
|
||||
static File esWorkingDir
|
||||
|
||||
@Shared
|
||||
static RestClient client
|
||||
|
||||
def setupSpec() {
|
||||
esWorkingDir = File.createTempFile("test-es-working-dir-", "")
|
||||
esWorkingDir.delete()
|
||||
esWorkingDir.mkdir()
|
||||
esWorkingDir.deleteOnExit()
|
||||
println "ES work dir: $esWorkingDir"
|
||||
|
||||
def settings = Settings.builder()
|
||||
.put("path.home", esWorkingDir.path)
|
||||
.put("http.port", HTTP_PORT)
|
||||
.put("transport.tcp.port", TCP_PORT)
|
||||
.put("transport.type", "netty3")
|
||||
.put("http.type", "netty3")
|
||||
.put(CLUSTER_NAME_SETTING.getKey(), "test-cluster")
|
||||
.build()
|
||||
testNode = new Node(new Environment(InternalSettingsPreparer.prepareSettings(settings)), [Netty3Plugin])
|
||||
testNode.start()
|
||||
|
||||
client = RestClient.builder(new HttpHost("localhost", HTTP_PORT))
|
||||
.setMaxRetryTimeoutMillis(Integer.MAX_VALUE)
|
||||
.setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() {
|
||||
@Override
|
||||
RequestConfig.Builder customizeRequestConfig(RequestConfig.Builder builder) {
|
||||
return builder.setConnectTimeout(Integer.MAX_VALUE).setSocketTimeout(Integer.MAX_VALUE)
|
||||
}
|
||||
})
|
||||
.build()
|
||||
|
||||
}
|
||||
|
||||
def cleanupSpec() {
|
||||
testNode?.close()
|
||||
if (esWorkingDir != null) {
|
||||
FileSystemUtils.deleteSubDirectories(esWorkingDir.toPath())
|
||||
esWorkingDir.delete()
|
||||
}
|
||||
}
|
||||
|
||||
def "test elasticsearch status"() {
|
||||
setup:
|
||||
Response response = client.performRequest("GET", "_cluster/health")
|
||||
|
||||
Map result = new JsonSlurper().parseText(EntityUtils.toString(response.entity))
|
||||
|
||||
expect:
|
||||
result.status == "green"
|
||||
|
||||
assertTraces(TEST_WRITER, 1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "elasticsearch"
|
||||
resourceName "GET _cluster/health"
|
||||
operationName "elasticsearch.rest.query"
|
||||
spanType null
|
||||
parent()
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "elasticsearch-java"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_URL.key" "_cluster/health"
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_PORT.key" HTTP_PORT
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
apply plugin: 'version-scan'
|
||||
|
||||
versionScan {
|
||||
group = "org.elasticsearch"
|
||||
module = "elasticsearch"
|
||||
versions = "[2.0,3)"
|
||||
verifyPresent = [
|
||||
"org.elasticsearch.plugins.SitePlugin": null,
|
||||
]
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
|
||||
apply plugin: 'org.unbroken-dome.test-sets'
|
||||
|
||||
testSets {
|
||||
latestDepTest {
|
||||
dirName = 'test'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly group: 'org.elasticsearch', name: 'elasticsearch', version: '2.0.0'
|
||||
|
||||
compile project(':dd-java-agent:agent-tooling')
|
||||
|
||||
compile deps.bytebuddy
|
||||
compile deps.opentracing
|
||||
annotationProcessor deps.autoservice
|
||||
implementation deps.autoservice
|
||||
|
||||
testCompile project(':dd-java-agent:testing')
|
||||
// Ensure no cross interference
|
||||
testCompile project(':dd-java-agent:instrumentation:elasticsearch-rest-5')
|
||||
testCompile project(':dd-java-agent:instrumentation:elasticsearch-transport-5')
|
||||
// TODO: add netty instrumentation when that is complete.
|
||||
|
||||
testCompile group: 'org.elasticsearch', name: 'elasticsearch', version: '2.0.0'
|
||||
testCompile group: 'net.java.dev.jna', name: 'jna', version: '4.5.1'
|
||||
|
||||
testCompile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.0'
|
||||
testCompile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.0'
|
||||
|
||||
latestDepTestCompile group: 'org.elasticsearch', name: 'elasticsearch', version: '2.+'
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package datadog.trace.instrumentation.elasticsearch2;
|
||||
|
||||
import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderHasClasses;
|
||||
import static io.opentracing.log.Fields.ERROR_OBJECT;
|
||||
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.DDAdvice;
|
||||
import datadog.trace.agent.tooling.DDTransformers;
|
||||
import datadog.trace.agent.tooling.Instrumenter;
|
||||
import datadog.trace.api.DDTags;
|
||||
import io.opentracing.Scope;
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.tag.Tags;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
import java.util.Collections;
|
||||
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import org.elasticsearch.action.Action;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
|
||||
@AutoService(Instrumenter.class)
|
||||
public class Elasticsearch2TransportClientInstrumentation extends Instrumenter.Configurable {
|
||||
|
||||
public Elasticsearch2TransportClientInstrumentation() {
|
||||
super("elasticsearch", "elasticsearch-transport", "elasticsearch-transport-2");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgentBuilder apply(final AgentBuilder agentBuilder) {
|
||||
return agentBuilder
|
||||
.type(
|
||||
not(isInterface()).and(named("org.elasticsearch.client.support.AbstractClient")),
|
||||
// If we want to be more generic, we could instrument the interface instead:
|
||||
// .and(hasSuperType(named("org.elasticsearch.client.ElasticsearchClient"))))
|
||||
classLoaderHasClasses("org.elasticsearch.plugins.SitePlugin"))
|
||||
.transform(DDTransformers.defaultTransformers())
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
isMethod()
|
||||
.and(named("execute"))
|
||||
.and(takesArgument(0, named("org.elasticsearch.action.Action")))
|
||||
.and(takesArgument(1, named("org.elasticsearch.action.ActionRequest")))
|
||||
.and(takesArgument(2, named("org.elasticsearch.action.ActionListener"))),
|
||||
ElasticsearchTransportClientAdvice.class.getName()))
|
||||
.asDecorator();
|
||||
}
|
||||
|
||||
public static class ElasticsearchTransportClientAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static Scope startSpan(
|
||||
@Advice.Argument(0) final Action action,
|
||||
@Advice.Argument(1) final ActionRequest actionRequest,
|
||||
@Advice.Argument(value = 2, readOnly = false)
|
||||
ActionListener<ActionResponse> actionListener) {
|
||||
|
||||
final Scope scope =
|
||||
GlobalTracer.get()
|
||||
.buildSpan("elasticsearch.query")
|
||||
.withTag(DDTags.SERVICE_NAME, "elasticsearch")
|
||||
.withTag(DDTags.RESOURCE_NAME, action.getClass().getSimpleName())
|
||||
.withTag(Tags.COMPONENT.getKey(), "elasticsearch-java")
|
||||
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
|
||||
.withTag("elasticsearch.action", action.getClass().getSimpleName())
|
||||
.withTag("elasticsearch.request", actionRequest.getClass().getSimpleName())
|
||||
.startActive(false);
|
||||
|
||||
actionListener = new TransportActionListener<>(actionListener, scope.span());
|
||||
return scope;
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void stopSpan(
|
||||
@Advice.Enter final Scope scope, @Advice.Thrown final Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
final Span span = scope.span();
|
||||
Tags.ERROR.set(span, true);
|
||||
span.log(Collections.singletonMap(ERROR_OBJECT, throwable));
|
||||
span.finish();
|
||||
}
|
||||
scope.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package datadog.trace.instrumentation.elasticsearch2;
|
||||
|
||||
import static io.opentracing.log.Fields.ERROR_OBJECT;
|
||||
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.tag.Tags;
|
||||
import java.util.Collections;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
|
||||
public class TransportActionListener<T> implements ActionListener<T> {
|
||||
|
||||
private final ActionListener<T> listener;
|
||||
private final Span span;
|
||||
|
||||
public TransportActionListener(final ActionListener<T> listener, final Span span) {
|
||||
this.listener = listener;
|
||||
this.span = span;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(final T response) {
|
||||
if (response instanceof ActionResponse) {
|
||||
final ActionResponse ar = (ActionResponse) response;
|
||||
if (ar.remoteAddress() != null) {
|
||||
Tags.PEER_HOSTNAME.set(span, ar.remoteAddress().getHost());
|
||||
Tags.PEER_PORT.set(span, ar.remoteAddress().getPort());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
listener.onResponse(response);
|
||||
} finally {
|
||||
span.finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable e) {
|
||||
span.log(Collections.singletonMap(ERROR_OBJECT, e));
|
||||
|
||||
try {
|
||||
listener.onFailure(e);
|
||||
} finally {
|
||||
span.finish();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import datadog.trace.agent.test.TestUtils
|
||||
import io.opentracing.tag.Tags
|
||||
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest
|
||||
import org.elasticsearch.common.io.FileSystemUtils
|
||||
import org.elasticsearch.common.settings.Settings
|
||||
import org.elasticsearch.node.Node
|
||||
import org.elasticsearch.node.NodeBuilder
|
||||
import spock.lang.Shared
|
||||
|
||||
import static datadog.trace.agent.test.ListWriterAssert.assertTraces
|
||||
|
||||
class Elasticsearch2NodeClientTest extends AgentTestRunner {
|
||||
static {
|
||||
System.setProperty("dd.integration.elasticsearch.enabled", "true")
|
||||
}
|
||||
|
||||
static final int HTTP_PORT = TestUtils.randomOpenPort()
|
||||
static final int TCP_PORT = TestUtils.randomOpenPort()
|
||||
|
||||
@Shared
|
||||
static Node testNode
|
||||
static File esWorkingDir
|
||||
|
||||
def client = testNode.client()
|
||||
|
||||
def setupSpec() {
|
||||
esWorkingDir = File.createTempFile("test-es-working-dir-", "")
|
||||
esWorkingDir.delete()
|
||||
esWorkingDir.mkdir()
|
||||
esWorkingDir.deleteOnExit()
|
||||
println "ES work dir: $esWorkingDir"
|
||||
|
||||
def settings = Settings.builder()
|
||||
.put("path.home", esWorkingDir.path)
|
||||
.put("http.port", HTTP_PORT)
|
||||
.put("transport.tcp.port", TCP_PORT)
|
||||
.build()
|
||||
testNode = NodeBuilder.newInstance().clusterName("test-cluster").settings(settings).build()
|
||||
testNode.start()
|
||||
}
|
||||
|
||||
def cleanupSpec() {
|
||||
testNode?.close()
|
||||
if (esWorkingDir != null) {
|
||||
FileSystemUtils.deleteSubDirectories(esWorkingDir.toPath())
|
||||
esWorkingDir.delete()
|
||||
}
|
||||
}
|
||||
|
||||
def "test elasticsearch status"() {
|
||||
setup:
|
||||
def result = client.admin().cluster().health(new ClusterHealthRequest(new String[0]))
|
||||
|
||||
def status = result.get().status
|
||||
|
||||
expect:
|
||||
status.name() == "GREEN"
|
||||
|
||||
assertTraces(TEST_WRITER, 1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "elasticsearch"
|
||||
resourceName "ClusterHealthAction"
|
||||
operationName "elasticsearch.query"
|
||||
spanType null
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "elasticsearch-java"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"elasticsearch.action" "ClusterHealthAction"
|
||||
"elasticsearch.request" "ClusterHealthRequest"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import datadog.trace.agent.test.TestUtils
|
||||
import io.opentracing.tag.Tags
|
||||
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest
|
||||
import org.elasticsearch.client.transport.TransportClient
|
||||
import org.elasticsearch.common.io.FileSystemUtils
|
||||
import org.elasticsearch.common.settings.Settings
|
||||
import org.elasticsearch.common.transport.InetSocketTransportAddress
|
||||
import org.elasticsearch.node.Node
|
||||
import org.elasticsearch.node.NodeBuilder
|
||||
import spock.lang.Shared
|
||||
|
||||
import static datadog.trace.agent.test.ListWriterAssert.assertTraces
|
||||
|
||||
class Elasticsearch2TransportClientTest extends AgentTestRunner {
|
||||
static {
|
||||
System.setProperty("dd.integration.elasticsearch.enabled", "true")
|
||||
}
|
||||
|
||||
static final int HTTP_PORT = TestUtils.randomOpenPort()
|
||||
static final int TCP_PORT = TestUtils.randomOpenPort()
|
||||
|
||||
@Shared
|
||||
static Node testNode
|
||||
static File esWorkingDir
|
||||
|
||||
@Shared
|
||||
static TransportClient client
|
||||
|
||||
def setupSpec() {
|
||||
esWorkingDir = File.createTempFile("test-es-working-dir-", "")
|
||||
esWorkingDir.delete()
|
||||
esWorkingDir.mkdir()
|
||||
esWorkingDir.deleteOnExit()
|
||||
println "ES work dir: $esWorkingDir"
|
||||
|
||||
def settings = Settings.builder()
|
||||
.put("path.home", esWorkingDir.path)
|
||||
.put("http.port", HTTP_PORT)
|
||||
.put("transport.tcp.port", TCP_PORT)
|
||||
.build()
|
||||
testNode = NodeBuilder.newInstance().clusterName("test-cluster").settings(settings).build()
|
||||
testNode.start()
|
||||
|
||||
client = TransportClient.builder().settings(
|
||||
Settings.builder()
|
||||
.put("cluster.name", "test-cluster")
|
||||
.build()
|
||||
).build()
|
||||
client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("localhost"), TCP_PORT))
|
||||
}
|
||||
|
||||
def cleanupSpec() {
|
||||
testNode?.close()
|
||||
if (esWorkingDir != null) {
|
||||
FileSystemUtils.deleteSubDirectories(esWorkingDir.toPath())
|
||||
esWorkingDir.delete()
|
||||
}
|
||||
}
|
||||
|
||||
def "test elasticsearch status"() {
|
||||
setup:
|
||||
def result = client.admin().cluster().health(new ClusterHealthRequest(new String[0]))
|
||||
|
||||
def status = result.get().status
|
||||
|
||||
expect:
|
||||
status.name() == "GREEN"
|
||||
|
||||
assertTraces(TEST_WRITER, 1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "elasticsearch"
|
||||
resourceName "ClusterHealthAction"
|
||||
operationName "elasticsearch.query"
|
||||
spanType null
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "elasticsearch-java"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.PEER_HOSTNAME.key" "127.0.0.1"
|
||||
"$Tags.PEER_PORT.key" TCP_PORT
|
||||
"elasticsearch.action" "ClusterHealthAction"
|
||||
"elasticsearch.request" "ClusterHealthRequest"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
apply plugin: 'version-scan'
|
||||
|
||||
versionScan {
|
||||
group = "org.elasticsearch.client"
|
||||
module = "transport"
|
||||
versions = "[5.0,6)"
|
||||
legacyGroup = "org.elasticsearch"
|
||||
legacyModule = "elasticsearch"
|
||||
scanDependencies = true
|
||||
verifyPresent = [
|
||||
"org.elasticsearch.percolator.TransportMultiPercolateAction": null,
|
||||
]
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
|
||||
apply plugin: 'org.unbroken-dome.test-sets'
|
||||
|
||||
testSets {
|
||||
latestDepTest {
|
||||
dirName = 'test'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly group: 'org.elasticsearch.client', name: 'transport', version: '5.0.0'
|
||||
|
||||
compile project(':dd-java-agent:agent-tooling')
|
||||
|
||||
compile deps.bytebuddy
|
||||
compile deps.opentracing
|
||||
annotationProcessor deps.autoservice
|
||||
implementation deps.autoservice
|
||||
|
||||
testCompile project(':dd-java-agent:testing')
|
||||
// Ensure no cross interference
|
||||
testCompile project(':dd-java-agent:instrumentation:elasticsearch-rest-5')
|
||||
// Include httpclient instrumentation for testing because it is a dependency for elasticsearch-rest-client.
|
||||
// It doesn't actually work though. They use HttpAsyncClient, which isn't currently instrumented.
|
||||
// TODO: add HttpAsyncClient instrumentation when that is complete.
|
||||
testCompile project(':dd-java-agent:instrumentation:apache-httpclient-4.3')
|
||||
// TODO: add netty instrumentation when that is complete.
|
||||
|
||||
testCompile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.0'
|
||||
testCompile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.0'
|
||||
|
||||
testCompile group: 'org.elasticsearch.plugin', name: 'transport-netty3-client', version: '5.0.0'
|
||||
testCompile group: 'org.elasticsearch.client', name: 'transport', version: '5.0.0'
|
||||
|
||||
latestDepTestCompile group: 'org.elasticsearch.plugin', name: 'transport-netty3-client', version: '+'
|
||||
latestDepTestCompile group: 'org.elasticsearch.client', name: 'transport', version: '+'
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package datadog.trace.instrumentation.elasticsearch5;
|
||||
|
||||
import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderHasClasses;
|
||||
import static io.opentracing.log.Fields.ERROR_OBJECT;
|
||||
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.DDAdvice;
|
||||
import datadog.trace.agent.tooling.DDTransformers;
|
||||
import datadog.trace.agent.tooling.Instrumenter;
|
||||
import datadog.trace.api.DDTags;
|
||||
import io.opentracing.Scope;
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.tag.Tags;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
import java.util.Collections;
|
||||
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import org.elasticsearch.action.Action;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
|
||||
@AutoService(Instrumenter.class)
|
||||
public class Elasticsearch5TransportClientInstrumentation extends Instrumenter.Configurable {
|
||||
|
||||
public Elasticsearch5TransportClientInstrumentation() {
|
||||
super("elasticsearch", "elasticsearch-transport", "elasticsearch-transport-5");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgentBuilder apply(final AgentBuilder agentBuilder) {
|
||||
return agentBuilder
|
||||
.type(
|
||||
not(isInterface()).and(named("org.elasticsearch.client.support.AbstractClient")),
|
||||
// If we want to be more generic, we could instrument the interface instead:
|
||||
// .and(hasSuperType(named("org.elasticsearch.client.ElasticsearchClient"))))
|
||||
classLoaderHasClasses("org.elasticsearch.percolator.TransportMultiPercolateAction"))
|
||||
.transform(DDTransformers.defaultTransformers())
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
isMethod()
|
||||
.and(named("execute"))
|
||||
.and(takesArgument(0, named("org.elasticsearch.action.Action")))
|
||||
.and(takesArgument(1, named("org.elasticsearch.action.ActionRequest")))
|
||||
.and(takesArgument(2, named("org.elasticsearch.action.ActionListener"))),
|
||||
ElasticsearchTransportClientAdvice.class.getName()))
|
||||
.asDecorator();
|
||||
}
|
||||
|
||||
public static class ElasticsearchTransportClientAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static Scope startSpan(
|
||||
@Advice.Argument(0) final Action action,
|
||||
@Advice.Argument(1) final ActionRequest actionRequest,
|
||||
@Advice.Argument(value = 2, readOnly = false)
|
||||
ActionListener<ActionResponse> actionListener) {
|
||||
|
||||
final Scope scope =
|
||||
GlobalTracer.get()
|
||||
.buildSpan("elasticsearch.query")
|
||||
.withTag(DDTags.SERVICE_NAME, "elasticsearch")
|
||||
.withTag(DDTags.RESOURCE_NAME, action.getClass().getSimpleName())
|
||||
.withTag(Tags.COMPONENT.getKey(), "elasticsearch-java")
|
||||
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
|
||||
.withTag("elasticsearch.action", action.getClass().getSimpleName())
|
||||
.withTag("elasticsearch.request", actionRequest.getClass().getSimpleName())
|
||||
.startActive(false);
|
||||
|
||||
actionListener = new TransportActionListener<>(actionListener, scope.span());
|
||||
return scope;
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void stopSpan(
|
||||
@Advice.Enter final Scope scope, @Advice.Thrown final Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
final Span span = scope.span();
|
||||
Tags.ERROR.set(span, true);
|
||||
span.log(Collections.singletonMap(ERROR_OBJECT, throwable));
|
||||
span.finish();
|
||||
}
|
||||
scope.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package datadog.trace.instrumentation.elasticsearch5;
|
||||
|
||||
import static io.opentracing.log.Fields.ERROR_OBJECT;
|
||||
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.tag.Tags;
|
||||
import java.util.Collections;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
|
||||
public class TransportActionListener<T extends ActionResponse> implements ActionListener<T> {
|
||||
|
||||
private final ActionListener<T> listener;
|
||||
private final Span span;
|
||||
|
||||
public TransportActionListener(final ActionListener<T> listener, final Span span) {
|
||||
this.listener = listener;
|
||||
this.span = span;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(final T response) {
|
||||
if (response.remoteAddress() != null) {
|
||||
Tags.PEER_HOSTNAME.set(span, response.remoteAddress().getHost());
|
||||
Tags.PEER_HOST_IPV4.set(span, response.remoteAddress().getAddress());
|
||||
Tags.PEER_PORT.set(span, response.remoteAddress().getPort());
|
||||
}
|
||||
|
||||
try {
|
||||
listener.onResponse(response);
|
||||
} finally {
|
||||
span.finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Exception e) {
|
||||
span.log(Collections.singletonMap(ERROR_OBJECT, e));
|
||||
|
||||
try {
|
||||
listener.onFailure(e);
|
||||
} finally {
|
||||
span.finish();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import datadog.trace.agent.test.TestUtils
|
||||
import io.opentracing.tag.Tags
|
||||
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest
|
||||
import org.elasticsearch.common.io.FileSystemUtils
|
||||
import org.elasticsearch.common.settings.Settings
|
||||
import org.elasticsearch.env.Environment
|
||||
import org.elasticsearch.node.Node
|
||||
import org.elasticsearch.node.internal.InternalSettingsPreparer
|
||||
import org.elasticsearch.transport.Netty3Plugin
|
||||
import spock.lang.Shared
|
||||
|
||||
import static datadog.trace.agent.test.ListWriterAssert.assertTraces
|
||||
import static org.elasticsearch.cluster.ClusterName.CLUSTER_NAME_SETTING
|
||||
|
||||
class Elasticsearch5NodeClientTest extends AgentTestRunner {
|
||||
static {
|
||||
System.setProperty("dd.integration.elasticsearch.enabled", "true")
|
||||
}
|
||||
|
||||
static final int HTTP_PORT = TestUtils.randomOpenPort()
|
||||
static final int TCP_PORT = TestUtils.randomOpenPort()
|
||||
|
||||
@Shared
|
||||
static Node testNode
|
||||
static File esWorkingDir
|
||||
|
||||
def client = testNode.client()
|
||||
|
||||
def setupSpec() {
|
||||
esWorkingDir = File.createTempFile("test-es-working-dir-", "")
|
||||
esWorkingDir.delete()
|
||||
esWorkingDir.mkdir()
|
||||
esWorkingDir.deleteOnExit()
|
||||
println "ES work dir: $esWorkingDir"
|
||||
|
||||
def settings = Settings.builder()
|
||||
.put("path.home", esWorkingDir.path)
|
||||
.put("http.port", HTTP_PORT)
|
||||
.put("transport.tcp.port", TCP_PORT)
|
||||
.put("transport.type", "netty3")
|
||||
.put("http.type", "netty3")
|
||||
.put(CLUSTER_NAME_SETTING.getKey(), "test-cluster")
|
||||
.build()
|
||||
testNode = new Node(new Environment(InternalSettingsPreparer.prepareSettings(settings)), [Netty3Plugin])
|
||||
testNode.start()
|
||||
}
|
||||
|
||||
def cleanupSpec() {
|
||||
testNode?.close()
|
||||
if (esWorkingDir != null) {
|
||||
FileSystemUtils.deleteSubDirectories(esWorkingDir.toPath())
|
||||
esWorkingDir.delete()
|
||||
}
|
||||
}
|
||||
|
||||
def "test elasticsearch status"() {
|
||||
setup:
|
||||
def result = client.admin().cluster().health(new ClusterHealthRequest())
|
||||
|
||||
def status = result.get().status
|
||||
|
||||
expect:
|
||||
status.name() == "GREEN"
|
||||
|
||||
assertTraces(TEST_WRITER, 1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "elasticsearch"
|
||||
resourceName "ClusterHealthAction"
|
||||
operationName "elasticsearch.query"
|
||||
spanType null
|
||||
parent()
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "elasticsearch-java"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"elasticsearch.action" "ClusterHealthAction"
|
||||
"elasticsearch.request" "ClusterHealthRequest"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import datadog.trace.agent.test.TestUtils
|
||||
import io.opentracing.tag.Tags
|
||||
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest
|
||||
import org.elasticsearch.client.transport.TransportClient
|
||||
import org.elasticsearch.common.io.FileSystemUtils
|
||||
import org.elasticsearch.common.settings.Settings
|
||||
import org.elasticsearch.common.transport.InetSocketTransportAddress
|
||||
import org.elasticsearch.env.Environment
|
||||
import org.elasticsearch.node.Node
|
||||
import org.elasticsearch.node.internal.InternalSettingsPreparer
|
||||
import org.elasticsearch.transport.Netty3Plugin
|
||||
import org.elasticsearch.transport.client.PreBuiltTransportClient
|
||||
import spock.lang.Shared
|
||||
|
||||
import static datadog.trace.agent.test.ListWriterAssert.assertTraces
|
||||
import static org.elasticsearch.cluster.ClusterName.CLUSTER_NAME_SETTING
|
||||
|
||||
class Elasticsearch5TransportClientTest extends AgentTestRunner {
|
||||
static {
|
||||
System.setProperty("dd.integration.elasticsearch.enabled", "true")
|
||||
}
|
||||
|
||||
static final int HTTP_PORT = TestUtils.randomOpenPort()
|
||||
static final int TCP_PORT = TestUtils.randomOpenPort()
|
||||
|
||||
@Shared
|
||||
static Node testNode
|
||||
static File esWorkingDir
|
||||
|
||||
@Shared
|
||||
static TransportClient client
|
||||
|
||||
def setupSpec() {
|
||||
esWorkingDir = File.createTempFile("test-es-working-dir-", "")
|
||||
esWorkingDir.delete()
|
||||
esWorkingDir.mkdir()
|
||||
esWorkingDir.deleteOnExit()
|
||||
println "ES work dir: $esWorkingDir"
|
||||
|
||||
def settings = Settings.builder()
|
||||
.put("path.home", esWorkingDir.path)
|
||||
.put("http.port", HTTP_PORT)
|
||||
.put("transport.tcp.port", TCP_PORT)
|
||||
.put("transport.type", "netty3")
|
||||
.put("http.type", "netty3")
|
||||
.put(CLUSTER_NAME_SETTING.getKey(), "test-cluster")
|
||||
.build()
|
||||
testNode = new Node(new Environment(InternalSettingsPreparer.prepareSettings(settings)), [Netty3Plugin])
|
||||
testNode.start()
|
||||
|
||||
client = new PreBuiltTransportClient(
|
||||
Settings.builder()
|
||||
.put(CLUSTER_NAME_SETTING.getKey(), "test-cluster")
|
||||
.build()
|
||||
)
|
||||
client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("localhost"), TCP_PORT))
|
||||
}
|
||||
|
||||
def cleanupSpec() {
|
||||
testNode?.close()
|
||||
if (esWorkingDir != null) {
|
||||
FileSystemUtils.deleteSubDirectories(esWorkingDir.toPath())
|
||||
esWorkingDir.delete()
|
||||
}
|
||||
}
|
||||
|
||||
def "test elasticsearch status"() {
|
||||
setup:
|
||||
def result = client.admin().cluster().health(new ClusterHealthRequest())
|
||||
|
||||
def status = result.get().status
|
||||
|
||||
expect:
|
||||
status.name() == "GREEN"
|
||||
|
||||
assertTraces(TEST_WRITER, 1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "elasticsearch"
|
||||
resourceName "ClusterHealthAction"
|
||||
operationName "elasticsearch.query"
|
||||
spanType null
|
||||
parent()
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "elasticsearch-java"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.PEER_HOSTNAME.key" "127.0.0.1"
|
||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
||||
"$Tags.PEER_PORT.key" TCP_PORT
|
||||
"elasticsearch.action" "ClusterHealthAction"
|
||||
"elasticsearch.request" "ClusterHealthRequest"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
apply plugin: 'version-scan'
|
||||
|
||||
versionScan {
|
||||
group = "org.elasticsearch.client"
|
||||
module = "transport"
|
||||
versions = "[6.0,)"
|
||||
legacyGroup = "org.elasticsearch"
|
||||
legacyModule = "elasticsearch"
|
||||
scanDependencies = true
|
||||
verifyPresent = [
|
||||
"org.elasticsearch.client.RestClientBuilder\$2": null,
|
||||
]
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
|
||||
apply plugin: 'org.unbroken-dome.test-sets'
|
||||
|
||||
testSets {
|
||||
latestDepTest {
|
||||
dirName = 'test'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly group: 'org.elasticsearch.client', name: 'transport', version: '6.0.0'
|
||||
|
||||
compile project(':dd-java-agent:agent-tooling')
|
||||
|
||||
compile deps.bytebuddy
|
||||
compile deps.opentracing
|
||||
annotationProcessor deps.autoservice
|
||||
implementation deps.autoservice
|
||||
|
||||
testCompile project(':dd-java-agent:testing')
|
||||
// Ensure no cross interference
|
||||
testCompile project(':dd-java-agent:instrumentation:elasticsearch-rest-5')
|
||||
// Include httpclient instrumentation for testing because it is a dependency for elasticsearch-rest-client.
|
||||
// It doesn't actually work though. They use HttpAsyncClient, which isn't currently instrumented.
|
||||
// TODO: add HttpAsyncClient instrumentation when that is complete.
|
||||
testCompile project(':dd-java-agent:instrumentation:apache-httpclient-4.3')
|
||||
// TODO: add netty instrumentation when that is complete.
|
||||
|
||||
testCompile group: 'org.elasticsearch.plugin', name: 'transport-netty4-client', version: '6.0.0'
|
||||
testCompile group: 'org.elasticsearch.client', name: 'transport', version: '6.0.0'
|
||||
|
||||
testCompile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.0'
|
||||
testCompile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.0'
|
||||
|
||||
latestDepTestCompile group: 'org.elasticsearch.plugin', name: 'transport-netty4-client', version: '+'
|
||||
latestDepTestCompile group: 'org.elasticsearch.client', name: 'transport', version: '+'
|
||||
latestDepTestCompile group: 'org.elasticsearch.client', name: 'elasticsearch-rest-client', version: '+'
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package datadog.trace.instrumentation.elasticsearch6;
|
||||
|
||||
import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderHasClasses;
|
||||
import static io.opentracing.log.Fields.ERROR_OBJECT;
|
||||
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.DDAdvice;
|
||||
import datadog.trace.agent.tooling.DDTransformers;
|
||||
import datadog.trace.agent.tooling.Instrumenter;
|
||||
import datadog.trace.api.DDTags;
|
||||
import io.opentracing.Scope;
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.tag.Tags;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
import java.util.Collections;
|
||||
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import org.elasticsearch.action.Action;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
|
||||
/**
|
||||
* Most of this class is identical to version 5's instrumentation, but they changed an interface to
|
||||
* an abstract class, so the bytecode isn't directly compatible.
|
||||
*/
|
||||
@AutoService(Instrumenter.class)
|
||||
public class Elasticsearch6TransportClientInstrumentation extends Instrumenter.Configurable {
|
||||
|
||||
public Elasticsearch6TransportClientInstrumentation() {
|
||||
super("elasticsearch", "elasticsearch-transport", "elasticsearch-transport-6");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgentBuilder apply(final AgentBuilder agentBuilder) {
|
||||
return agentBuilder
|
||||
.type(
|
||||
not(isInterface()).and(named("org.elasticsearch.client.support.AbstractClient")),
|
||||
// If we want to be more generic, we could instrument the interface instead:
|
||||
// .and(hasSuperType(named("org.elasticsearch.client.ElasticsearchClient"))))
|
||||
classLoaderHasClasses("org.elasticsearch.client.RestClientBuilder$2"))
|
||||
.transform(DDTransformers.defaultTransformers())
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
isMethod()
|
||||
.and(named("execute"))
|
||||
.and(takesArgument(0, named("org.elasticsearch.action.Action")))
|
||||
.and(takesArgument(1, named("org.elasticsearch.action.ActionRequest")))
|
||||
.and(takesArgument(2, named("org.elasticsearch.action.ActionListener"))),
|
||||
Elasticsearch6TransportClientAdvice.class.getName()))
|
||||
.asDecorator();
|
||||
}
|
||||
|
||||
public static class Elasticsearch6TransportClientAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static Scope startSpan(
|
||||
@Advice.Argument(0) final Action action,
|
||||
@Advice.Argument(1) final ActionRequest actionRequest,
|
||||
@Advice.Argument(value = 2, readOnly = false)
|
||||
ActionListener<ActionResponse> actionListener) {
|
||||
|
||||
final Scope scope =
|
||||
GlobalTracer.get()
|
||||
.buildSpan("elasticsearch.query")
|
||||
.withTag(DDTags.SERVICE_NAME, "elasticsearch")
|
||||
.withTag(DDTags.RESOURCE_NAME, action.getClass().getSimpleName())
|
||||
.withTag(Tags.COMPONENT.getKey(), "elasticsearch-java")
|
||||
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
|
||||
.withTag("elasticsearch.action", action.getClass().getSimpleName())
|
||||
.withTag("elasticsearch.request", actionRequest.getClass().getSimpleName())
|
||||
.startActive(false);
|
||||
|
||||
actionListener = new TransportActionListener<>(actionListener, scope.span());
|
||||
return scope;
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void stopSpan(
|
||||
@Advice.Enter final Scope scope, @Advice.Thrown final Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
final Span span = scope.span();
|
||||
Tags.ERROR.set(span, true);
|
||||
span.log(Collections.singletonMap(ERROR_OBJECT, throwable));
|
||||
span.finish();
|
||||
}
|
||||
scope.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package datadog.trace.instrumentation.elasticsearch6;
|
||||
|
||||
import static io.opentracing.log.Fields.ERROR_OBJECT;
|
||||
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.tag.Tags;
|
||||
import java.util.Collections;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
|
||||
/**
|
||||
* Most of this class is identical to version 5's instrumentation, but they changed an interface to
|
||||
* an abstract class, so the bytecode isn't directly compatible.
|
||||
*/
|
||||
public class TransportActionListener<T extends ActionResponse> implements ActionListener<T> {
|
||||
|
||||
private final ActionListener<T> listener;
|
||||
private final Span span;
|
||||
|
||||
public TransportActionListener(final ActionListener<T> listener, final Span span) {
|
||||
this.listener = listener;
|
||||
this.span = span;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(final T response) {
|
||||
if (response.remoteAddress() != null) {
|
||||
Tags.PEER_HOSTNAME.set(span, response.remoteAddress().address().getHostName());
|
||||
Tags.PEER_HOST_IPV4.set(span, response.remoteAddress().getAddress());
|
||||
Tags.PEER_PORT.set(span, response.remoteAddress().getPort());
|
||||
}
|
||||
|
||||
try {
|
||||
listener.onResponse(response);
|
||||
} finally {
|
||||
span.finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Exception e) {
|
||||
span.log(Collections.singletonMap(ERROR_OBJECT, e));
|
||||
|
||||
try {
|
||||
listener.onFailure(e);
|
||||
} finally {
|
||||
span.finish();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import datadog.trace.agent.test.TestUtils
|
||||
import io.opentracing.tag.Tags
|
||||
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest
|
||||
import org.elasticsearch.common.io.FileSystemUtils
|
||||
import org.elasticsearch.common.settings.Settings
|
||||
import org.elasticsearch.node.InternalSettingsPreparer
|
||||
import org.elasticsearch.node.Node
|
||||
import org.elasticsearch.transport.Netty4Plugin
|
||||
import spock.lang.Shared
|
||||
|
||||
import static datadog.trace.agent.test.ListWriterAssert.assertTraces
|
||||
import static org.elasticsearch.cluster.ClusterName.CLUSTER_NAME_SETTING
|
||||
|
||||
class Elasticsearch6NodeClientTest extends AgentTestRunner {
|
||||
static {
|
||||
System.setProperty("dd.integration.elasticsearch.enabled", "true")
|
||||
}
|
||||
|
||||
static final int HTTP_PORT = TestUtils.randomOpenPort()
|
||||
static final int TCP_PORT = TestUtils.randomOpenPort()
|
||||
|
||||
@Shared
|
||||
static Node testNode
|
||||
static File esWorkingDir
|
||||
|
||||
def client = testNode.client()
|
||||
|
||||
def setupSpec() {
|
||||
esWorkingDir = File.createTempFile("test-es-working-dir-", "")
|
||||
esWorkingDir.delete()
|
||||
esWorkingDir.mkdir()
|
||||
esWorkingDir.deleteOnExit()
|
||||
println "ES work dir: $esWorkingDir"
|
||||
|
||||
def settings = Settings.builder()
|
||||
.put("path.home", esWorkingDir.path)
|
||||
.put("http.port", HTTP_PORT)
|
||||
.put("transport.tcp.port", TCP_PORT)
|
||||
.put(CLUSTER_NAME_SETTING.getKey(), "test-cluster")
|
||||
.build()
|
||||
testNode = new Node(InternalSettingsPreparer.prepareEnvironment(settings, null), [Netty4Plugin])
|
||||
testNode.start()
|
||||
}
|
||||
|
||||
def cleanupSpec() {
|
||||
testNode?.close()
|
||||
if (esWorkingDir != null) {
|
||||
FileSystemUtils.deleteSubDirectories(esWorkingDir.toPath())
|
||||
esWorkingDir.delete()
|
||||
}
|
||||
}
|
||||
|
||||
def "test elasticsearch status"() {
|
||||
setup:
|
||||
def result = client.admin().cluster().health(new ClusterHealthRequest())
|
||||
|
||||
def status = result.get().status
|
||||
|
||||
expect:
|
||||
status.name() == "GREEN"
|
||||
|
||||
assertTraces(TEST_WRITER, 1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "elasticsearch"
|
||||
resourceName "ClusterHealthAction"
|
||||
operationName "elasticsearch.query"
|
||||
spanType null
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "elasticsearch-java"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"elasticsearch.action" "ClusterHealthAction"
|
||||
"elasticsearch.request" "ClusterHealthRequest"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import datadog.trace.agent.test.TestUtils
|
||||
import io.opentracing.tag.Tags
|
||||
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest
|
||||
import org.elasticsearch.client.transport.TransportClient
|
||||
import org.elasticsearch.common.io.FileSystemUtils
|
||||
import org.elasticsearch.common.settings.Settings
|
||||
import org.elasticsearch.common.transport.TransportAddress
|
||||
import org.elasticsearch.node.InternalSettingsPreparer
|
||||
import org.elasticsearch.node.Node
|
||||
import org.elasticsearch.transport.Netty4Plugin
|
||||
import org.elasticsearch.transport.client.PreBuiltTransportClient
|
||||
import spock.lang.Shared
|
||||
|
||||
import static datadog.trace.agent.test.ListWriterAssert.assertTraces
|
||||
import static org.elasticsearch.cluster.ClusterName.CLUSTER_NAME_SETTING
|
||||
|
||||
class Elasticsearch6TransportClientTest extends AgentTestRunner {
|
||||
static {
|
||||
System.setProperty("dd.integration.elasticsearch.enabled", "true")
|
||||
}
|
||||
|
||||
static final int HTTP_PORT = TestUtils.randomOpenPort()
|
||||
static final int TCP_PORT = TestUtils.randomOpenPort()
|
||||
|
||||
@Shared
|
||||
static Node testNode
|
||||
static File esWorkingDir
|
||||
|
||||
@Shared
|
||||
static TransportClient client
|
||||
|
||||
def setupSpec() {
|
||||
esWorkingDir = File.createTempFile("test-es-working-dir-", "")
|
||||
esWorkingDir.delete()
|
||||
esWorkingDir.mkdir()
|
||||
esWorkingDir.deleteOnExit()
|
||||
println "ES work dir: $esWorkingDir"
|
||||
|
||||
def settings = Settings.builder()
|
||||
.put("path.home", esWorkingDir.path)
|
||||
.put("http.port", HTTP_PORT)
|
||||
.put("transport.tcp.port", TCP_PORT)
|
||||
.put(CLUSTER_NAME_SETTING.getKey(), "test-cluster")
|
||||
.build()
|
||||
testNode = new Node(InternalSettingsPreparer.prepareEnvironment(settings, null), [Netty4Plugin])
|
||||
testNode.start()
|
||||
|
||||
client = new PreBuiltTransportClient(
|
||||
Settings.builder()
|
||||
.put(CLUSTER_NAME_SETTING.getKey(), "test-cluster")
|
||||
.build()
|
||||
)
|
||||
client.addTransportAddress(new TransportAddress(InetAddress.getByName("localhost"), TCP_PORT))
|
||||
}
|
||||
|
||||
def cleanupSpec() {
|
||||
testNode?.close()
|
||||
if (esWorkingDir != null) {
|
||||
FileSystemUtils.deleteSubDirectories(esWorkingDir.toPath())
|
||||
esWorkingDir.delete()
|
||||
}
|
||||
}
|
||||
|
||||
def "test elasticsearch status"() {
|
||||
setup:
|
||||
def result = client.admin().cluster().health(new ClusterHealthRequest())
|
||||
|
||||
def status = result.get().status
|
||||
|
||||
expect:
|
||||
status.name() == "GREEN"
|
||||
|
||||
assertTraces(TEST_WRITER, 1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "elasticsearch"
|
||||
resourceName "ClusterHealthAction"
|
||||
operationName "elasticsearch.query"
|
||||
spanType null
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "elasticsearch-java"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
||||
"$Tags.PEER_PORT.key" TCP_PORT
|
||||
"elasticsearch.action" "ClusterHealthAction"
|
||||
"elasticsearch.request" "ClusterHealthRequest"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
apply plugin: 'version-scan'
|
||||
|
||||
versionScan {
|
||||
group = "javax.ws.rs"
|
||||
module = "jsr311-api"
|
||||
versions = "(,)"
|
||||
}
|
||||
//apply plugin: 'version-scan'
|
||||
//
|
||||
//versionScan {
|
||||
// group = "javax.ws.rs"
|
||||
// module = "jsr311-api"
|
||||
// versions = "(,)"
|
||||
//}
|
||||
|
||||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
apply plugin: 'version-scan'
|
||||
|
||||
versionScan {
|
||||
group = "javax.ws.rs"
|
||||
module = "jsr311-api"
|
||||
versions = "(,)"
|
||||
}
|
||||
//apply plugin: 'version-scan'
|
||||
//
|
||||
//versionScan {
|
||||
// group = "javax.ws.rs"
|
||||
// module = "jsr311-api"
|
||||
// versions = "(,)"
|
||||
//}
|
||||
|
||||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
|
||||
|
|
|
@ -2,8 +2,9 @@ apply plugin: 'version-scan'
|
|||
|
||||
versionScan {
|
||||
group = "io.ratpack"
|
||||
module = 'ratpack'
|
||||
module = 'ratpack-core'
|
||||
versions = "[1.4.0,)"
|
||||
scanMethods = true
|
||||
verifyPresent = [
|
||||
"ratpack.path.PathBinding": "getDescription",
|
||||
]
|
||||
|
|
|
@ -116,14 +116,19 @@ public class DDTracer implements io.opentracing.Tracer {
|
|||
this.sampler = sampler;
|
||||
this.spanTags = defaultSpanTags;
|
||||
|
||||
Runtime.getRuntime()
|
||||
.addShutdownHook(
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
DDTracer.this.close();
|
||||
}
|
||||
});
|
||||
try {
|
||||
Runtime.getRuntime()
|
||||
.addShutdownHook(
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
DDTracer.this.close();
|
||||
}
|
||||
});
|
||||
} catch (final IllegalStateException ex) {
|
||||
// The JVM might be shutting down.
|
||||
log.debug("Error adding shutdown hook.", ex);
|
||||
}
|
||||
|
||||
registry = new CodecRegistry();
|
||||
registry.register(Format.Builtin.HTTP_HEADERS, new HTTPCodec(taggedHeaders));
|
||||
|
|
|
@ -14,6 +14,10 @@ include ':dd-java-agent:instrumentation:aws-java-sdk-1.11.0'
|
|||
include ':dd-java-agent:instrumentation:aws-java-sdk-1.11.106'
|
||||
include ':dd-java-agent:instrumentation:classloaders'
|
||||
include ':dd-java-agent:instrumentation:datastax-cassandra-3.2'
|
||||
include ':dd-java-agent:instrumentation:elasticsearch-rest-5'
|
||||
include ':dd-java-agent:instrumentation:elasticsearch-transport-2'
|
||||
include ':dd-java-agent:instrumentation:elasticsearch-transport-5'
|
||||
include ':dd-java-agent:instrumentation:elasticsearch-transport-6'
|
||||
include ':dd-java-agent:instrumentation:hystrix-1.4'
|
||||
include ':dd-java-agent:instrumentation:jax-rs-annotations'
|
||||
include ':dd-java-agent:instrumentation:jax-rs-client'
|
||||
|
|
Loading…
Reference in New Issue