Merge branch 'master' of github.com:DataDog/dd-trace-java into labbati/atlas-1.1.0-compatibility
This commit is contained in:
commit
8609a07eaf
|
|
@ -95,7 +95,7 @@ public class ClassLoaderMatcher {
|
|||
|
||||
/**
|
||||
* TODO: this turns out to be useless with OSGi: {@code
|
||||
* }org.eclipse.osgi.internal.loader.BundleLoader#isRequestFromVM} returns {@code true} when
|
||||
* org.eclipse.osgi.internal.loader.BundleLoader#isRequestFromVM} returns {@code true} when
|
||||
* class loading is issued from this check and {@code false} for 'real' class loads. We should
|
||||
* come up with some sort of hack to avoid this problem.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -25,8 +25,16 @@ class AkkaHttpClientInstrumentationTest extends HttpClientTest<AkkaHttpClientDec
|
|||
|
||||
def response
|
||||
try {
|
||||
response = Http.get(system).singleRequest(request, materializer).toCompletableFuture().get()
|
||||
response = Http.get(system)
|
||||
.singleRequest(request, materializer)
|
||||
//.whenComplete { result, error ->
|
||||
// FIXME: Callback should be here instead.
|
||||
// callback?.call()
|
||||
//}
|
||||
.toCompletableFuture()
|
||||
.get()
|
||||
} finally {
|
||||
// FIXME: remove this when callback above works.
|
||||
blockUntilChildSpansFinished(1)
|
||||
}
|
||||
callback?.call()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import datadog.trace.agent.test.base.HttpClientTest
|
||||
import datadog.trace.instrumentation.apachehttpasyncclient.ApacheHttpAsyncClientDecorator
|
||||
import io.opentracing.util.GlobalTracer
|
||||
import org.apache.http.HttpResponse
|
||||
import org.apache.http.concurrent.FutureCallback
|
||||
import org.apache.http.impl.nio.client.HttpAsyncClients
|
||||
|
|
@ -22,8 +21,6 @@ class ApacheHttpAsyncClientCallbackTest extends HttpClientTest<ApacheHttpAsyncCl
|
|||
|
||||
@Override
|
||||
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
|
||||
def hasParent = GlobalTracer.get().activeSpan() != null
|
||||
|
||||
def request = new HttpUriRequest(method, uri)
|
||||
headers.entrySet().each {
|
||||
request.addHeader(new BasicHeader(it.key, it.value))
|
||||
|
|
@ -35,21 +32,13 @@ class ApacheHttpAsyncClientCallbackTest extends HttpClientTest<ApacheHttpAsyncCl
|
|||
|
||||
@Override
|
||||
void completed(HttpResponse result) {
|
||||
if (hasParent && GlobalTracer.get().activeSpan() == null) {
|
||||
responseFuture.completeExceptionally(new Exception("Missing span in scope"))
|
||||
} else {
|
||||
responseFuture.complete(result.statusLine.statusCode)
|
||||
}
|
||||
responseFuture.complete(result.statusLine.statusCode)
|
||||
callback?.call()
|
||||
}
|
||||
|
||||
@Override
|
||||
void failed(Exception ex) {
|
||||
if (hasParent && GlobalTracer.get().activeSpan() == null) {
|
||||
responseFuture.completeExceptionally(new Exception("Missing span in scope"))
|
||||
} else {
|
||||
responseFuture.completeExceptionally(ex)
|
||||
}
|
||||
responseFuture.completeExceptionally(ex)
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
muzzle {
|
||||
pass {
|
||||
group = "com.google.http-client"
|
||||
module = "google-http-client"
|
||||
|
||||
// 1.19.0 is the first release. The versions before are betas and RCs
|
||||
versions = "[1.19.0,)"
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
apply plugin: 'org.unbroken-dome.test-sets'
|
||||
|
||||
testSets {
|
||||
latestDepTest {
|
||||
dirName = 'test'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly group: 'com.google.http-client', name: 'google-http-client', version: '1.19.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')
|
||||
testCompile group: 'com.google.http-client', name: 'google-http-client', version: '1.19.0'
|
||||
|
||||
latestDepTestCompile group: 'com.google.http-client', name: 'google-http-client', version: '+'
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package datadog.trace.instrumentation.googlehttpclient;
|
||||
|
||||
import com.google.api.client.http.HttpRequest;
|
||||
import com.google.api.client.http.HttpResponse;
|
||||
import datadog.trace.agent.decorator.HttpClientDecorator;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
public class GoogleHttpClientDecorator extends HttpClientDecorator<HttpRequest, HttpResponse> {
|
||||
public static final GoogleHttpClientDecorator DECORATE = new GoogleHttpClientDecorator();
|
||||
|
||||
@Override
|
||||
protected String method(final HttpRequest httpRequest) {
|
||||
return httpRequest.getRequestMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URI url(final HttpRequest httpRequest) throws URISyntaxException {
|
||||
// Google uses %20 (space) instead of "+" for spaces in the fragment
|
||||
// Add "+" back for consistency with the other http client instrumentations
|
||||
final String url = httpRequest.getUrl().build();
|
||||
final String fixedUrl = url.replaceAll("%20", "+");
|
||||
return new URI(fixedUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String hostname(final HttpRequest httpRequest) {
|
||||
return httpRequest.getUrl().getHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer port(final HttpRequest httpRequest) {
|
||||
return httpRequest.getUrl().getPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer status(final HttpResponse httpResponse) {
|
||||
return httpResponse.getStatusCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] instrumentationNames() {
|
||||
return new String[] {"google-http-client"};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String component() {
|
||||
return "google-http-client";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
package datadog.trace.instrumentation.googlehttpclient;
|
||||
|
||||
import static datadog.trace.instrumentation.googlehttpclient.GoogleHttpClientDecorator.DECORATE;
|
||||
import static java.util.Collections.singletonMap;
|
||||
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.takesArgument;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
|
||||
|
||||
import com.google.api.client.http.HttpRequest;
|
||||
import com.google.api.client.http.HttpResponse;
|
||||
import com.google.auto.service.AutoService;
|
||||
import datadog.trace.agent.tooling.Instrumenter;
|
||||
import datadog.trace.bootstrap.ContextStore;
|
||||
import datadog.trace.bootstrap.InstrumentationContext;
|
||||
import io.opentracing.Scope;
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.log.Fields;
|
||||
import io.opentracing.propagation.Format;
|
||||
import io.opentracing.propagation.TextMap;
|
||||
import io.opentracing.tag.Tags;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
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 class GoogleHttpClientInstrumentation extends Instrumenter.Default {
|
||||
public GoogleHttpClientInstrumentation() {
|
||||
super("google-http-client");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher<? super TypeDescription> typeMatcher() {
|
||||
// HttpRequest is a final class. Only need to instrument it exactly
|
||||
return named("com.google.api.client.http.HttpRequest");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> contextStore() {
|
||||
return Collections.singletonMap(
|
||||
"com.google.api.client.http.HttpRequest", RequestState.class.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] helperClassNames() {
|
||||
return new String[] {
|
||||
"datadog.trace.agent.decorator.BaseDecorator",
|
||||
"datadog.trace.agent.decorator.ClientDecorator",
|
||||
"datadog.trace.agent.decorator.HttpClientDecorator",
|
||||
packageName + ".GoogleHttpClientDecorator",
|
||||
packageName + ".RequestState",
|
||||
getClass().getName() + "$GoogleHttpClientAdvice",
|
||||
getClass().getName() + "$GoogleHttpClientAsyncAdvice",
|
||||
getClass().getName() + "$HeadersInjectAdapter"
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||
final Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>();
|
||||
transformers.put(
|
||||
isMethod().and(isPublic()).and(named("execute")).and(takesArguments(0)),
|
||||
GoogleHttpClientAdvice.class.getName());
|
||||
|
||||
transformers.put(
|
||||
isMethod()
|
||||
.and(isPublic())
|
||||
.and(named("executeAsync"))
|
||||
.and(takesArguments(1))
|
||||
.and(takesArgument(0, (named("java.util.concurrent.Executor")))),
|
||||
GoogleHttpClientAsyncAdvice.class.getName());
|
||||
|
||||
return transformers;
|
||||
}
|
||||
|
||||
public static class GoogleHttpClientAdvice {
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void methodEnter(@Advice.This final HttpRequest request) {
|
||||
|
||||
final ContextStore<HttpRequest, RequestState> contextStore =
|
||||
InstrumentationContext.get(HttpRequest.class, RequestState.class);
|
||||
|
||||
RequestState state = contextStore.get(request);
|
||||
|
||||
if (state == null) {
|
||||
state = new RequestState(GlobalTracer.get().buildSpan("http.request").start());
|
||||
contextStore.put(request, state);
|
||||
}
|
||||
|
||||
final Span span = state.getSpan();
|
||||
|
||||
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
|
||||
DECORATE.afterStart(span);
|
||||
DECORATE.onRequest(span, request);
|
||||
GlobalTracer.get()
|
||||
.inject(span.context(), Format.Builtin.HTTP_HEADERS, new HeadersInjectAdapter(request));
|
||||
}
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void methodExit(
|
||||
@Advice.This final HttpRequest request,
|
||||
@Advice.Return final HttpResponse response,
|
||||
@Advice.Thrown final Throwable throwable) {
|
||||
|
||||
final ContextStore<HttpRequest, RequestState> contextStore =
|
||||
InstrumentationContext.get(HttpRequest.class, RequestState.class);
|
||||
final RequestState state = contextStore.get(request);
|
||||
|
||||
if (state != null) {
|
||||
final Span span = state.getSpan();
|
||||
|
||||
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
|
||||
DECORATE.onResponse(span, response);
|
||||
DECORATE.onError(span, throwable);
|
||||
|
||||
// If HttpRequest.setThrowExceptionOnExecuteError is set to false, there are no exceptions
|
||||
// for a failed request. Thus, check the response code
|
||||
if (response != null && !response.isSuccessStatusCode()) {
|
||||
Tags.ERROR.set(span, true);
|
||||
span.log(singletonMap(Fields.MESSAGE, response.getStatusMessage()));
|
||||
}
|
||||
|
||||
DECORATE.beforeFinish(span);
|
||||
span.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class GoogleHttpClientAsyncAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void methodEnter(@Advice.This final HttpRequest request) {
|
||||
final Span span = GlobalTracer.get().buildSpan("http.request").start();
|
||||
|
||||
final ContextStore<HttpRequest, RequestState> contextStore =
|
||||
InstrumentationContext.get(HttpRequest.class, RequestState.class);
|
||||
|
||||
final RequestState state = new RequestState(span);
|
||||
contextStore.put(request, state);
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void methodExit(
|
||||
@Advice.This final HttpRequest request, @Advice.Thrown final Throwable throwable) {
|
||||
|
||||
if (throwable != null) {
|
||||
|
||||
final ContextStore<HttpRequest, RequestState> contextStore =
|
||||
InstrumentationContext.get(HttpRequest.class, RequestState.class);
|
||||
final RequestState state = contextStore.get(request);
|
||||
|
||||
if (state != null) {
|
||||
final Span span = state.getSpan();
|
||||
|
||||
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
|
||||
DECORATE.onError(span, throwable);
|
||||
|
||||
DECORATE.beforeFinish(span);
|
||||
span.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class HeadersInjectAdapter implements TextMap {
|
||||
private final HttpRequest request;
|
||||
|
||||
public HeadersInjectAdapter(final HttpRequest request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Map.Entry<String, String>> iterator() {
|
||||
throw new UnsupportedOperationException(
|
||||
"This class should be used only with tracer#inject()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(final String key, final String value) {
|
||||
request.getHeaders().put(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package datadog.trace.instrumentation.googlehttpclient;
|
||||
|
||||
import io.opentracing.Span;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
|
||||
@Data
|
||||
public class RequestState {
|
||||
@NonNull public Span span;
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import com.google.api.client.http.GenericUrl
|
||||
import com.google.api.client.http.HttpRequest
|
||||
import com.google.api.client.http.HttpResponse
|
||||
import com.google.api.client.http.javanet.NetHttpTransport
|
||||
import datadog.trace.agent.test.base.HttpClientTest
|
||||
import datadog.trace.api.DDSpanTypes
|
||||
import datadog.trace.instrumentation.googlehttpclient.GoogleHttpClientDecorator
|
||||
import spock.lang.Shared
|
||||
|
||||
abstract class AbstractGoogleHttpClientTest extends HttpClientTest<GoogleHttpClientDecorator> {
|
||||
|
||||
@Shared
|
||||
def requestFactory = new NetHttpTransport().createRequestFactory()
|
||||
|
||||
@Override
|
||||
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
|
||||
doRequest(method, uri, headers, callback, false)
|
||||
}
|
||||
|
||||
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback, boolean throwExceptionOnError) {
|
||||
GenericUrl genericUrl = new GenericUrl(uri)
|
||||
|
||||
HttpRequest request = requestFactory.buildRequest(method, genericUrl, null)
|
||||
request.getHeaders().putAll(headers)
|
||||
request.setThrowExceptionOnExecuteError(throwExceptionOnError)
|
||||
|
||||
HttpResponse response = executeRequest(request)
|
||||
callback?.call()
|
||||
|
||||
return response.getStatusCode()
|
||||
}
|
||||
|
||||
abstract HttpResponse executeRequest(HttpRequest request)
|
||||
|
||||
@Override
|
||||
GoogleHttpClientDecorator decorator() {
|
||||
return GoogleHttpClientDecorator.DECORATE
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean testRedirects() {
|
||||
// Circular redirects don't throw an exception with Google Http Client
|
||||
return false
|
||||
}
|
||||
|
||||
def "error traces when exception is not thrown"() {
|
||||
given:
|
||||
def uri = server.address.resolve("/error")
|
||||
|
||||
when:
|
||||
def status = doRequest(method, uri)
|
||||
|
||||
then:
|
||||
status == 500
|
||||
assertTraces(2) {
|
||||
server.distributedRequestTrace(it, 0, trace(1).last())
|
||||
trace(1, size(1)) {
|
||||
span(0) {
|
||||
resourceName "$method $uri.path"
|
||||
spanType DDSpanTypes.HTTP_CLIENT
|
||||
errored true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
where:
|
||||
method = "GET"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import com.google.api.client.http.HttpRequest
|
||||
import com.google.api.client.http.HttpResponse
|
||||
|
||||
class GoogleHttpClientAsyncTest extends AbstractGoogleHttpClientTest {
|
||||
@Override
|
||||
HttpResponse executeRequest(HttpRequest request) {
|
||||
return request.executeAsync().get()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import com.google.api.client.http.HttpRequest
|
||||
import com.google.api.client.http.HttpResponse
|
||||
|
||||
class GoogleHttpClientTest extends AbstractGoogleHttpClientTest {
|
||||
@Override
|
||||
HttpResponse executeRequest(HttpRequest request) {
|
||||
return request.execute()
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import javax.ws.rs.client.AsyncInvoker
|
|||
import javax.ws.rs.client.Client
|
||||
import javax.ws.rs.client.ClientBuilder
|
||||
import javax.ws.rs.client.Entity
|
||||
import javax.ws.rs.client.InvocationCallback
|
||||
import javax.ws.rs.client.WebTarget
|
||||
import javax.ws.rs.core.MediaType
|
||||
import javax.ws.rs.core.Response
|
||||
|
|
@ -22,8 +23,16 @@ abstract class JaxRsClientAsyncTest extends HttpClientTest<JaxRsClientDecorator>
|
|||
AsyncInvoker request = builder.async()
|
||||
|
||||
def body = BODY_METHODS.contains(method) ? Entity.text("") : null
|
||||
Response response = request.method(method, (Entity) body).get()
|
||||
callback?.call()
|
||||
Response response = request.method(method, (Entity) body, new InvocationCallback<Response>(){
|
||||
@Override
|
||||
void completed(Response s) {
|
||||
callback?.call()
|
||||
}
|
||||
|
||||
@Override
|
||||
void failed(Throwable throwable) {
|
||||
}
|
||||
}).get()
|
||||
|
||||
return response.status
|
||||
}
|
||||
|
|
|
|||
|
|
@ -295,7 +295,6 @@ public enum JDBCConnectionUrlParser {
|
|||
MSSQLSERVER("microsoft", "sqlserver") {
|
||||
private static final String DEFAULT_HOST = "localhost";
|
||||
private static final int DEFAULT_PORT = 1433;
|
||||
private static final String DEFAULT_INSTANCE = "MSSQLSERVER";
|
||||
|
||||
@Override
|
||||
DBInfo.Builder doParse(String jdbcUrl, final DBInfo.Builder builder) {
|
||||
|
|
@ -307,9 +306,6 @@ public enum JDBCConnectionUrlParser {
|
|||
}
|
||||
builder.type("sqlserver");
|
||||
final DBInfo dbInfo = builder.build();
|
||||
if (dbInfo.getInstance() == null) {
|
||||
builder.instance(DEFAULT_INSTANCE);
|
||||
}
|
||||
if (dbInfo.getHost() == null) {
|
||||
builder.host(DEFAULT_HOST);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,12 +88,12 @@ class JDBCConnectionUrlParserTest extends Specification {
|
|||
"address=(host=anotherhost)(port=3306)(user=wrong)(password=PW)/mdbdb?user=mdbuser&password=PW" | null | "mysql" | "replication" | "mdbuser" | "mdb.host" | 3306 | null | "mdbdb"
|
||||
|
||||
//https://docs.microsoft.com/en-us/sql/connect/jdbc/building-the-connection-url
|
||||
"jdbc:microsoft:sqlserver://;" | null | "sqlserver" | null | null | "localhost" | 1433 | "MSSQLSERVER" | null
|
||||
"jdbc:microsoft:sqlserver://;" | stdProps | "sqlserver" | null | "stdUserName" | "stdServerName" | 9999 | "MSSQLSERVER" | "stdDatabaseName"
|
||||
"jdbc:microsoft:sqlserver://;" | null | "sqlserver" | null | null | "localhost" | 1433 | null | null
|
||||
"jdbc:microsoft:sqlserver://;" | stdProps | "sqlserver" | null | "stdUserName" | "stdServerName" | 9999 | null | "stdDatabaseName"
|
||||
"jdbc:sqlserver://ss.host\\ssinstance:44;databaseName=ssdb;user=ssuser;password=pw" | null | "sqlserver" | null | "ssuser" | "ss.host" | 44 | "ssinstance" | "ssdb"
|
||||
"jdbc:sqlserver://;serverName=ss.host\\ssinstance:44;DatabaseName=;" | null | "sqlserver" | null | null | "ss.host" | 44 | "ssinstance" | null
|
||||
"jdbc:sqlserver://ss.host;serverName=althost;DatabaseName=ssdb;" | null | "sqlserver" | null | null | "ss.host" | 1433 | "MSSQLSERVER" | "ssdb"
|
||||
"jdbc:microsoft:sqlserver://ss.host:44;DatabaseName=ssdb;user=ssuser;password=pw;user=ssuser2;" | null | "sqlserver" | null | "ssuser" | "ss.host" | 44 | "MSSQLSERVER" | "ssdb"
|
||||
"jdbc:sqlserver://ss.host;serverName=althost;DatabaseName=ssdb;" | null | "sqlserver" | null | null | "ss.host" | 1433 | null | "ssdb"
|
||||
"jdbc:microsoft:sqlserver://ss.host:44;DatabaseName=ssdb;user=ssuser;password=pw;user=ssuser2;" | null | "sqlserver" | null | "ssuser" | "ss.host" | 44 | null | "ssdb"
|
||||
|
||||
// https://docs.oracle.com/cd/B28359_01/java.111/b31224/urls.htm
|
||||
// https://docs.oracle.com/cd/B28359_01/java.111/b31224/jdbcthin.htm
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ public class TracingIterable implements Iterable<ConsumerRecord> {
|
|||
private final Iterable<ConsumerRecord> delegate;
|
||||
private final String operationName;
|
||||
private final KafkaDecorator decorator;
|
||||
private boolean firstIterator = true;
|
||||
|
||||
public TracingIterable(
|
||||
final Iterable<ConsumerRecord> delegate,
|
||||
|
|
@ -19,6 +20,17 @@ public class TracingIterable implements Iterable<ConsumerRecord> {
|
|||
|
||||
@Override
|
||||
public Iterator<ConsumerRecord> iterator() {
|
||||
return new TracingIterator(delegate.iterator(), operationName, decorator);
|
||||
Iterator<ConsumerRecord> it;
|
||||
// We should only return one iterator with tracing.
|
||||
// However, this is not thread-safe, but usually the first (hopefully only) traversal of
|
||||
// ConsumerRecords is performed in the same thread that called poll()
|
||||
if (this.firstIterator) {
|
||||
it = new TracingIterator(delegate.iterator(), operationName, decorator);
|
||||
firstIterator = false;
|
||||
} else {
|
||||
it = delegate.iterator();
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,19 @@
|
|||
package datadog.trace.instrumentation.kafka_clients;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
||||
|
||||
public class TracingList implements List<ConsumerRecord> {
|
||||
public class TracingList extends TracingIterable implements List<ConsumerRecord> {
|
||||
private final List<ConsumerRecord> delegate;
|
||||
private final String operationName;
|
||||
private final KafkaDecorator decorator;
|
||||
|
||||
public TracingList(
|
||||
final List<ConsumerRecord> delegate,
|
||||
final String operationName,
|
||||
final KafkaDecorator decorator) {
|
||||
super(delegate, operationName, decorator);
|
||||
this.delegate = delegate;
|
||||
this.operationName = operationName;
|
||||
this.decorator = decorator;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -35,11 +31,6 @@ public class TracingList implements List<ConsumerRecord> {
|
|||
return delegate.contains(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ConsumerRecord> iterator() {
|
||||
return new TracingIterator(delegate.iterator(), operationName, decorator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return delegate.toArray();
|
||||
|
|
@ -137,6 +128,10 @@ public class TracingList implements List<ConsumerRecord> {
|
|||
|
||||
@Override
|
||||
public List<ConsumerRecord> subList(final int fromIndex, final int toIndex) {
|
||||
return new TracingList(delegate.subList(fromIndex, toIndex), operationName, decorator);
|
||||
// TODO: the API for subList is not really good to instrument it in context of Kafka
|
||||
// Consumer so we will not do that for now
|
||||
// Kafka is essentially a sequential commit log. We should only enable tracing when traversing
|
||||
// sequentially with an iterator
|
||||
return delegate.subList(fromIndex, toIndex);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import org.apache.kafka.clients.consumer.ConsumerConfig
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord
|
||||
import org.junit.ClassRule
|
||||
import org.apache.kafka.clients.consumer.KafkaConsumer
|
||||
import org.apache.kafka.clients.producer.KafkaProducer
|
||||
import org.apache.kafka.clients.producer.ProducerRecord
|
||||
import org.apache.kafka.common.TopicPartition
|
||||
import org.junit.Rule
|
||||
import org.springframework.kafka.core.DefaultKafkaConsumerFactory
|
||||
import org.springframework.kafka.core.DefaultKafkaProducerFactory
|
||||
import org.springframework.kafka.core.KafkaTemplate
|
||||
|
|
@ -9,7 +14,6 @@ import org.springframework.kafka.listener.MessageListener
|
|||
import org.springframework.kafka.test.rule.KafkaEmbedded
|
||||
import org.springframework.kafka.test.utils.ContainerTestUtils
|
||||
import org.springframework.kafka.test.utils.KafkaTestUtils
|
||||
import spock.lang.Shared
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
|
@ -17,8 +21,7 @@ import java.util.concurrent.TimeUnit
|
|||
class KafkaClientTest extends AgentTestRunner {
|
||||
static final SHARED_TOPIC = "shared.topic"
|
||||
|
||||
@Shared
|
||||
@ClassRule
|
||||
@Rule
|
||||
KafkaEmbedded embeddedKafka = new KafkaEmbedded(1, true, SHARED_TOPIC)
|
||||
|
||||
def "test kafka produce and consume"() {
|
||||
|
|
@ -121,4 +124,83 @@ class KafkaClientTest extends AgentTestRunner {
|
|||
container?.stop()
|
||||
}
|
||||
|
||||
def "test records(TopicPartition) kafka consume"() {
|
||||
setup:
|
||||
|
||||
// set up the Kafka consumer properties
|
||||
def kafkaPartition = 0
|
||||
def consumerProperties = KafkaTestUtils.consumerProps("sender", "false", embeddedKafka)
|
||||
consumerProperties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest")
|
||||
def consumer = new KafkaConsumer<String,String>(consumerProperties)
|
||||
|
||||
def senderProps = KafkaTestUtils.senderProps(embeddedKafka.getBrokersAsString())
|
||||
def producer = new KafkaProducer(senderProps)
|
||||
|
||||
consumer.assign(Arrays.asList(new TopicPartition(SHARED_TOPIC, kafkaPartition)))
|
||||
|
||||
when:
|
||||
def greeting = "Hello from MockConsumer!"
|
||||
producer.send(new ProducerRecord<Integer, String>(SHARED_TOPIC, kafkaPartition, null, greeting))
|
||||
|
||||
then:
|
||||
TEST_WRITER.waitForTraces(1)
|
||||
def records = new LinkedBlockingQueue<ConsumerRecord<String, String>>()
|
||||
def pollResult = KafkaTestUtils.getRecords(consumer)
|
||||
|
||||
def recs = pollResult.records(new TopicPartition(SHARED_TOPIC, kafkaPartition)).iterator()
|
||||
|
||||
def first = null
|
||||
if (recs.hasNext()) {
|
||||
first = recs.next()
|
||||
}
|
||||
|
||||
then:
|
||||
recs.hasNext() == false
|
||||
first.value() == greeting
|
||||
first.key() == null
|
||||
|
||||
assertTraces(2) {
|
||||
trace(0, 1) {
|
||||
// PRODUCER span 0
|
||||
span(0) {
|
||||
serviceName "kafka"
|
||||
operationName "kafka.produce"
|
||||
resourceName "Produce Topic $SHARED_TOPIC"
|
||||
spanType "queue"
|
||||
errored false
|
||||
parent()
|
||||
tags {
|
||||
"component" "java-kafka"
|
||||
"span.kind" "producer"
|
||||
"kafka.partition" { it >= 0 }
|
||||
defaultTags(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
trace(1, 1) {
|
||||
// CONSUMER span 0
|
||||
span(0) {
|
||||
serviceName "kafka"
|
||||
operationName "kafka.consume"
|
||||
resourceName "Consume Topic $SHARED_TOPIC"
|
||||
spanType "queue"
|
||||
errored false
|
||||
childOf TEST_WRITER[0][0]
|
||||
tags {
|
||||
"component" "java-kafka"
|
||||
"span.kind" "consumer"
|
||||
"partition" { it >= 0 }
|
||||
"offset" 0
|
||||
defaultTags(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
consumer.close()
|
||||
producer.close()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ public class AttributeKeys {
|
|||
public static final AttributeKey<Span> CLIENT_ATTRIBUTE_KEY =
|
||||
attributeKey(HttpClientTracingHandler.class.getName() + ".span");
|
||||
|
||||
public static final AttributeKey<Span> CLIENT_PARENT_ATTRIBUTE_KEY =
|
||||
attributeKey(HttpClientTracingHandler.class.getName() + ".parent");
|
||||
|
||||
/**
|
||||
* Generate an attribute key or reuse the one existing in the global app map. This implementation
|
||||
* creates attributes only once even if the current class is loaded by several class loaders and
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import io.netty.channel.ChannelPromise;
|
|||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.opentracing.Scope;
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.Tracer;
|
||||
import io.opentracing.propagation.Format;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
import java.net.InetSocketAddress;
|
||||
|
|
@ -34,19 +35,19 @@ public class HttpClientRequestTracingHandler extends ChannelOutboundHandlerAdapt
|
|||
|
||||
final HttpRequest request = (HttpRequest) msg;
|
||||
|
||||
final Span span = GlobalTracer.get().buildSpan("netty.client.request").start();
|
||||
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
|
||||
final Tracer tracer = GlobalTracer.get();
|
||||
ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY).set(tracer.activeSpan());
|
||||
|
||||
final Span span = tracer.buildSpan("netty.client.request").start();
|
||||
try (final Scope scope = tracer.scopeManager().activate(span, false)) {
|
||||
DECORATE.afterStart(span);
|
||||
DECORATE.onRequest(span, request);
|
||||
DECORATE.onPeerConnection(span, (InetSocketAddress) ctx.channel().remoteAddress());
|
||||
|
||||
// AWS calls are often signed, so we can't add headers without breaking the signature.
|
||||
if (!request.headers().contains("amz-sdk-invocation-id")) {
|
||||
GlobalTracer.get()
|
||||
.inject(
|
||||
span.context(),
|
||||
Format.Builtin.HTTP_HEADERS,
|
||||
new NettyResponseInjectAdapter(request));
|
||||
tracer.inject(
|
||||
span.context(), Format.Builtin.HTTP_HEADERS, new NettyResponseInjectAdapter(request));
|
||||
}
|
||||
|
||||
ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).set(span);
|
||||
|
|
|
|||
|
|
@ -7,44 +7,38 @@ import datadog.trace.instrumentation.netty40.AttributeKeys;
|
|||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.util.Attribute;
|
||||
import io.opentracing.Scope;
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.tag.Tags;
|
||||
import io.opentracing.noop.NoopSpan;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
|
||||
public class HttpClientResponseTracingHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
@Override
|
||||
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
|
||||
final Attribute<Span> parentAttr =
|
||||
ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY);
|
||||
parentAttr.setIfAbsent(NoopSpan.INSTANCE);
|
||||
final Span parent = parentAttr.get();
|
||||
final Span span = ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).get();
|
||||
if (span == null) {
|
||||
ctx.fireChannelRead(msg);
|
||||
return;
|
||||
|
||||
final boolean finishSpan = msg instanceof HttpResponse;
|
||||
|
||||
if (span != null && finishSpan) {
|
||||
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
|
||||
DECORATE.onResponse(span, (HttpResponse) msg);
|
||||
DECORATE.beforeFinish(span);
|
||||
span.finish();
|
||||
}
|
||||
}
|
||||
|
||||
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
|
||||
final boolean finishSpan = msg instanceof HttpResponse;
|
||||
|
||||
// We want the callback in the scope of the parent, not the client span
|
||||
try (final Scope scope = GlobalTracer.get().scopeManager().activate(parent, false)) {
|
||||
if (scope instanceof TraceScope) {
|
||||
((TraceScope) scope).setAsyncPropagation(true);
|
||||
}
|
||||
try {
|
||||
ctx.fireChannelRead(msg);
|
||||
} catch (final Throwable throwable) {
|
||||
if (finishSpan) {
|
||||
DECORATE.onError(span, throwable);
|
||||
DECORATE.beforeFinish(span);
|
||||
Tags.HTTP_STATUS.set(span, 500);
|
||||
span.finish(); // Finish the span manually since finishSpanOnClose was false
|
||||
throw throwable;
|
||||
}
|
||||
}
|
||||
|
||||
if (finishSpan) {
|
||||
DECORATE.onResponse(span, (HttpResponse) msg);
|
||||
DECORATE.beforeFinish(span);
|
||||
span.finish(); // Finish the span manually since finishSpanOnClose was false
|
||||
}
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import datadog.trace.agent.test.base.HttpClientTest
|
||||
import datadog.trace.instrumentation.netty40.client.NettyHttpClientDecorator
|
||||
import io.opentracing.tag.Tags
|
||||
import org.asynchttpclient.AsyncCompletionHandler
|
||||
import org.asynchttpclient.AsyncHttpClient
|
||||
import org.asynchttpclient.DefaultAsyncHttpClientConfig
|
||||
import org.asynchttpclient.Response
|
||||
import spock.lang.Shared
|
||||
|
||||
import java.util.concurrent.ExecutionException
|
||||
|
|
@ -25,8 +27,13 @@ class Netty40ClientTest extends HttpClientTest<NettyHttpClientDecorator> {
|
|||
def methodName = "prepare" + method.toLowerCase().capitalize()
|
||||
def requestBuilder = asyncHttpClient."$methodName"(uri.toString())
|
||||
headers.each { requestBuilder.setHeader(it.key, it.value) }
|
||||
def response = requestBuilder.execute().get()
|
||||
callback?.call()
|
||||
def response = requestBuilder.execute(new AsyncCompletionHandler() {
|
||||
@Override
|
||||
Object onCompleted(Response response) throws Exception {
|
||||
callback?.call()
|
||||
return response
|
||||
}
|
||||
}).get()
|
||||
return response.statusCode
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +73,7 @@ class Netty40ClientTest extends HttpClientTest<NettyHttpClientDecorator> {
|
|||
and:
|
||||
assertTraces(1) {
|
||||
trace(0, 2) {
|
||||
basicSpan(it, 0, "parent", thrownException)
|
||||
basicSpan(it, 0, "parent", null, thrownException)
|
||||
|
||||
span(1) {
|
||||
operationName "netty.connect"
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ public class AttributeKeys {
|
|||
public static final AttributeKey<Span> CLIENT_ATTRIBUTE_KEY =
|
||||
attributeKey(HttpClientTracingHandler.class.getName() + ".span");
|
||||
|
||||
public static final AttributeKey<Span> CLIENT_PARENT_ATTRIBUTE_KEY =
|
||||
attributeKey(HttpClientTracingHandler.class.getName() + ".parent");
|
||||
|
||||
/**
|
||||
* Generate an attribute key or reuse the one existing in the global app map. This implementation
|
||||
* creates attributes only once even if the current class is loaded by several class loaders and
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ public class ChannelFutureListenerInstrumentation extends Instrumenter.Default {
|
|||
public String[] helperClassNames() {
|
||||
return new String[] {
|
||||
packageName + ".AttributeKeys",
|
||||
packageName + ".AttributeKeys$1",
|
||||
"datadog.trace.agent.decorator.BaseDecorator",
|
||||
// client helpers
|
||||
"datadog.trace.agent.decorator.ClientDecorator",
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ public class NettyChannelPipelineInstrumentation extends Instrumenter.Default {
|
|||
public String[] helperClassNames() {
|
||||
return new String[] {
|
||||
packageName + ".AttributeKeys",
|
||||
packageName + ".AttributeKeys$1",
|
||||
"datadog.trace.agent.decorator.BaseDecorator",
|
||||
// client helpers
|
||||
"datadog.trace.agent.decorator.ClientDecorator",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import io.netty.channel.ChannelPromise;
|
|||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.opentracing.Scope;
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.Tracer;
|
||||
import io.opentracing.propagation.Format;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
import java.net.InetSocketAddress;
|
||||
|
|
@ -34,19 +35,19 @@ public class HttpClientRequestTracingHandler extends ChannelOutboundHandlerAdapt
|
|||
|
||||
final HttpRequest request = (HttpRequest) msg;
|
||||
|
||||
final Span span = GlobalTracer.get().buildSpan("netty.client.request").start();
|
||||
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
|
||||
final Tracer tracer = GlobalTracer.get();
|
||||
ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY).set(tracer.activeSpan());
|
||||
|
||||
final Span span = tracer.buildSpan("netty.client.request").start();
|
||||
try (final Scope scope = tracer.scopeManager().activate(span, false)) {
|
||||
DECORATE.afterStart(span);
|
||||
DECORATE.onRequest(span, request);
|
||||
DECORATE.onPeerConnection(span, (InetSocketAddress) ctx.channel().remoteAddress());
|
||||
|
||||
// AWS calls are often signed, so we can't add headers without breaking the signature.
|
||||
if (!request.headers().contains("amz-sdk-invocation-id")) {
|
||||
GlobalTracer.get()
|
||||
.inject(
|
||||
span.context(),
|
||||
Format.Builtin.HTTP_HEADERS,
|
||||
new NettyResponseInjectAdapter(request));
|
||||
tracer.inject(
|
||||
span.context(), Format.Builtin.HTTP_HEADERS, new NettyResponseInjectAdapter(request));
|
||||
}
|
||||
|
||||
ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).set(span);
|
||||
|
|
|
|||
|
|
@ -7,44 +7,38 @@ import datadog.trace.instrumentation.netty41.AttributeKeys;
|
|||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.util.Attribute;
|
||||
import io.opentracing.Scope;
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.tag.Tags;
|
||||
import io.opentracing.noop.NoopSpan;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
|
||||
public class HttpClientResponseTracingHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
@Override
|
||||
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
|
||||
final Attribute<Span> parentAttr =
|
||||
ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY);
|
||||
parentAttr.setIfAbsent(NoopSpan.INSTANCE);
|
||||
final Span parent = parentAttr.get();
|
||||
final Span span = ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).get();
|
||||
if (span == null) {
|
||||
ctx.fireChannelRead(msg);
|
||||
return;
|
||||
|
||||
final boolean finishSpan = msg instanceof HttpResponse;
|
||||
|
||||
if (span != null && finishSpan) {
|
||||
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
|
||||
DECORATE.onResponse(span, (HttpResponse) msg);
|
||||
DECORATE.beforeFinish(span);
|
||||
span.finish();
|
||||
}
|
||||
}
|
||||
|
||||
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
|
||||
final boolean finishSpan = msg instanceof HttpResponse;
|
||||
|
||||
// We want the callback in the scope of the parent, not the client span
|
||||
try (final Scope scope = GlobalTracer.get().scopeManager().activate(parent, false)) {
|
||||
if (scope instanceof TraceScope) {
|
||||
((TraceScope) scope).setAsyncPropagation(true);
|
||||
}
|
||||
try {
|
||||
ctx.fireChannelRead(msg);
|
||||
} catch (final Throwable throwable) {
|
||||
if (finishSpan) {
|
||||
DECORATE.onError(span, throwable);
|
||||
DECORATE.beforeFinish(span);
|
||||
Tags.HTTP_STATUS.set(span, 500);
|
||||
span.finish(); // Finish the span manually since finishSpanOnClose was false
|
||||
throw throwable;
|
||||
}
|
||||
}
|
||||
|
||||
if (finishSpan) {
|
||||
DECORATE.onResponse(span, (HttpResponse) msg);
|
||||
DECORATE.beforeFinish(span);
|
||||
span.finish(); // Finish the span manually since finishSpanOnClose was false
|
||||
}
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,10 @@ import io.netty.channel.ChannelInitializer
|
|||
import io.netty.channel.embedded.EmbeddedChannel
|
||||
import io.netty.handler.codec.http.HttpClientCodec
|
||||
import io.opentracing.tag.Tags
|
||||
import org.asynchttpclient.AsyncCompletionHandler
|
||||
import org.asynchttpclient.AsyncHttpClient
|
||||
import org.asynchttpclient.DefaultAsyncHttpClientConfig
|
||||
import org.asynchttpclient.Response
|
||||
import spock.lang.Shared
|
||||
|
||||
import java.util.concurrent.ExecutionException
|
||||
|
|
@ -33,8 +35,13 @@ class Netty41ClientTest extends HttpClientTest<NettyHttpClientDecorator> {
|
|||
def methodName = "prepare" + method.toLowerCase().capitalize()
|
||||
def requestBuilder = asyncHttpClient."$methodName"(uri.toString())
|
||||
headers.each { requestBuilder.setHeader(it.key, it.value) }
|
||||
def response = requestBuilder.execute().get()
|
||||
callback?.call()
|
||||
def response = requestBuilder.execute(new AsyncCompletionHandler() {
|
||||
@Override
|
||||
Object onCompleted(Response response) throws Exception {
|
||||
callback?.call()
|
||||
return response
|
||||
}
|
||||
}).get()
|
||||
return response.statusCode
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +82,7 @@ class Netty41ClientTest extends HttpClientTest<NettyHttpClientDecorator> {
|
|||
and:
|
||||
assertTraces(1) {
|
||||
trace(0, 2) {
|
||||
basicSpan(it, 0, "parent", thrownException)
|
||||
basicSpan(it, 0, "parent", null, thrownException)
|
||||
|
||||
span(1) {
|
||||
operationName "netty.connect"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
import static datadog.trace.agent.test.server.http.TestHttpServer.distributedRequestTrace
|
||||
import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer
|
||||
import static datadog.trace.agent.test.utils.PortUtils.UNUSABLE_PORT
|
||||
|
||||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import datadog.trace.agent.test.utils.OkHttpUtils
|
||||
import datadog.trace.api.DDSpanTypes
|
||||
|
|
@ -12,8 +8,6 @@ import io.opentracing.Scope
|
|||
import io.opentracing.Span
|
||||
import io.opentracing.tag.Tags
|
||||
import io.opentracing.util.GlobalTracer
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.regex.Pattern
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
|
|
@ -26,21 +20,55 @@ import ratpack.http.client.HttpClient
|
|||
import ratpack.path.PathBinding
|
||||
import ratpack.test.exec.ExecHarness
|
||||
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.regex.Pattern
|
||||
|
||||
import static datadog.trace.agent.test.server.http.TestHttpServer.distributedRequestTrace
|
||||
import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer
|
||||
import static datadog.trace.agent.test.utils.PortUtils.UNUSABLE_PORT
|
||||
|
||||
class RatpackTest extends AgentTestRunner {
|
||||
|
||||
OkHttpClient client = OkHttpUtils.client()
|
||||
|
||||
def "test path call"() {
|
||||
def "test bindings for #path"() {
|
||||
setup:
|
||||
def app = GroovyEmbeddedApp.ratpack {
|
||||
handlers {
|
||||
get {
|
||||
context.render("success")
|
||||
prefix("a") {
|
||||
all {
|
||||
context.render(context.get(PathBinding).description)
|
||||
}
|
||||
}
|
||||
prefix("b/::\\d+") {
|
||||
all {
|
||||
context.render(context.get(PathBinding).description)
|
||||
}
|
||||
}
|
||||
prefix("c/:val?") {
|
||||
all {
|
||||
context.render(context.get(PathBinding).description)
|
||||
}
|
||||
}
|
||||
prefix("d/:val") {
|
||||
all {
|
||||
context.render(context.get(PathBinding).description)
|
||||
}
|
||||
}
|
||||
prefix("e/:val?:\\d+") {
|
||||
all {
|
||||
context.render(context.get(PathBinding).description)
|
||||
}
|
||||
}
|
||||
prefix("f/:val:\\d+") {
|
||||
all {
|
||||
context.render(context.get(PathBinding).description)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
def request = new Request.Builder()
|
||||
.url(app.address.toURL())
|
||||
.url(HttpUrl.get(app.address).newBuilder().addPathSegments(path).build())
|
||||
.get()
|
||||
.build()
|
||||
|
||||
|
|
@ -49,12 +77,12 @@ class RatpackTest extends AgentTestRunner {
|
|||
|
||||
then:
|
||||
resp.code() == 200
|
||||
resp.body.string() == "success"
|
||||
resp.body.string() == route
|
||||
|
||||
assertTraces(1) {
|
||||
trace(0, 2) {
|
||||
span(0) {
|
||||
resourceName "GET /"
|
||||
resourceName "GET /$route"
|
||||
serviceName "unnamed-java-app"
|
||||
operationName "netty.request"
|
||||
spanType DDSpanTypes.HTTP_SERVER
|
||||
|
|
@ -65,7 +93,7 @@ class RatpackTest extends AgentTestRunner {
|
|||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 200
|
||||
"$Tags.HTTP_URL.key" "$app.address"
|
||||
"$Tags.HTTP_URL.key" "${app.address.resolve(path)}"
|
||||
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
|
||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
||||
"$Tags.PEER_PORT.key" Integer
|
||||
|
|
@ -73,7 +101,7 @@ class RatpackTest extends AgentTestRunner {
|
|||
}
|
||||
}
|
||||
span(1) {
|
||||
resourceName "GET /"
|
||||
resourceName "GET /$route"
|
||||
serviceName "unnamed-java-app"
|
||||
operationName "ratpack.handler"
|
||||
spanType DDSpanTypes.HTTP_SERVER
|
||||
|
|
@ -84,7 +112,7 @@ class RatpackTest extends AgentTestRunner {
|
|||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 200
|
||||
"$Tags.HTTP_URL.key" "$app.address"
|
||||
"$Tags.HTTP_URL.key" "${app.address.resolve(path)}"
|
||||
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
|
||||
"$Tags.PEER_PORT.key" Integer
|
||||
defaultTags()
|
||||
|
|
@ -92,85 +120,35 @@ class RatpackTest extends AgentTestRunner {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test path with bindings call"() {
|
||||
setup:
|
||||
def app = GroovyEmbeddedApp.ratpack {
|
||||
handlers {
|
||||
prefix(":foo/:bar?") {
|
||||
get("baz") {ctx ->
|
||||
context.render(ctx.get(PathBinding).description)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
def request = new Request.Builder()
|
||||
.url(HttpUrl.get(app.address).newBuilder().addPathSegments("a/b/baz").build())
|
||||
.get()
|
||||
.build()
|
||||
|
||||
when:
|
||||
def resp = client.newCall(request).execute()
|
||||
|
||||
then:
|
||||
resp.code() == 200
|
||||
resp.body.string() == ":foo/:bar?/baz"
|
||||
|
||||
assertTraces(1) {
|
||||
trace(0, 2) {
|
||||
span(0) {
|
||||
resourceName "GET /:foo/:bar?/baz"
|
||||
serviceName "unnamed-java-app"
|
||||
operationName "netty.request"
|
||||
spanType DDSpanTypes.HTTP_SERVER
|
||||
parent()
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "netty"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 200
|
||||
"$Tags.HTTP_URL.key" "${app.address}a/b/baz"
|
||||
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
|
||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
||||
"$Tags.PEER_PORT.key" Integer
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
resourceName "GET /:foo/:bar?/baz"
|
||||
serviceName "unnamed-java-app"
|
||||
operationName "ratpack.handler"
|
||||
spanType DDSpanTypes.HTTP_SERVER
|
||||
childOf(span(0))
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "ratpack"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 200
|
||||
"$Tags.HTTP_URL.key" "${app.address}a/b/baz"
|
||||
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
|
||||
"$Tags.PEER_PORT.key" Integer
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
where:
|
||||
path | route
|
||||
"a" | "a"
|
||||
"b/123" | "b/::\\d+"
|
||||
"c" | "c/:val?"
|
||||
"c/123" | "c/:val?"
|
||||
"c/foo" | "c/:val?"
|
||||
"d/123" | "d/:val"
|
||||
"d/foo" | "d/:val"
|
||||
"e" | "e/:val?:\\d+"
|
||||
"e/123" | "e/:val?:\\d+"
|
||||
"e/foo" | "e/:val?:\\d+"
|
||||
"f/123" | "f/:val:\\d+"
|
||||
}
|
||||
|
||||
def "test handler error response"() {
|
||||
setup:
|
||||
def app = GroovyEmbeddedApp.ratpack {
|
||||
handlers {
|
||||
get {
|
||||
0 / 0
|
||||
prefix("handler-error") {
|
||||
all {
|
||||
0 / 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
def request = new Request.Builder()
|
||||
.url(app.address.toURL())
|
||||
.url(app.address.resolve("/handler-error?query=param").toURL())
|
||||
.get()
|
||||
.build()
|
||||
when:
|
||||
|
|
@ -181,7 +159,7 @@ class RatpackTest extends AgentTestRunner {
|
|||
assertTraces(1) {
|
||||
trace(0, 2) {
|
||||
span(0) {
|
||||
resourceName "GET /"
|
||||
resourceName "GET /handler-error"
|
||||
serviceName "unnamed-java-app"
|
||||
operationName "netty.request"
|
||||
spanType DDSpanTypes.HTTP_SERVER
|
||||
|
|
@ -192,7 +170,7 @@ class RatpackTest extends AgentTestRunner {
|
|||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 500
|
||||
"$Tags.HTTP_URL.key" "$app.address"
|
||||
"$Tags.HTTP_URL.key" "${app.address.resolve('handler-error')}"
|
||||
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
|
||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
||||
"$Tags.PEER_PORT.key" Integer
|
||||
|
|
@ -201,7 +179,7 @@ class RatpackTest extends AgentTestRunner {
|
|||
}
|
||||
}
|
||||
span(1) {
|
||||
resourceName "GET /"
|
||||
resourceName "GET /handler-error"
|
||||
serviceName "unnamed-java-app"
|
||||
operationName "ratpack.handler"
|
||||
spanType DDSpanTypes.HTTP_SERVER
|
||||
|
|
@ -212,7 +190,7 @@ class RatpackTest extends AgentTestRunner {
|
|||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 500
|
||||
"$Tags.HTTP_URL.key" "$app.address"
|
||||
"$Tags.HTTP_URL.key" "${app.address.resolve('handler-error')}"
|
||||
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
|
||||
"$Tags.PEER_PORT.key" Integer
|
||||
errorTags(HandlerException, Pattern.compile("java.lang.ArithmeticException: Division( is)? undefined"))
|
||||
|
|
@ -227,7 +205,7 @@ class RatpackTest extends AgentTestRunner {
|
|||
setup:
|
||||
def app = GroovyEmbeddedApp.ratpack {
|
||||
handlers {
|
||||
get {
|
||||
get("promise-error") {
|
||||
Promise.async {
|
||||
0 / 0
|
||||
}.then {
|
||||
|
|
@ -237,7 +215,7 @@ class RatpackTest extends AgentTestRunner {
|
|||
}
|
||||
}
|
||||
def request = new Request.Builder()
|
||||
.url(app.address.toURL())
|
||||
.url(app.address.resolve("promise-error?query=param").toURL())
|
||||
.get()
|
||||
.build()
|
||||
when:
|
||||
|
|
@ -248,7 +226,7 @@ class RatpackTest extends AgentTestRunner {
|
|||
assertTraces(1) {
|
||||
trace(0, 2) {
|
||||
span(0) {
|
||||
resourceName "GET /"
|
||||
resourceName "GET /promise-error"
|
||||
serviceName "unnamed-java-app"
|
||||
operationName "netty.request"
|
||||
spanType DDSpanTypes.HTTP_SERVER
|
||||
|
|
@ -259,7 +237,7 @@ class RatpackTest extends AgentTestRunner {
|
|||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 500
|
||||
"$Tags.HTTP_URL.key" "$app.address"
|
||||
"$Tags.HTTP_URL.key" "${app.address.resolve('promise-error')}"
|
||||
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
|
||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
||||
"$Tags.PEER_PORT.key" Integer
|
||||
|
|
@ -268,7 +246,7 @@ class RatpackTest extends AgentTestRunner {
|
|||
}
|
||||
}
|
||||
span(1) {
|
||||
resourceName "GET /"
|
||||
resourceName "GET /promise-error"
|
||||
serviceName "unnamed-java-app"
|
||||
operationName "ratpack.handler"
|
||||
spanType DDSpanTypes.HTTP_SERVER
|
||||
|
|
@ -279,7 +257,7 @@ class RatpackTest extends AgentTestRunner {
|
|||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 500
|
||||
"$Tags.HTTP_URL.key" "$app.address"
|
||||
"$Tags.HTTP_URL.key" "${app.address.resolve('promise-error')}"
|
||||
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
|
||||
"$Tags.PEER_PORT.key" Integer
|
||||
"$Tags.ERROR.key" true
|
||||
|
|
@ -294,7 +272,7 @@ class RatpackTest extends AgentTestRunner {
|
|||
setup:
|
||||
def app = GroovyEmbeddedApp.ratpack {
|
||||
handlers {
|
||||
get {
|
||||
all {
|
||||
context.render(Promise.sync {
|
||||
return "fail " + 0 / 0
|
||||
})
|
||||
|
|
@ -302,7 +280,7 @@ class RatpackTest extends AgentTestRunner {
|
|||
}
|
||||
}
|
||||
def request = new Request.Builder()
|
||||
.url(app.address.toURL())
|
||||
.url(app.address.resolve("?query=param").toURL())
|
||||
.get()
|
||||
.build()
|
||||
when:
|
||||
|
|
@ -374,13 +352,13 @@ class RatpackTest extends AgentTestRunner {
|
|||
|
||||
def app = GroovyEmbeddedApp.ratpack {
|
||||
handlers {
|
||||
get {HttpClient httpClient ->
|
||||
get { HttpClient httpClient ->
|
||||
// 1st internal http client call to nested
|
||||
httpClient.get(HttpUrlBuilder.base(external.address).path("nested").build())
|
||||
.map {it.body.text}
|
||||
.flatMap {t ->
|
||||
.map { it.body.text }
|
||||
.flatMap { t ->
|
||||
// make a 2nd http request and concatenate the two bodies together
|
||||
httpClient.get(HttpUrlBuilder.base(external.address).path("nested2").build()) map {t + it.body.text}
|
||||
httpClient.get(HttpUrlBuilder.base(external.address).path("nested2").build()) map { t + it.body.text }
|
||||
}
|
||||
.then {
|
||||
context.render(it)
|
||||
|
|
@ -452,7 +430,7 @@ class RatpackTest extends AgentTestRunner {
|
|||
serviceName "unnamed-java-app"
|
||||
operationName "netty.client.request"
|
||||
spanType DDSpanTypes.HTTP_CLIENT
|
||||
childOf(span(3))
|
||||
childOf(span(1))
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "netty-client"
|
||||
|
|
@ -496,9 +474,9 @@ class RatpackTest extends AgentTestRunner {
|
|||
|
||||
def app = GroovyEmbeddedApp.ratpack {
|
||||
handlers {
|
||||
get {HttpClient httpClient ->
|
||||
get { HttpClient httpClient ->
|
||||
httpClient.get(badAddress)
|
||||
.map {it.body.text}
|
||||
.map { it.body.text }
|
||||
.then {
|
||||
context.render(it)
|
||||
}
|
||||
|
|
@ -681,7 +659,7 @@ class RatpackTest extends AgentTestRunner {
|
|||
scope.span().setBaggageItem("test-baggage", "foo")
|
||||
ParallelBatch.of(testPromise(), testPromise())
|
||||
.yield()
|
||||
.map({now ->
|
||||
.map({ now ->
|
||||
// close the scope now that we got the baggage inside the promises
|
||||
scope.close()
|
||||
return now
|
||||
|
|
|
|||
|
|
@ -429,7 +429,7 @@ class RatpackTest extends AgentTestRunner {
|
|||
serviceName "unnamed-java-app"
|
||||
operationName "netty.client.request"
|
||||
spanType DDSpanTypes.HTTP_CLIENT
|
||||
childOf(span(3))
|
||||
childOf(span(1))
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "netty-client"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
package datadog.trace.instrumentation.springwebflux.client;
|
||||
|
||||
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
|
||||
import static java.util.Collections.singletonMap;
|
||||
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.takesArgument;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import datadog.trace.agent.tooling.Instrumenter;
|
||||
import java.util.Map;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
|
||||
@AutoService(Instrumenter.class)
|
||||
public class DefaultWebClientInstrumentation extends Instrumenter.Default {
|
||||
|
||||
public DefaultWebClientInstrumentation() {
|
||||
super("spring-webflux", "spring-webflux-client");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] helperClassNames() {
|
||||
return new String[] {
|
||||
"datadog.trace.agent.decorator.BaseDecorator",
|
||||
"datadog.trace.agent.decorator.ClientDecorator",
|
||||
"datadog.trace.agent.decorator.HttpClientDecorator",
|
||||
packageName + ".SpringWebfluxHttpClientDecorator",
|
||||
packageName + ".HttpHeadersInjectAdapter",
|
||||
packageName + ".TracingClientResponseSubscriber",
|
||||
packageName + ".TracingClientResponseSubscriber$1",
|
||||
packageName + ".TracingClientResponseMono",
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher<? super TypeDescription> typeMatcher() {
|
||||
return safeHasSuperType(
|
||||
named("org.springframework.web.reactive.function.client.ExchangeFunction"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||
return singletonMap(
|
||||
isMethod()
|
||||
.and(isPublic())
|
||||
.and(named("exchange"))
|
||||
.and(
|
||||
takesArgument(
|
||||
0, named("org.springframework.web.reactive.function.client.ClientRequest"))),
|
||||
packageName + ".DefaultWebClientAdvice");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package datadog.trace.instrumentation.springwebflux;
|
||||
package datadog.trace.instrumentation.springwebflux.server;
|
||||
|
||||
import datadog.trace.agent.tooling.Instrumenter;
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package datadog.trace.instrumentation.springwebflux;
|
||||
package datadog.trace.instrumentation.springwebflux.server;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package datadog.trace.instrumentation.springwebflux;
|
||||
package datadog.trace.instrumentation.springwebflux.server;
|
||||
|
||||
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
|
||||
import static java.util.Collections.singletonMap;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package datadog.trace.instrumentation.springwebflux;
|
||||
package datadog.trace.instrumentation.springwebflux.server;
|
||||
|
||||
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
|
||||
import static java.util.Collections.singletonMap;
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package datadog.trace.instrumentation.springwebflux.client;
|
||||
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import org.springframework.web.reactive.function.client.ClientRequest;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.ExchangeFunction;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public class DefaultWebClientAdvice {
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void methodExit(
|
||||
@Advice.Thrown final Throwable throwable,
|
||||
@Advice.This final ExchangeFunction exchangeFunction,
|
||||
@Advice.Argument(0) final ClientRequest clientRequest,
|
||||
@Advice.Return(readOnly = false) Mono<ClientResponse> mono) {
|
||||
if (throwable == null
|
||||
&& mono != null
|
||||
// The response of the org.springframework.web.reactive.function.client.ExchangeFunction.exchange method is
|
||||
// replaced by a decorator that in turn also calls the
|
||||
// org.springframework.web.reactive.function.client.ExchangeFunction.exchange method. If the original return value
|
||||
// is already decorated (we detect this if the "x-datadog-trace-id" is added), the result is not decorated again
|
||||
// to avoid StackOverflowErrors.
|
||||
&& !clientRequest.headers().keySet().contains("x-datadog-trace-id")) {
|
||||
mono = new TracingClientResponseMono(clientRequest, exchangeFunction, GlobalTracer.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package datadog.trace.instrumentation.springwebflux.client;
|
||||
|
||||
import io.opentracing.propagation.TextMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
public class HttpHeadersInjectAdapter implements TextMap {
|
||||
|
||||
private final HttpHeaders headers;
|
||||
|
||||
public HttpHeadersInjectAdapter(final HttpHeaders headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Map.Entry<String, String>> iterator() {
|
||||
throw new UnsupportedOperationException("This class should be used only with Tracer.inject()!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(final String key, final String value) {
|
||||
headers.set(key, value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package datadog.trace.instrumentation.springwebflux.client;
|
||||
|
||||
import datadog.trace.agent.decorator.HttpClientDecorator;
|
||||
import io.opentracing.Span;
|
||||
import java.net.URI;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.reactive.function.client.ClientRequest;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
|
||||
@Slf4j
|
||||
public class SpringWebfluxHttpClientDecorator
|
||||
extends HttpClientDecorator<ClientRequest, ClientResponse> {
|
||||
public static final SpringWebfluxHttpClientDecorator DECORATE =
|
||||
new SpringWebfluxHttpClientDecorator();
|
||||
|
||||
public void onCancel(final Span span) {
|
||||
span.setTag("event", "cancelled");
|
||||
span.setTag("message", "The subscription was cancelled");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] instrumentationNames() {
|
||||
return new String[] {"spring-webflux", "spring-webflux-client"};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String component() {
|
||||
return "spring-webflux-client";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String method(final ClientRequest httpRequest) {
|
||||
return httpRequest.method().name();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URI url(final ClientRequest httpRequest) {
|
||||
return httpRequest.url();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String hostname(final ClientRequest httpRequest) {
|
||||
return httpRequest.url().getHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer port(final ClientRequest httpRequest) {
|
||||
return httpRequest.url().getPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer status(final ClientResponse httpResponse) {
|
||||
return httpResponse.statusCode().value();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package datadog.trace.instrumentation.springwebflux.client;
|
||||
|
||||
import static datadog.trace.instrumentation.springwebflux.client.SpringWebfluxHttpClientDecorator.DECORATE;
|
||||
|
||||
import datadog.trace.context.TraceScope;
|
||||
import io.opentracing.Scope;
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.Tracer;
|
||||
import io.opentracing.propagation.Format;
|
||||
import io.opentracing.tag.Tags;
|
||||
import org.springframework.web.reactive.function.client.ClientRequest;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.ExchangeFunction;
|
||||
import reactor.core.CoreSubscriber;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.context.Context;
|
||||
|
||||
public class TracingClientResponseMono extends Mono<ClientResponse> {
|
||||
|
||||
private final ClientRequest clientRequest;
|
||||
private final ExchangeFunction exchangeFunction;
|
||||
private final Tracer tracer;
|
||||
|
||||
public TracingClientResponseMono(
|
||||
final ClientRequest clientRequest,
|
||||
final ExchangeFunction exchangeFunction,
|
||||
final Tracer tracer) {
|
||||
this.clientRequest = clientRequest;
|
||||
this.exchangeFunction = exchangeFunction;
|
||||
this.tracer = tracer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribe(final CoreSubscriber<? super ClientResponse> subscriber) {
|
||||
final Context context = subscriber.currentContext();
|
||||
final Span parentSpan = context.<Span>getOrEmpty(Span.class).orElseGet(tracer::activeSpan);
|
||||
|
||||
final Span span =
|
||||
tracer
|
||||
.buildSpan("http.request")
|
||||
.asChildOf(parentSpan)
|
||||
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
|
||||
.start();
|
||||
DECORATE.afterStart(span);
|
||||
|
||||
try (final Scope scope = tracer.scopeManager().activate(span, false)) {
|
||||
|
||||
if (scope instanceof TraceScope) {
|
||||
((TraceScope) scope).setAsyncPropagation(true);
|
||||
}
|
||||
|
||||
final ClientRequest mutatedRequest =
|
||||
ClientRequest.from(clientRequest)
|
||||
.headers(
|
||||
httpHeaders ->
|
||||
tracer.inject(
|
||||
span.context(),
|
||||
Format.Builtin.HTTP_HEADERS,
|
||||
new HttpHeadersInjectAdapter(httpHeaders)))
|
||||
.build();
|
||||
exchangeFunction
|
||||
.exchange(mutatedRequest)
|
||||
.subscribe(
|
||||
new TracingClientResponseSubscriber(subscriber, mutatedRequest, context, span, parentSpan));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
package datadog.trace.instrumentation.springwebflux.client;
|
||||
|
||||
import static datadog.trace.instrumentation.springwebflux.client.SpringWebfluxHttpClientDecorator.DECORATE;
|
||||
|
||||
import datadog.trace.context.TraceScope;
|
||||
import io.opentracing.Scope;
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.Tracer;
|
||||
import io.opentracing.noop.NoopSpan;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.springframework.web.reactive.function.client.ClientRequest;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import reactor.core.CoreSubscriber;
|
||||
import reactor.util.context.Context;
|
||||
|
||||
public class TracingClientResponseSubscriber implements CoreSubscriber<ClientResponse> {
|
||||
|
||||
private final Tracer tracer = GlobalTracer.get();
|
||||
private final CoreSubscriber<? super ClientResponse> subscriber;
|
||||
private final ClientRequest clientRequest;
|
||||
private final Context context;
|
||||
private final AtomicReference<Span> spanRef;
|
||||
private final Span parentSpan;
|
||||
|
||||
public TracingClientResponseSubscriber(
|
||||
final CoreSubscriber<? super ClientResponse> subscriber,
|
||||
final ClientRequest clientRequest,
|
||||
final Context context,
|
||||
final Span span,
|
||||
final Span parentSpan) {
|
||||
this.subscriber = subscriber;
|
||||
this.clientRequest = clientRequest;
|
||||
this.context = context;
|
||||
spanRef = new AtomicReference<>(span);
|
||||
this.parentSpan = parentSpan == null ? NoopSpan.INSTANCE : parentSpan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscribe(final Subscription subscription) {
|
||||
final Span span = spanRef.get();
|
||||
if (span == null) {
|
||||
subscriber.onSubscribe(subscription);
|
||||
return;
|
||||
}
|
||||
|
||||
try (final Scope scope = tracer.scopeManager().activate(span, false)) {
|
||||
|
||||
if (scope instanceof TraceScope) {
|
||||
((TraceScope) scope).setAsyncPropagation(true);
|
||||
}
|
||||
|
||||
DECORATE.onRequest(span, clientRequest);
|
||||
|
||||
subscriber.onSubscribe(
|
||||
new Subscription() {
|
||||
@Override
|
||||
public void request(final long n) {
|
||||
try (final Scope scope = tracer.scopeManager().activate(span, false)) {
|
||||
subscription.request(n);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
DECORATE.onCancel(span);
|
||||
DECORATE.beforeFinish(span);
|
||||
subscription.cancel();
|
||||
span.finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(final ClientResponse clientResponse) {
|
||||
final Span span = spanRef.getAndSet(null);
|
||||
if (span != null) {
|
||||
DECORATE.onResponse(span, clientResponse);
|
||||
DECORATE.beforeFinish(span);
|
||||
span.finish();
|
||||
}
|
||||
|
||||
try (final Scope scope = tracer.scopeManager().activate(parentSpan, false)) {
|
||||
|
||||
if (scope instanceof TraceScope) {
|
||||
((TraceScope) scope).setAsyncPropagation(true);
|
||||
}
|
||||
|
||||
subscriber.onNext(clientResponse);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(final Throwable throwable) {
|
||||
final Span span = spanRef.getAndSet(null);
|
||||
if (span != null) {
|
||||
DECORATE.onError(span, throwable);
|
||||
DECORATE.beforeFinish(span);
|
||||
span.finish();
|
||||
}
|
||||
|
||||
try (final Scope scope = tracer.scopeManager().activate(parentSpan, false)) {
|
||||
|
||||
if (scope instanceof TraceScope) {
|
||||
((TraceScope) scope).setAsyncPropagation(true);
|
||||
}
|
||||
|
||||
subscriber.onError(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
final Span span = spanRef.getAndSet(null);
|
||||
if (span != null) {
|
||||
DECORATE.beforeFinish(span);
|
||||
span.finish();
|
||||
}
|
||||
|
||||
try (final Scope scope = tracer.scopeManager().activate(parentSpan, false)) {
|
||||
|
||||
if (scope instanceof TraceScope) {
|
||||
((TraceScope) scope).setAsyncPropagation(true);
|
||||
}
|
||||
|
||||
subscriber.onComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context currentContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
package datadog.trace.instrumentation.springwebflux;
|
||||
package datadog.trace.instrumentation.springwebflux.server;
|
||||
|
||||
import static datadog.trace.instrumentation.springwebflux.SpringWebfluxHttpServerDecorator.DECORATE;
|
||||
import static datadog.trace.instrumentation.springwebflux.server.SpringWebfluxHttpServerDecorator.DECORATE;
|
||||
|
||||
import datadog.trace.instrumentation.reactor.core.ReactorCoreAdviceUtils;
|
||||
import io.opentracing.Span;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.reactive.function.client.ClientRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
|
|
@ -39,4 +40,10 @@ public class AdviceUtils {
|
|||
ReactorCoreAdviceUtils.finishSpanIfPresent(
|
||||
(Span) serverRequest.attributes().remove(SPAN_ATTRIBUTE), throwable);
|
||||
}
|
||||
|
||||
public static void finishSpanIfPresent(
|
||||
final ClientRequest clientRequest, final Throwable throwable) {
|
||||
ReactorCoreAdviceUtils.finishSpanIfPresent(
|
||||
(Span) clientRequest.attributes().remove(SPAN_ATTRIBUTE), throwable);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package datadog.trace.instrumentation.springwebflux;
|
||||
package datadog.trace.instrumentation.springwebflux.server;
|
||||
|
||||
import static datadog.trace.instrumentation.springwebflux.SpringWebfluxHttpServerDecorator.DECORATE;
|
||||
import static datadog.trace.instrumentation.springwebflux.server.SpringWebfluxHttpServerDecorator.DECORATE;
|
||||
|
||||
import datadog.trace.context.TraceScope;
|
||||
import datadog.trace.instrumentation.reactor.core.ReactorCoreAdviceUtils;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package datadog.trace.instrumentation.springwebflux;
|
||||
package datadog.trace.instrumentation.springwebflux.server;
|
||||
|
||||
import static datadog.trace.instrumentation.springwebflux.SpringWebfluxHttpServerDecorator.DECORATE;
|
||||
import static datadog.trace.instrumentation.springwebflux.server.SpringWebfluxHttpServerDecorator.DECORATE;
|
||||
|
||||
import datadog.trace.api.DDTags;
|
||||
import datadog.trace.context.TraceScope;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package datadog.trace.instrumentation.springwebflux;
|
||||
package datadog.trace.instrumentation.springwebflux.server;
|
||||
|
||||
import datadog.trace.api.DDTags;
|
||||
import io.opentracing.Span;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package datadog.trace.instrumentation.springwebflux;
|
||||
package datadog.trace.instrumentation.springwebflux.server;
|
||||
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import org.springframework.web.reactive.function.server.HandlerFunction;
|
||||
|
|
@ -14,10 +14,10 @@ public class RouterFunctionAdvice {
|
|||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void methodExit(
|
||||
@Advice.This final RouterFunction thiz,
|
||||
@Advice.Argument(0) final ServerRequest serverRequest,
|
||||
@Advice.Return(readOnly = false) Mono<HandlerFunction<?>> result,
|
||||
@Advice.Thrown final Throwable throwable) {
|
||||
@Advice.This final RouterFunction thiz,
|
||||
@Advice.Argument(0) final ServerRequest serverRequest,
|
||||
@Advice.Return(readOnly = false) Mono<HandlerFunction<?>> result,
|
||||
@Advice.Thrown final Throwable throwable) {
|
||||
if (throwable == null) {
|
||||
result = result.doOnSuccessOrError(new RouteOnSuccessOrError(thiz, serverRequest));
|
||||
} else {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package datadog.trace.instrumentation.springwebflux;
|
||||
package datadog.trace.instrumentation.springwebflux.server;
|
||||
|
||||
import datadog.trace.agent.decorator.ServerDecorator;
|
||||
import datadog.trace.api.DDSpanTypes;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import dd.trace.instrumentation.springwebflux.SpringWebFluxTestApplication
|
||||
import dd.trace.instrumentation.springwebflux.server.SpringWebFluxTestApplication
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.boot.test.context.TestConfiguration
|
||||
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import datadog.trace.agent.test.utils.OkHttpUtils
|
||||
import datadog.trace.api.DDSpanTypes
|
||||
import dd.trace.instrumentation.springwebflux.EchoHandlerFunction
|
||||
import dd.trace.instrumentation.springwebflux.FooModel
|
||||
import dd.trace.instrumentation.springwebflux.SpringWebFluxTestApplication
|
||||
import dd.trace.instrumentation.springwebflux.TestController
|
||||
import dd.trace.instrumentation.springwebflux.server.EchoHandlerFunction
|
||||
import dd.trace.instrumentation.springwebflux.server.FooModel
|
||||
import dd.trace.instrumentation.springwebflux.server.SpringWebFluxTestApplication
|
||||
import dd.trace.instrumentation.springwebflux.server.TestController
|
||||
import io.opentracing.tag.Tags
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
package dd.trace.instrumentation.springwebflux.client
|
||||
|
||||
import datadog.trace.agent.test.asserts.TraceAssert
|
||||
import datadog.trace.agent.test.base.HttpClientTest
|
||||
import datadog.trace.api.DDSpanTypes
|
||||
import datadog.trace.api.DDTags
|
||||
import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator
|
||||
import datadog.trace.instrumentation.springwebflux.client.SpringWebfluxHttpClientDecorator
|
||||
import io.opentracing.tag.Tags
|
||||
import io.opentracing.util.GlobalTracer
|
||||
import org.springframework.http.HttpMethod
|
||||
import org.springframework.web.reactive.function.client.ClientResponse
|
||||
import org.springframework.web.reactive.function.client.WebClient
|
||||
import spock.lang.Shared
|
||||
|
||||
class SpringWebfluxHttpClientTest extends HttpClientTest<SpringWebfluxHttpClientDecorator> {
|
||||
|
||||
@Shared
|
||||
def client = WebClient.builder().build()
|
||||
|
||||
@Override
|
||||
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
|
||||
def hasParent = GlobalTracer.get().activeSpan() != null
|
||||
ClientResponse response = client.method(HttpMethod.resolve(method))
|
||||
.headers { h -> headers.forEach({ key, value -> h.add(key, value) }) }
|
||||
.uri(uri)
|
||||
.exchange()
|
||||
.doOnSuccessOrError { success, error ->
|
||||
blockUntilChildSpansFinished(1)
|
||||
callback?.call()
|
||||
}
|
||||
.block()
|
||||
|
||||
if(hasParent) {
|
||||
blockUntilChildSpansFinished(callback ? 3 : 2)
|
||||
}
|
||||
response.statusCode().value()
|
||||
}
|
||||
|
||||
@Override
|
||||
SpringWebfluxHttpClientDecorator decorator() {
|
||||
return SpringWebfluxHttpClientDecorator.DECORATE
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
// parent spanRef must be cast otherwise it breaks debugging classloading (junit loads it early)
|
||||
void clientSpan(TraceAssert trace, int index, Object parentSpan, String method = "GET", boolean renameService = false, boolean tagQueryString = false, URI uri = server.address.resolve("/success"), Integer status = 200, Throwable exception = null) {
|
||||
super.clientSpan(trace, index, parentSpan, method, renameService, tagQueryString, uri, status, exception)
|
||||
if (!exception) {
|
||||
trace.span(index + 1) {
|
||||
childOf(trace.span(index))
|
||||
serviceName renameService ? "localhost" : "unnamed-java-app"
|
||||
operationName "netty.client.request"
|
||||
resourceName "$method $uri.path"
|
||||
spanType DDSpanTypes.HTTP_CLIENT
|
||||
errored exception != null
|
||||
tags {
|
||||
defaultTags()
|
||||
if (exception) {
|
||||
errorTags(exception.class, exception.message)
|
||||
}
|
||||
"$Tags.COMPONENT.key" NettyHttpClientDecorator.DECORATE.component()
|
||||
if (status) {
|
||||
"$Tags.HTTP_STATUS.key" status
|
||||
}
|
||||
"$Tags.HTTP_URL.key" "${uri.resolve(uri.path)}"
|
||||
if (tagQueryString) {
|
||||
"$DDTags.HTTP_QUERY" uri.query
|
||||
"$DDTags.HTTP_FRAGMENT" { it == null || it == uri.fragment } // Optional
|
||||
}
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_PORT.key" uri.port
|
||||
"$Tags.PEER_HOST_IPV4.key" { it == null || it == "127.0.0.1" } // Optional
|
||||
"$Tags.HTTP_METHOD.key" method
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int size(int size) {
|
||||
return size + 1
|
||||
}
|
||||
|
||||
boolean testRedirects() {
|
||||
false
|
||||
}
|
||||
|
||||
boolean testConnectionFailure() {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dd.trace.instrumentation.springwebflux
|
||||
package dd.trace.instrumentation.springwebflux.server
|
||||
|
||||
import datadog.trace.api.Trace
|
||||
import org.springframework.http.MediaType
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dd.trace.instrumentation.springwebflux
|
||||
package dd.trace.instrumentation.springwebflux.server
|
||||
|
||||
|
||||
import org.springframework.web.reactive.function.server.HandlerFunction
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dd.trace.instrumentation.springwebflux
|
||||
package dd.trace.instrumentation.springwebflux.server
|
||||
|
||||
class FooModel {
|
||||
public long id
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dd.trace.instrumentation.springwebflux
|
||||
package dd.trace.instrumentation.springwebflux.server
|
||||
|
||||
import datadog.trace.api.Trace
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dd.trace.instrumentation.springwebflux
|
||||
package dd.trace.instrumentation.springwebflux.server
|
||||
|
||||
import datadog.trace.api.Trace
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dd.trace.instrumentation.springwebflux;
|
||||
package dd.trace.instrumentation.springwebflux.server;
|
||||
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
|
||||
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||
|
|
@ -2,8 +2,10 @@ package datadog.trace.instrumentation.trace_annotation;
|
|||
|
||||
import static datadog.trace.instrumentation.trace_annotation.TraceDecorator.DECORATE;
|
||||
|
||||
import datadog.trace.api.DDTags;
|
||||
import datadog.trace.api.Trace;
|
||||
import io.opentracing.Scope;
|
||||
import io.opentracing.Tracer;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
import java.lang.reflect.Method;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
|
|
@ -12,12 +14,20 @@ public class TraceAdvice {
|
|||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static Scope startSpan(@Advice.Origin final Method method) {
|
||||
final Trace trace = method.getAnnotation(Trace.class);
|
||||
String operationName = trace == null ? null : trace.operationName();
|
||||
final Trace traceAnnotation = method.getAnnotation(Trace.class);
|
||||
String operationName = traceAnnotation == null ? null : traceAnnotation.operationName();
|
||||
if (operationName == null || operationName.isEmpty()) {
|
||||
operationName = DECORATE.spanNameForMethod(method);
|
||||
}
|
||||
return DECORATE.afterStart(GlobalTracer.get().buildSpan(operationName).startActive(true));
|
||||
|
||||
Tracer.SpanBuilder spanBuilder = GlobalTracer.get().buildSpan(operationName);
|
||||
|
||||
final String resourceName = traceAnnotation == null ? null : traceAnnotation.resourceName();
|
||||
if (resourceName != null && !resourceName.isEmpty()) {
|
||||
spanBuilder = spanBuilder.withTag(DDTags.RESOURCE_NAME, resourceName);
|
||||
}
|
||||
|
||||
return DECORATE.afterStart(spanBuilder.startActive(true));
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,77 @@ class TraceAnnotationsTest extends AgentTestRunner {
|
|||
}
|
||||
}
|
||||
|
||||
def "test simple case with only operation name set"() {
|
||||
setup:
|
||||
// Test single span in new trace
|
||||
SayTracedHello.sayHA()
|
||||
|
||||
expect:
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "test"
|
||||
resourceName "SAY_HA"
|
||||
operationName "SAY_HA"
|
||||
spanType "DB"
|
||||
parent()
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "trace"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test simple case with only resource name set"() {
|
||||
setup:
|
||||
// Test single span in new trace
|
||||
SayTracedHello.sayHelloOnlyResourceSet()
|
||||
|
||||
expect:
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "test"
|
||||
resourceName "WORLD"
|
||||
operationName "SayTracedHello.sayHelloOnlyResourceSet"
|
||||
parent()
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "trace"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test simple case with both resource and operation name set"() {
|
||||
setup:
|
||||
// Test single span in new trace
|
||||
SayTracedHello.sayHAWithResource()
|
||||
|
||||
expect:
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "test"
|
||||
resourceName "EARTH"
|
||||
operationName "SAY_HA"
|
||||
spanType "DB"
|
||||
parent()
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "trace"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test complex case annotations"() {
|
||||
when:
|
||||
// Test new trace with 2 children spans
|
||||
|
|
@ -82,6 +153,94 @@ class TraceAnnotationsTest extends AgentTestRunner {
|
|||
}
|
||||
}
|
||||
|
||||
def "test complex case with resource name at top level"() {
|
||||
when:
|
||||
// Test new trace with 2 children spans
|
||||
SayTracedHello.sayHELLOsayHAWithResource()
|
||||
|
||||
then:
|
||||
assertTraces(1) {
|
||||
trace(0, 3) {
|
||||
span(0) {
|
||||
resourceName "WORLD"
|
||||
operationName "NEW_TRACE"
|
||||
parent()
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "trace"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
resourceName "SAY_HA"
|
||||
operationName "SAY_HA"
|
||||
spanType "DB"
|
||||
childOf span(0)
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "trace"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
serviceName "test"
|
||||
resourceName "SayTracedHello.sayHello"
|
||||
operationName "SayTracedHello.sayHello"
|
||||
childOf span(0)
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "trace"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test complex case with resource name at various levels"() {
|
||||
when:
|
||||
// Test new trace with 2 children spans
|
||||
SayTracedHello.sayHELLOsayHAMixedResourceChildren()
|
||||
|
||||
then:
|
||||
assertTraces(1) {
|
||||
trace(0, 3) {
|
||||
span(0) {
|
||||
resourceName "WORLD"
|
||||
operationName "NEW_TRACE"
|
||||
parent()
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "trace"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
resourceName "EARTH"
|
||||
operationName "SAY_HA"
|
||||
spanType "DB"
|
||||
childOf span(0)
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "trace"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
serviceName "test"
|
||||
resourceName "SayTracedHello.sayHello"
|
||||
operationName "SayTracedHello.sayHello"
|
||||
childOf span(0)
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "trace"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test exception exit"() {
|
||||
setup:
|
||||
|
||||
|
|
@ -111,6 +270,35 @@ class TraceAnnotationsTest extends AgentTestRunner {
|
|||
}
|
||||
}
|
||||
|
||||
def "test exception exit with resource name"() {
|
||||
setup:
|
||||
|
||||
TEST_TRACER.addDecorator(new ErrorFlag())
|
||||
|
||||
Throwable error = null
|
||||
try {
|
||||
SayTracedHello.sayERRORWithResource()
|
||||
} catch (final Throwable ex) {
|
||||
error = ex
|
||||
}
|
||||
|
||||
expect:
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
resourceName "WORLD"
|
||||
operationName "ERROR"
|
||||
errored true
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "trace"
|
||||
errorTags(error.class)
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test annonymous class annotations"() {
|
||||
setup:
|
||||
// Test anonymous classes with package.
|
||||
|
|
|
|||
|
|
@ -15,6 +15,13 @@ public class SayTracedHello {
|
|||
return "hello!";
|
||||
}
|
||||
|
||||
@Trace(resourceName = "WORLD")
|
||||
public static String sayHelloOnlyResourceSet() {
|
||||
new StringTag(DDTags.SERVICE_NAME)
|
||||
.set(GlobalTracer.get().scopeManager().active().span(), "test");
|
||||
return "hello!";
|
||||
}
|
||||
|
||||
@Trace(operationName = "SAY_HA")
|
||||
public static String sayHA() {
|
||||
new StringTag(DDTags.SERVICE_NAME)
|
||||
|
|
@ -23,6 +30,14 @@ public class SayTracedHello {
|
|||
return "HA!!";
|
||||
}
|
||||
|
||||
@Trace(operationName = "SAY_HA", resourceName = "EARTH")
|
||||
public static String sayHAWithResource() {
|
||||
new StringTag(DDTags.SERVICE_NAME)
|
||||
.set(GlobalTracer.get().scopeManager().active().span(), "test");
|
||||
new StringTag(DDTags.SPAN_TYPE).set(GlobalTracer.get().scopeManager().active().span(), "DB");
|
||||
return "HA EARTH!!";
|
||||
}
|
||||
|
||||
@Trace(operationName = "NEW_TRACE")
|
||||
public static String sayHELLOsayHA() {
|
||||
new StringTag(DDTags.SERVICE_NAME)
|
||||
|
|
@ -30,11 +45,30 @@ public class SayTracedHello {
|
|||
return sayHello() + sayHA();
|
||||
}
|
||||
|
||||
@Trace(operationName = "NEW_TRACE", resourceName = "WORLD")
|
||||
public static String sayHELLOsayHAWithResource() {
|
||||
new StringTag(DDTags.SERVICE_NAME)
|
||||
.set(GlobalTracer.get().scopeManager().active().span(), "test2");
|
||||
return sayHello() + sayHA();
|
||||
}
|
||||
|
||||
@Trace(operationName = "NEW_TRACE", resourceName = "WORLD")
|
||||
public static String sayHELLOsayHAMixedResourceChildren() {
|
||||
new StringTag(DDTags.SERVICE_NAME)
|
||||
.set(GlobalTracer.get().scopeManager().active().span(), "test2");
|
||||
return sayHello() + sayHAWithResource();
|
||||
}
|
||||
|
||||
@Trace(operationName = "ERROR")
|
||||
public static String sayERROR() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@Trace(operationName = "ERROR", resourceName = "WORLD")
|
||||
public static String sayERRORWithResource() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public static String fromCallable() throws Exception {
|
||||
return new Callable<String>() {
|
||||
@com.newrelic.api.agent.Trace
|
||||
|
|
|
|||
|
|
@ -1,151 +1,54 @@
|
|||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import datadog.trace.agent.test.utils.PortUtils
|
||||
import datadog.trace.api.DDSpanTypes
|
||||
import io.netty.channel.AbstractChannel
|
||||
import io.opentracing.tag.Tags
|
||||
import datadog.trace.agent.test.base.HttpClientTest
|
||||
import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator
|
||||
import io.vertx.core.Vertx
|
||||
import io.vertx.core.VertxOptions
|
||||
import io.vertx.core.http.HttpClient
|
||||
import io.vertx.core.http.HttpClientRequest
|
||||
import io.vertx.core.http.HttpClientResponse
|
||||
import io.vertx.core.http.HttpMethod
|
||||
import spock.lang.AutoCleanup
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Timeout
|
||||
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer
|
||||
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
|
||||
|
||||
class VertxHttpClientTest extends AgentTestRunner {
|
||||
|
||||
private static final String MESSAGE = "hello world"
|
||||
|
||||
@AutoCleanup
|
||||
@Shared
|
||||
def server = httpServer {
|
||||
handlers {
|
||||
prefix("success") {
|
||||
handleDistributedRequest()
|
||||
|
||||
response.status(200).send(MESSAGE)
|
||||
}
|
||||
|
||||
prefix("error") {
|
||||
handleDistributedRequest()
|
||||
|
||||
throw new RuntimeException("error")
|
||||
}
|
||||
}
|
||||
}
|
||||
@Timeout(10)
|
||||
class VertxHttpClientTest extends HttpClientTest<NettyHttpClientDecorator> {
|
||||
|
||||
@Shared
|
||||
Vertx vertx = Vertx.vertx(new VertxOptions())
|
||||
@Shared
|
||||
HttpClient httpClient = vertx.createHttpClient()
|
||||
|
||||
def "#route request trace"() {
|
||||
setup:
|
||||
def responseFuture = new CompletableFuture<HttpClientResponse>()
|
||||
def messageFuture = new CompletableFuture<String>()
|
||||
httpClient.getNow(server.address.port, server.address.host, "/" + route, { response ->
|
||||
responseFuture.complete(response)
|
||||
response.bodyHandler({ buffer ->
|
||||
messageFuture.complete(buffer.toString())
|
||||
})
|
||||
})
|
||||
|
||||
when:
|
||||
HttpClientResponse response = responseFuture.get()
|
||||
String message = messageFuture.get()
|
||||
|
||||
then:
|
||||
response.statusCode() == expectedStatus
|
||||
if (expectedMessage != null) {
|
||||
message == expectedMessage
|
||||
@Override
|
||||
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
|
||||
CompletableFuture<HttpClientResponse> future = new CompletableFuture<>()
|
||||
def request = httpClient.request(HttpMethod.valueOf(method), uri.port, uri.host, "$uri")
|
||||
headers.each { request.putHeader(it.key, it.value) }
|
||||
request.handler { response ->
|
||||
callback?.call()
|
||||
future.complete(response)
|
||||
}
|
||||
request.end()
|
||||
|
||||
assertTraces(2) {
|
||||
server.distributedRequestTrace(it, 0, TEST_WRITER[1][0])
|
||||
trace(1, 1) {
|
||||
span(0) {
|
||||
parent()
|
||||
serviceName "unnamed-java-app"
|
||||
operationName "netty.client.request"
|
||||
resourceName "GET /$route"
|
||||
spanType DDSpanTypes.HTTP_CLIENT
|
||||
errored expectedError
|
||||
tags {
|
||||
defaultTags()
|
||||
"$Tags.HTTP_STATUS.key" expectedStatus
|
||||
"$Tags.HTTP_URL.key" "${server.address}/$route"
|
||||
"$Tags.PEER_HOSTNAME.key" server.address.host
|
||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
||||
"$Tags.PEER_PORT.key" server.address.port
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.COMPONENT.key" "netty-client"
|
||||
if (expectedError) {
|
||||
"$Tags.ERROR.key" true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
where:
|
||||
route | expectedStatus | expectedError | expectedMessage
|
||||
"success" | 200 | false | MESSAGE
|
||||
"error" | 500 | true | null
|
||||
return future.get().statusCode()
|
||||
}
|
||||
|
||||
def "test connection failure"() {
|
||||
setup:
|
||||
def invalidPort = PortUtils.randomOpenPort()
|
||||
@Override
|
||||
NettyHttpClientDecorator decorator() {
|
||||
return NettyHttpClientDecorator.DECORATE
|
||||
}
|
||||
|
||||
def errorFuture = new CompletableFuture<Throwable>()
|
||||
@Override
|
||||
String expectedOperationName() {
|
||||
return "netty.client.request"
|
||||
}
|
||||
|
||||
runUnderTrace("parent") {
|
||||
HttpClientRequest request = httpClient.request(
|
||||
HttpMethod.GET,
|
||||
invalidPort,
|
||||
server.address.host,
|
||||
"/",
|
||||
{ response ->
|
||||
// We expect to never get here since our request is expected to fail
|
||||
errorFuture.complete(null)
|
||||
})
|
||||
request.exceptionHandler({ error ->
|
||||
errorFuture.complete(error)
|
||||
})
|
||||
request.end()
|
||||
}
|
||||
@Override
|
||||
boolean testRedirects() {
|
||||
false
|
||||
}
|
||||
|
||||
when:
|
||||
def throwable = errorFuture.get()
|
||||
|
||||
then:
|
||||
throwable.cause instanceof ConnectException
|
||||
|
||||
and:
|
||||
assertTraces(1) {
|
||||
trace(0, 2) {
|
||||
span(0) {
|
||||
operationName "parent"
|
||||
parent()
|
||||
}
|
||||
span(1) {
|
||||
operationName "netty.connect"
|
||||
resourceName "netty.connect"
|
||||
childOf span(0)
|
||||
errored true
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "netty"
|
||||
errorTags AbstractChannel.AnnotatedConnectException, "Connection refused: localhost/127.0.0.1:$invalidPort"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override
|
||||
boolean testConnectionFailure() {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,157 @@
|
|||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import datadog.trace.agent.test.utils.OkHttpUtils
|
||||
import datadog.trace.agent.test.utils.PortUtils
|
||||
import datadog.trace.api.DDSpanTypes
|
||||
import io.netty.handler.codec.http.HttpResponseStatus
|
||||
import io.opentracing.tag.Tags
|
||||
import io.vertx.core.Vertx
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import spock.lang.Shared
|
||||
|
||||
class VertxRxServerTest extends AgentTestRunner {
|
||||
|
||||
@Shared
|
||||
OkHttpClient client = OkHttpUtils.client()
|
||||
|
||||
@Shared
|
||||
int port
|
||||
@Shared
|
||||
Vertx server
|
||||
|
||||
def setupSpec() {
|
||||
port = PortUtils.randomOpenPort()
|
||||
server = VertxRxWebTestServer.start(port)
|
||||
}
|
||||
|
||||
def cleanupSpec() {
|
||||
server.close()
|
||||
}
|
||||
|
||||
def "test server request/response"() {
|
||||
setup:
|
||||
def request = new Request.Builder()
|
||||
.url("http://localhost:$port/proxy")
|
||||
.header("x-datadog-trace-id", "123")
|
||||
.header("x-datadog-parent-id", "456")
|
||||
.get()
|
||||
.build()
|
||||
def response = client.newCall(request).execute()
|
||||
|
||||
expect:
|
||||
response.code() == 200
|
||||
response.body().string() == "Hello World"
|
||||
|
||||
and:
|
||||
assertTraces(2) {
|
||||
trace(0, 2) {
|
||||
span(0) {
|
||||
serviceName "unnamed-java-app"
|
||||
operationName "netty.request"
|
||||
resourceName "GET /test"
|
||||
childOf(trace(1).get(1))
|
||||
spanType DDSpanTypes.HTTP_SERVER
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "netty"
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 200
|
||||
"$Tags.HTTP_URL.key" "http://localhost:$port/test"
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
||||
"$Tags.PEER_PORT.key" Integer
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||
defaultTags(true)
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
childOf span(0)
|
||||
assert span(1).operationName.endsWith('.tracedMethod')
|
||||
}
|
||||
}
|
||||
trace(1, 2) {
|
||||
span(0) {
|
||||
serviceName "unnamed-java-app"
|
||||
operationName "netty.request"
|
||||
resourceName "GET /proxy"
|
||||
traceId "123"
|
||||
parentId "456"
|
||||
spanType DDSpanTypes.HTTP_SERVER
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "netty"
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 200
|
||||
"$Tags.HTTP_URL.key" "http://localhost:$port/proxy"
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
||||
"$Tags.PEER_PORT.key" Integer
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||
defaultTags(true)
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
serviceName "unnamed-java-app"
|
||||
operationName "netty.client.request"
|
||||
resourceName "GET /test"
|
||||
childOf(span(0))
|
||||
spanType DDSpanTypes.HTTP_CLIENT
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "netty-client"
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 200
|
||||
"$Tags.HTTP_URL.key" "http://localhost:$port/test"
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
||||
"$Tags.PEER_PORT.key" Integer
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test #responseCode response handling"() {
|
||||
setup:
|
||||
def request = new Request.Builder().url("http://localhost:$port/$path").get().build()
|
||||
def response = client.newCall(request).execute()
|
||||
|
||||
expect:
|
||||
response.code() == responseCode.code()
|
||||
|
||||
and:
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "unnamed-java-app"
|
||||
operationName "netty.request"
|
||||
resourceName name
|
||||
spanType DDSpanTypes.HTTP_SERVER
|
||||
errored error
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "netty"
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" responseCode.code()
|
||||
"$Tags.HTTP_URL.key" "http://localhost:$port/$path"
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
||||
"$Tags.PEER_PORT.key" Integer
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||
if (error) {
|
||||
tag("error", true)
|
||||
}
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
where:
|
||||
responseCode | name | path | error
|
||||
HttpResponseStatus.OK | "GET /" | "" | false
|
||||
HttpResponseStatus.NOT_FOUND | "404" | "doesnt-exit" | false
|
||||
HttpResponseStatus.INTERNAL_SERVER_ERROR | "GET /error" | "error" | true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import datadog.trace.agent.test.base.HttpClientTest
|
||||
import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator
|
||||
import io.vertx.core.VertxOptions
|
||||
import io.vertx.core.http.HttpMethod
|
||||
import io.vertx.reactivex.core.Vertx
|
||||
import io.vertx.reactivex.ext.web.client.WebClient
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Timeout
|
||||
|
||||
@Timeout(10)
|
||||
class VertxRxWebClientTest extends HttpClientTest<NettyHttpClientDecorator> {
|
||||
|
||||
@Shared
|
||||
Vertx vertx = Vertx.vertx(new VertxOptions())
|
||||
@Shared
|
||||
WebClient client = WebClient.create(vertx)
|
||||
|
||||
@Override
|
||||
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
|
||||
def request = client.request(HttpMethod.valueOf(method), uri.port, uri.host, "$uri")
|
||||
headers.each { request.putHeader(it.key, it.value) }
|
||||
return request
|
||||
.rxSend()
|
||||
.doOnSuccess { response -> callback?.call() }
|
||||
.map { it.statusCode() }
|
||||
.toObservable()
|
||||
.blockingFirst()
|
||||
}
|
||||
|
||||
@Override
|
||||
NettyHttpClientDecorator decorator() {
|
||||
return NettyHttpClientDecorator.DECORATE
|
||||
}
|
||||
|
||||
@Override
|
||||
String expectedOperationName() {
|
||||
return "netty.client.request"
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean testRedirects() {
|
||||
false
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean testConnectionFailure() {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ class VertxServerTest extends AgentTestRunner {
|
|||
def "test server request/response"() {
|
||||
setup:
|
||||
def request = new Request.Builder()
|
||||
.url("http://localhost:$port/test")
|
||||
.url("http://localhost:$port/proxy")
|
||||
.header("x-datadog-trace-id", "123")
|
||||
.header("x-datadog-parent-id", "456")
|
||||
.get()
|
||||
|
|
@ -43,14 +43,13 @@ class VertxServerTest extends AgentTestRunner {
|
|||
response.body().string() == "Hello World"
|
||||
|
||||
and:
|
||||
assertTraces(1) {
|
||||
assertTraces(2) {
|
||||
trace(0, 2) {
|
||||
span(0) {
|
||||
traceId "123"
|
||||
parentId "456"
|
||||
serviceName "unnamed-java-app"
|
||||
operationName "netty.request"
|
||||
resourceName "GET /test"
|
||||
childOf(trace(1).get(1))
|
||||
spanType DDSpanTypes.HTTP_SERVER
|
||||
errored false
|
||||
tags {
|
||||
|
|
@ -70,6 +69,47 @@ class VertxServerTest extends AgentTestRunner {
|
|||
assert span(1).operationName.endsWith('.tracedMethod')
|
||||
}
|
||||
}
|
||||
trace(1, 2) {
|
||||
span(0) {
|
||||
serviceName "unnamed-java-app"
|
||||
operationName "netty.request"
|
||||
resourceName "GET /proxy"
|
||||
traceId "123"
|
||||
parentId "456"
|
||||
spanType DDSpanTypes.HTTP_SERVER
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "netty"
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 200
|
||||
"$Tags.HTTP_URL.key" "http://localhost:$port/proxy"
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
||||
"$Tags.PEER_PORT.key" Integer
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||
defaultTags(true)
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
serviceName "unnamed-java-app"
|
||||
operationName "netty.client.request"
|
||||
resourceName "GET /test"
|
||||
childOf(span(0))
|
||||
spanType DDSpanTypes.HTTP_CLIENT
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "netty-client"
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 200
|
||||
"$Tags.HTTP_URL.key" "http://localhost:$port/test"
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
||||
"$Tags.PEER_PORT.key" Integer
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
import datadog.trace.api.Trace;
|
||||
import io.vertx.circuitbreaker.CircuitBreakerOptions;
|
||||
import io.vertx.core.DeploymentOptions;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Vertx;
|
||||
import io.vertx.core.VertxOptions;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.reactivex.circuitbreaker.CircuitBreaker;
|
||||
import io.vertx.reactivex.core.AbstractVerticle;
|
||||
import io.vertx.reactivex.core.buffer.Buffer;
|
||||
import io.vertx.reactivex.ext.web.Router;
|
||||
import io.vertx.reactivex.ext.web.RoutingContext;
|
||||
import io.vertx.reactivex.ext.web.client.WebClient;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class VertxRxWebTestServer extends AbstractVerticle {
|
||||
public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port";
|
||||
|
||||
public static Vertx start(final int port) throws ExecutionException, InterruptedException {
|
||||
/* This is highly against Vertx ideas, but our tests are synchronous
|
||||
so we have to make sure server is up and running */
|
||||
final CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
|
||||
final Vertx vertx = Vertx.vertx(new VertxOptions().setClusterPort(port));
|
||||
|
||||
vertx.deployVerticle(
|
||||
VertxRxWebTestServer.class.getName(),
|
||||
new DeploymentOptions()
|
||||
.setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port))
|
||||
.setInstances(3),
|
||||
res -> {
|
||||
if (!res.succeeded()) {
|
||||
throw new RuntimeException("Cannot deploy server Verticle", res.cause());
|
||||
}
|
||||
future.complete(null);
|
||||
});
|
||||
|
||||
future.get();
|
||||
|
||||
return vertx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(final Future<Void> startFuture) {
|
||||
// final io.vertx.reactivex.core.Vertx vertx = new io.vertx.reactivex.core.Vertx(this.vertx);
|
||||
final WebClient client = WebClient.create(vertx);
|
||||
|
||||
final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT);
|
||||
|
||||
final Router router = Router.router(vertx);
|
||||
final CircuitBreaker breaker =
|
||||
CircuitBreaker.create(
|
||||
"my-circuit-breaker",
|
||||
vertx,
|
||||
new CircuitBreakerOptions()
|
||||
.setMaxFailures(5) // number of failure before opening the circuit
|
||||
.setTimeout(2000) // consider a failure if the operation does not succeed in time
|
||||
// .setFallbackOnFailure(true) // do we call the fallback on failure
|
||||
.setResetTimeout(10000) // time spent in open state before attempting to re-try
|
||||
);
|
||||
|
||||
router
|
||||
.route("/")
|
||||
.handler(
|
||||
routingContext -> {
|
||||
routingContext.response().putHeader("content-type", "text/html").end("Hello World");
|
||||
});
|
||||
router
|
||||
.route("/error")
|
||||
.handler(
|
||||
routingContext -> {
|
||||
routingContext.response().setStatusCode(500).end();
|
||||
});
|
||||
router
|
||||
.route("/proxy")
|
||||
.handler(
|
||||
routingContext -> {
|
||||
breaker.execute(
|
||||
ctx -> {
|
||||
client
|
||||
.get(port, "localhost", "/test")
|
||||
.rxSendBuffer(
|
||||
Optional.ofNullable(routingContext.getBody()).orElse(Buffer.buffer()))
|
||||
.subscribe(
|
||||
response -> {
|
||||
routingContext
|
||||
.response()
|
||||
.setStatusCode(response.statusCode())
|
||||
.end(response.body());
|
||||
});
|
||||
});
|
||||
});
|
||||
router
|
||||
.route("/test")
|
||||
.handler(
|
||||
routingContext -> {
|
||||
tracedMethod();
|
||||
routingContext.next();
|
||||
})
|
||||
.blockingHandler(RoutingContext::next)
|
||||
.handler(
|
||||
routingContext -> {
|
||||
routingContext.response().putHeader("content-type", "text/html").end("Hello World");
|
||||
});
|
||||
|
||||
vertx
|
||||
.createHttpServer()
|
||||
.requestHandler(router::accept)
|
||||
.listen(port, h -> startFuture.complete());
|
||||
}
|
||||
|
||||
@Trace
|
||||
private void tracedMethod() {}
|
||||
}
|
||||
|
|
@ -1,25 +1,36 @@
|
|||
import datadog.trace.api.Trace;
|
||||
import io.vertx.core.AbstractVerticle;
|
||||
import io.vertx.core.DeploymentOptions;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Vertx;
|
||||
import io.vertx.core.VertxOptions;
|
||||
import io.vertx.core.buffer.Buffer;
|
||||
import io.vertx.core.http.HttpClient;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.Router;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class VertxWebTestServer extends AbstractVerticle {
|
||||
public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port";
|
||||
|
||||
public static Vertx start(final int port) throws ExecutionException, InterruptedException {
|
||||
/* This is highly against Vertx ideas, but our tests are synchronous
|
||||
so we have to make sure server is up and running */
|
||||
final CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
|
||||
final Vertx vertx = Vertx.vertx(new VertxOptions());
|
||||
final Vertx vertx = Vertx.vertx(new VertxOptions().setClusterPort(port));
|
||||
|
||||
vertx.deployVerticle(
|
||||
new VertxWebTestServer(port),
|
||||
VertxWebTestServer.class.getName(),
|
||||
new DeploymentOptions()
|
||||
.setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port))
|
||||
.setInstances(3),
|
||||
res -> {
|
||||
if (!res.succeeded()) {
|
||||
throw new RuntimeException("Cannot deploy server Verticle");
|
||||
throw new RuntimeException("Cannot deploy server Verticle", res.cause());
|
||||
}
|
||||
future.complete(null);
|
||||
});
|
||||
|
|
@ -29,14 +40,12 @@ public class VertxWebTestServer extends AbstractVerticle {
|
|||
return vertx;
|
||||
}
|
||||
|
||||
private final int port;
|
||||
|
||||
public VertxWebTestServer(final int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(final Future<Void> startFuture) {
|
||||
final HttpClient client = vertx.createHttpClient();
|
||||
|
||||
final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT);
|
||||
|
||||
final Router router = Router.router(vertx);
|
||||
|
||||
router
|
||||
|
|
@ -51,6 +60,26 @@ public class VertxWebTestServer extends AbstractVerticle {
|
|||
routingContext -> {
|
||||
routingContext.response().setStatusCode(500).end();
|
||||
});
|
||||
router
|
||||
.route("/proxy")
|
||||
.handler(
|
||||
routingContext -> {
|
||||
client
|
||||
.get(
|
||||
port,
|
||||
"localhost",
|
||||
"/test",
|
||||
response -> {
|
||||
response.bodyHandler(
|
||||
buffer -> {
|
||||
routingContext
|
||||
.response()
|
||||
.setStatusCode(response.statusCode())
|
||||
.end(buffer);
|
||||
});
|
||||
})
|
||||
.end(Optional.ofNullable(routingContext.getBody()).orElse(Buffer.buffer()));
|
||||
});
|
||||
router
|
||||
.route("/test")
|
||||
.handler(
|
||||
|
|
@ -58,10 +87,7 @@ public class VertxWebTestServer extends AbstractVerticle {
|
|||
tracedMethod();
|
||||
routingContext.next();
|
||||
})
|
||||
.blockingHandler(
|
||||
routingContext -> {
|
||||
routingContext.next();
|
||||
})
|
||||
.blockingHandler(RoutingContext::next)
|
||||
.handler(
|
||||
routingContext -> {
|
||||
routingContext.response().putHeader("content-type", "text/html").end("Hello World");
|
||||
|
|
@ -74,5 +100,5 @@ public class VertxWebTestServer extends AbstractVerticle {
|
|||
}
|
||||
|
||||
@Trace
|
||||
public void tracedMethod() {}
|
||||
private void tracedMethod() {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,10 +42,19 @@ dependencies {
|
|||
implementation deps.autoservice
|
||||
|
||||
testCompile project(':dd-java-agent:testing')
|
||||
testCompile project(':dd-java-agent:instrumentation:netty-4.1')
|
||||
testCompile project(':dd-java-agent:instrumentation:java-concurrent')
|
||||
testCompile project(':dd-java-agent:instrumentation:trace-annotation')
|
||||
testCompile project(':dd-java-agent:instrumentation:netty-4.1')
|
||||
testCompile group: 'io.vertx', name: 'vertx-web', version: '3.5.0'
|
||||
|
||||
latestDepTestCompile group: 'io.vertx', name: 'vertx-web', version: '+'
|
||||
// Tests seem to fail before 3.5... maybe a problem with some of the tests?
|
||||
testCompile group: 'io.vertx', name: 'vertx-web', version: '3.5.0'
|
||||
testCompile group: 'io.vertx', name: 'vertx-web-client', version: '3.5.0'
|
||||
testCompile group: 'io.vertx', name: 'vertx-circuit-breaker', version: '3.5.0'
|
||||
testCompile group: 'io.vertx', name: 'vertx-rx-java2', version: '3.5.0'
|
||||
|
||||
// Vert.x 4.0 is incompatible with our tests.
|
||||
latestDepTestCompile group: 'io.vertx', name: 'vertx-web', version: '3.+'
|
||||
latestDepTestCompile group: 'io.vertx', name: 'vertx-web-client', version: '3.+'
|
||||
latestDepTestCompile group: 'io.vertx', name: 'vertx-circuit-breaker', version: '3.+'
|
||||
latestDepTestCompile group: 'io.vertx', name: 'vertx-rx-java2', version: '3.+'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import groovy.lang.Closure;
|
|||
import groovy.lang.DelegatesTo;
|
||||
import groovy.transform.stc.ClosureParams;
|
||||
import groovy.transform.stc.SimpleType;
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.Tracer;
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
|
|
@ -202,15 +203,13 @@ public abstract class AgentTestRunner extends Specification {
|
|||
}
|
||||
|
||||
public void blockUntilChildSpansFinished(final int numberOfSpans) throws InterruptedException {
|
||||
final DDSpan span = (DDSpan) io.opentracing.util.GlobalTracer.get().activeSpan();
|
||||
if (span == null) {
|
||||
// If there is no active span avoid getting an NPE
|
||||
return;
|
||||
}
|
||||
final PendingTrace pendingTrace = span.context().getTrace();
|
||||
final Span span = io.opentracing.util.GlobalTracer.get().activeSpan();
|
||||
if (span instanceof DDSpan) {
|
||||
final PendingTrace pendingTrace = ((DDSpan) span).context().getTrace();
|
||||
|
||||
while (pendingTrace.size() < numberOfSpans) {
|
||||
Thread.sleep(10);
|
||||
while (pendingTrace.size() < numberOfSpans) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -176,10 +176,7 @@ abstract class HttpClientTest<T extends HttpClientDecorator> extends AgentTestRu
|
|||
assertTraces(1) {
|
||||
trace(0, size(3)) {
|
||||
basicSpan(it, 0, "parent")
|
||||
span(1) {
|
||||
operationName "child"
|
||||
childOf span(0)
|
||||
}
|
||||
basicSpan(it, 1, "child", span(0))
|
||||
clientSpan(it, 2, span(0), method, false)
|
||||
}
|
||||
}
|
||||
|
|
@ -191,7 +188,7 @@ abstract class HttpClientTest<T extends HttpClientDecorator> extends AgentTestRu
|
|||
def "trace request with callback and no parent"() {
|
||||
when:
|
||||
def status = doRequest(method, server.address.resolve("/success"), ["is-dd-server": "false"]) {
|
||||
runUnderTrace("child") {
|
||||
runUnderTrace("callback") {
|
||||
// Ensure consistent ordering of traces for assertion.
|
||||
TEST_WRITER.waitForTraces(1)
|
||||
}
|
||||
|
|
@ -205,10 +202,7 @@ abstract class HttpClientTest<T extends HttpClientDecorator> extends AgentTestRu
|
|||
clientSpan(it, 0, null, method, false)
|
||||
}
|
||||
trace(1, 1) {
|
||||
span(0) {
|
||||
operationName "child"
|
||||
parent()
|
||||
}
|
||||
basicSpan(it, 0, "callback")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -306,7 +300,7 @@ abstract class HttpClientTest<T extends HttpClientDecorator> extends AgentTestRu
|
|||
and:
|
||||
assertTraces(1) {
|
||||
trace(0, 2) {
|
||||
basicSpan(it, 0, "parent", thrownException)
|
||||
basicSpan(it, 0, "parent", null, thrownException)
|
||||
clientSpan(it, 1, span(0), method, false, false, uri, null, thrownException)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package datadog.trace.agent.test.utils
|
||||
|
||||
import datadog.opentracing.DDSpan
|
||||
import datadog.trace.agent.decorator.BaseDecorator
|
||||
import datadog.trace.agent.test.asserts.TraceAssert
|
||||
import datadog.trace.context.TraceScope
|
||||
|
|
@ -42,9 +43,13 @@ class TraceUtils {
|
|||
}
|
||||
}
|
||||
|
||||
static basicSpan(TraceAssert trace, int index, String spanName, Throwable exception = null) {
|
||||
static basicSpan(TraceAssert trace, int index, String spanName, Object parentSpan = null, Throwable exception = null) {
|
||||
trace.span(index) {
|
||||
parent()
|
||||
if (parentSpan == null) {
|
||||
parent()
|
||||
} else {
|
||||
childOf((DDSpan) parentSpan)
|
||||
}
|
||||
serviceName "unnamed-java-app"
|
||||
operationName spanName
|
||||
resourceName spanName
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# Datadog Smoke Tests
|
||||
Assert that various application servers will start up with the Datadog JavaAgent without any obvious ill effects.
|
||||
Assert that various applications will start up with the Datadog JavaAgent without any obvious ill effects.
|
||||
|
||||
Each subproject underneath `dd-smoke-tests` is a single smoke test. Each test does the following
|
||||
* Launch the app server with stdout and stderr logged to `$buildDir/reports/server.log`
|
||||
* Run a spock test which does 200 requests to an endpoint on the server and asserts on an expected response.
|
||||
* Launch the application with stdout and stderr logged to `$buildDir/reports/server.log`
|
||||
* For web servers, run a spock test which does 200 requests to an endpoint on the server and asserts on an expected response.
|
||||
|
||||
Note that there is nothing special about doing 200 requests. 200 is simply an arbitrarily large number to exercise the server.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
plugins {
|
||||
id "com.github.johnrengelman.shadow" version "4.0.4"
|
||||
}
|
||||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
description = 'Command Line Application Smoke Tests.'
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes(
|
||||
'Main-Class': 'datadog.smoketest.cli.CliApplication'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':dd-trace-api')
|
||||
|
||||
testCompile project(':dd-smoke-tests')
|
||||
}
|
||||
|
||||
tasks.withType(Test).configureEach {
|
||||
dependsOn shadowJar
|
||||
|
||||
jvmArgs "-Ddatadog.smoketest.cli.shadowJar.path=${tasks.shadowJar.archivePath}"
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package datadog.smoketest.cli;
|
||||
|
||||
import datadog.trace.api.Trace;
|
||||
|
||||
/** Simple application that sleeps then quits. */
|
||||
public class CliApplication {
|
||||
|
||||
public static void main(final String[] args) throws InterruptedException {
|
||||
final CliApplication app = new CliApplication();
|
||||
|
||||
// Sleep to ensure all of the processes are running
|
||||
Thread.sleep(5000);
|
||||
|
||||
System.out.println("Calling example trace");
|
||||
|
||||
app.exampleTrace();
|
||||
|
||||
System.out.println("Finished calling example trace");
|
||||
}
|
||||
|
||||
@Trace(operationName = "example")
|
||||
public void exampleTrace() throws InterruptedException {
|
||||
Thread.sleep(500);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package datadog.smoketest
|
||||
|
||||
import spock.lang.Timeout
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class CliApplicationSmokeTest extends AbstractSmokeTest {
|
||||
// Estimate for the amount of time instrumentation, plus request, plus some extra
|
||||
private static final int TIMEOUT_SECS = 15
|
||||
|
||||
@Override
|
||||
ProcessBuilder createProcessBuilder() {
|
||||
String cliShadowJar = System.getProperty("datadog.smoketest.cli.shadowJar.path")
|
||||
|
||||
List<String> command = new ArrayList<>()
|
||||
command.add(javaPath())
|
||||
command.addAll(defaultJavaProperties)
|
||||
command.addAll((String[]) ["-jar", cliShadowJar])
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(command)
|
||||
processBuilder.directory(new File(buildDirectory))
|
||||
}
|
||||
|
||||
@Timeout(value = TIMEOUT_SECS, unit = TimeUnit.SECONDS)
|
||||
def "Cli application process ends before timeout"() {
|
||||
expect:
|
||||
assert serverProcess.waitFor() == 0
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
package datadog.trace.api;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -885,7 +887,7 @@ public class Config {
|
|||
* exist or if it is in a wrong format.
|
||||
*/
|
||||
private static Properties loadConfigurationFile() {
|
||||
Properties properties = new Properties();
|
||||
final Properties properties = new Properties();
|
||||
|
||||
// Reading from system property first and from env after
|
||||
String configurationFilePath =
|
||||
|
|
@ -922,11 +924,38 @@ public class Config {
|
|||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the detected hostname. This operation is time consuming so if the usage changes and
|
||||
* this method will be called several times then we should implement some sort of caching.
|
||||
*/
|
||||
/** Returns the detected hostname. First tries locally, then using DNS */
|
||||
private String getHostName() {
|
||||
String possibleHostname = null;
|
||||
|
||||
// Try environment variable. This works in almost all environments
|
||||
if (System.getProperty("os.name").startsWith("Windows")) {
|
||||
possibleHostname = System.getenv("COMPUTERNAME");
|
||||
} else {
|
||||
possibleHostname = System.getenv("HOSTNAME");
|
||||
}
|
||||
|
||||
if (possibleHostname != null && !possibleHostname.isEmpty()) {
|
||||
log.debug("Determined hostname from environment variable");
|
||||
return possibleHostname.trim();
|
||||
}
|
||||
|
||||
// Try hostname command
|
||||
try {
|
||||
final Process process = Runtime.getRuntime().exec("hostname");
|
||||
final BufferedReader reader =
|
||||
new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
possibleHostname = reader.readLine();
|
||||
} catch (final Exception e) {
|
||||
// Ignore. Hostname command is not always available
|
||||
}
|
||||
|
||||
if (possibleHostname != null && !possibleHostname.isEmpty()) {
|
||||
log.debug("Determined hostname from hostname command");
|
||||
return possibleHostname.trim();
|
||||
}
|
||||
|
||||
// From DNS
|
||||
try {
|
||||
return InetAddress.getLocalHost().getHostName();
|
||||
} catch (final UnknownHostException e) {
|
||||
|
|
|
|||
|
|
@ -13,4 +13,7 @@ public @interface Trace {
|
|||
|
||||
/** The operation name to set. By default it takes the method's name */
|
||||
String operationName() default "";
|
||||
|
||||
/** The resource name. By default it uses the same value as the operation name */
|
||||
String resourceName() default "";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -724,7 +724,7 @@ class ConfigTest extends Specification {
|
|||
def config = Config.get(properties)
|
||||
|
||||
then:
|
||||
config.localRootSpanTags.get('_dd.hostname') == InetAddress.localHost.hostName
|
||||
config.localRootSpanTags.containsKey('_dd.hostname')
|
||||
}
|
||||
|
||||
def "verify fallback to properties file"() {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ def isCI = System.getenv("CI") != null
|
|||
|
||||
allprojects {
|
||||
group = 'com.datadoghq'
|
||||
version = '0.31.0-SNAPSHOT'
|
||||
version = '0.32.0-SNAPSHOT'
|
||||
|
||||
if (isCI) {
|
||||
buildDir = "${rootDir}/workspace/${projectDir.path.replace(rootDir.path, '')}/build/"
|
||||
|
|
@ -52,7 +52,6 @@ buildScan {
|
|||
|
||||
wrapper {
|
||||
distributionType = Wrapper.DistributionType.ALL
|
||||
version = '5.5.1'
|
||||
}
|
||||
|
||||
allprojects {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,128 @@
|
|||
package datadog.opentracing;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Parses container information from /proc/self/cgroup. Implementation based largely on
|
||||
* Qard/container-info
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Slf4j
|
||||
public class ContainerInfo {
|
||||
private static final Path CGROUP_DEFAULT_PROCFILE = Paths.get("/proc/self/cgroup");
|
||||
private static final String UUID_REGEX =
|
||||
"[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}";
|
||||
private static final String CONTAINER_REGEX = "[0-9a-f]{64}";
|
||||
private static final Pattern LINE_PATTERN = Pattern.compile("(\\d+):([^:]*):(.+)$");
|
||||
private static final Pattern POD_PATTERN =
|
||||
Pattern.compile("(?:.+)?pod(" + UUID_REGEX + ")(?:.slice)?$");
|
||||
private static final Pattern CONTAINER_PATTERN =
|
||||
Pattern.compile("(?:.+)?(" + UUID_REGEX + "|" + CONTAINER_REGEX + ")(?:.scope)?$");
|
||||
|
||||
private static final ContainerInfo INSTANCE;
|
||||
|
||||
public String containerId;
|
||||
public String podId;
|
||||
public List<CGroupInfo> cGroups = new ArrayList<>();
|
||||
|
||||
static {
|
||||
ContainerInfo containerInfo = new ContainerInfo();
|
||||
if (ContainerInfo.isRunningInContainer()) {
|
||||
try {
|
||||
containerInfo = ContainerInfo.fromDefaultProcFile();
|
||||
} catch (final IOException | ParseException e) {
|
||||
log.error("Unable to parse proc file");
|
||||
}
|
||||
}
|
||||
|
||||
INSTANCE = containerInfo;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class CGroupInfo {
|
||||
public int id;
|
||||
public String path;
|
||||
public List<String> controllers;
|
||||
public String containerId;
|
||||
public String podId;
|
||||
}
|
||||
|
||||
public static ContainerInfo get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public static boolean isRunningInContainer() {
|
||||
return Files.isReadable(CGROUP_DEFAULT_PROCFILE);
|
||||
}
|
||||
|
||||
public static ContainerInfo fromDefaultProcFile() throws IOException, ParseException {
|
||||
final String content = new String(Files.readAllBytes(CGROUP_DEFAULT_PROCFILE));
|
||||
return parse(content);
|
||||
}
|
||||
|
||||
public static ContainerInfo parse(final String cgroupsContent) throws ParseException {
|
||||
final ContainerInfo containerInfo = new ContainerInfo();
|
||||
|
||||
final String[] lines = cgroupsContent.split("\n");
|
||||
for (final String line : lines) {
|
||||
final CGroupInfo cGroupInfo = parseLine(line);
|
||||
|
||||
containerInfo.getCGroups().add(cGroupInfo);
|
||||
|
||||
if (cGroupInfo.getPodId() != null) {
|
||||
containerInfo.setPodId(cGroupInfo.getPodId());
|
||||
}
|
||||
|
||||
if (cGroupInfo.getContainerId() != null) {
|
||||
containerInfo.setContainerId(cGroupInfo.getContainerId());
|
||||
}
|
||||
}
|
||||
|
||||
return containerInfo;
|
||||
}
|
||||
|
||||
static CGroupInfo parseLine(final String line) throws ParseException {
|
||||
final Matcher matcher = LINE_PATTERN.matcher(line);
|
||||
|
||||
if (!matcher.matches()) {
|
||||
throw new ParseException("Unable to match cgroup", 0);
|
||||
}
|
||||
|
||||
final CGroupInfo cGroupInfo = new CGroupInfo();
|
||||
cGroupInfo.setId(Integer.parseInt(matcher.group(1)));
|
||||
cGroupInfo.setControllers(Arrays.asList(matcher.group(2).split(",")));
|
||||
|
||||
final String path = matcher.group(3);
|
||||
final String[] pathParts = path.split("/");
|
||||
|
||||
cGroupInfo.setPath(path);
|
||||
|
||||
if (pathParts.length >= 1) {
|
||||
final Matcher containerIdMatcher = CONTAINER_PATTERN.matcher(pathParts[pathParts.length - 1]);
|
||||
final String containerId = containerIdMatcher.matches() ? containerIdMatcher.group(1) : null;
|
||||
cGroupInfo.setContainerId(containerId);
|
||||
}
|
||||
|
||||
if (pathParts.length >= 2) {
|
||||
final Matcher podIdMatcher = POD_PATTERN.matcher(pathParts[pathParts.length - 2]);
|
||||
final String podId = podIdMatcher.matches() ? podIdMatcher.group(1) : null;
|
||||
cGroupInfo.setPodId(podId);
|
||||
}
|
||||
|
||||
return cGroupInfo;
|
||||
}
|
||||
}
|
||||
|
|
@ -44,6 +44,7 @@ public class ContinuableScope implements Scope, TraceScope {
|
|||
final Continuation continuation,
|
||||
final DDSpan spanUnderScope,
|
||||
final boolean finishOnClose) {
|
||||
assert spanUnderScope != null : "span must not be null";
|
||||
this.scopeManager = scopeManager;
|
||||
this.openCount = openCount;
|
||||
this.continuation = continuation;
|
||||
|
|
|
|||
|
|
@ -15,10 +15,11 @@ public class SimpleScope implements Scope {
|
|||
final ContextualScopeManager scopeManager,
|
||||
final Span spanUnderScope,
|
||||
final boolean finishOnClose) {
|
||||
assert spanUnderScope != null : "span must not be null";
|
||||
this.scopeManager = scopeManager;
|
||||
this.spanUnderScope = spanUnderScope;
|
||||
this.finishOnClose = finishOnClose;
|
||||
this.toRestore = scopeManager.tlsScope.get();
|
||||
toRestore = scopeManager.tlsScope.get();
|
||||
scopeManager.tlsScope.set(this);
|
||||
for (final ScopeListener listener : scopeManager.scopeListeners) {
|
||||
listener.afterScopeActivated();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonParseException;
|
|||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import datadog.opentracing.ContainerInfo;
|
||||
import datadog.opentracing.DDSpan;
|
||||
import datadog.opentracing.DDTraceOTInfo;
|
||||
import datadog.trace.common.writer.unixdomainsockets.UnixDomainSocketFactory;
|
||||
|
|
@ -35,6 +36,7 @@ public class DDApi {
|
|||
private static final String DATADOG_META_LANG_INTERPRETER_VENDOR =
|
||||
"Datadog-Meta-Lang-Interpreter-Vendor";
|
||||
private static final String DATADOG_META_TRACER_VERSION = "Datadog-Meta-Tracer-Version";
|
||||
private static final String DATADOG_CONTAINER_ID = "Datadog-Container-ID";
|
||||
private static final String X_DATADOG_TRACE_COUNT = "X-Datadog-Trace-Count";
|
||||
|
||||
private static final int HTTP_TIMEOUT = 1; // 1 second for conenct/read/write operations
|
||||
|
|
@ -261,13 +263,21 @@ public class DDApi {
|
|||
}
|
||||
|
||||
private static Request.Builder prepareRequest(final HttpUrl url) {
|
||||
return new Request.Builder()
|
||||
.url(url)
|
||||
.addHeader(DATADOG_META_LANG, "java")
|
||||
.addHeader(DATADOG_META_LANG_VERSION, DDTraceOTInfo.JAVA_VERSION)
|
||||
.addHeader(DATADOG_META_LANG_INTERPRETER, DDTraceOTInfo.JAVA_VM_NAME)
|
||||
.addHeader(DATADOG_META_LANG_INTERPRETER_VENDOR, DDTraceOTInfo.JAVA_VM_VENDOR)
|
||||
.addHeader(DATADOG_META_TRACER_VERSION, DDTraceOTInfo.VERSION);
|
||||
final Request.Builder builder =
|
||||
new Request.Builder()
|
||||
.url(url)
|
||||
.addHeader(DATADOG_META_LANG, "java")
|
||||
.addHeader(DATADOG_META_LANG_VERSION, DDTraceOTInfo.JAVA_VERSION)
|
||||
.addHeader(DATADOG_META_LANG_INTERPRETER, DDTraceOTInfo.JAVA_VM_NAME)
|
||||
.addHeader(DATADOG_META_LANG_INTERPRETER_VENDOR, DDTraceOTInfo.JAVA_VM_VENDOR)
|
||||
.addHeader(DATADOG_META_TRACER_VERSION, DDTraceOTInfo.VERSION);
|
||||
|
||||
final String containerId = ContainerInfo.get().getContainerId();
|
||||
if (containerId == null) {
|
||||
return builder;
|
||||
} else {
|
||||
return builder.addHeader(DATADOG_CONTAINER_ID, containerId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,148 @@
|
|||
package datadog.opentracing
|
||||
|
||||
import spock.lang.Specification
|
||||
import spock.lang.Unroll
|
||||
|
||||
|
||||
class ContainerInfoTest extends Specification {
|
||||
|
||||
@Unroll
|
||||
def "CGroupInfo is parsed from individual lines"() {
|
||||
when:
|
||||
ContainerInfo.CGroupInfo cGroupInfo = ContainerInfo.parseLine(line)
|
||||
|
||||
then:
|
||||
cGroupInfo.getId() == id
|
||||
cGroupInfo.getPath() == path
|
||||
cGroupInfo.getControllers() == controllers
|
||||
cGroupInfo.getContainerId() == containerId
|
||||
cGroupInfo.podId == podId
|
||||
|
||||
// Examples from container tagging rfc and Qard/container-info
|
||||
where:
|
||||
id | controllers | path | containerId | podId | line
|
||||
|
||||
// Docker examples
|
||||
13 | ["name=systemd"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "13:name=systemd:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
|
||||
12 | ["pids"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "12:pids:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
|
||||
11 | ["hugetlb"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "11:hugetlb:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
|
||||
10 | ["net_prio"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "10:net_prio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
|
||||
9 | ["perf_event"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "9:perf_event:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
|
||||
8 | ["net_cls"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "8:net_cls:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
|
||||
7 | ["freezer"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "7:freezer:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
|
||||
6 | ["devices"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "6:devices:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
|
||||
5 | ["memory"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "5:memory:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
|
||||
4 | ["blkio"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "4:blkio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
|
||||
3 | ["cpuacct"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "3:cpuacct:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
|
||||
2 | ["cpu"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "2:cpu:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
|
||||
1 | ["cpuset"] | "/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | "3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | "1:cpuset:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"
|
||||
|
||||
// Kubernates examples
|
||||
11 | ["perf_event"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "11:perf_event:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
|
||||
10 | ["pids"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "10:pids:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
|
||||
9 | ["memory"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "9:memory:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
|
||||
8 | ["cpu", "cpuacct"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "8:cpu,cpuacct:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
|
||||
7 | ["blkio"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "7:blkio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
|
||||
6 | ["cpuset"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "6:cpuset:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
|
||||
5 | ["devices"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "5:devices:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
|
||||
4 | ["freezer"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "4:freezer:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
|
||||
3 | ["net_cls", "net_prio"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "3:net_cls,net_prio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
|
||||
2 | ["hugetlb"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "2:hugetlb:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
|
||||
1 | ["name=systemd"] | "/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | "1:name=systemd:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"
|
||||
|
||||
//ECS examples
|
||||
9 | ["perf_event"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "9:perf_event:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"
|
||||
8 | ["memory"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "8:memory:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"
|
||||
7 | ["hugetlb"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "7:hugetlb:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"
|
||||
6 | ["freezer"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "6:freezer:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"
|
||||
5 | ["devices"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "5:devices:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"
|
||||
4 | ["cpuset"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "4:cpuset:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"
|
||||
3 | ["cpuacct"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "3:cpuacct:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"
|
||||
2 | ["cpu"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "2:cpu:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"
|
||||
1 | ["blkio"] | "/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | "38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | "1:blkio:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"
|
||||
|
||||
//Fargate Examples
|
||||
11 | ["hugetlb"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "11:hugetlb:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
|
||||
10 | ["pids"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "10:pids:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
|
||||
9 | ["cpuset"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "9:cpuset:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
|
||||
8 | ["net_cls", "net_prio"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "8:net_cls,net_prio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
|
||||
7 | ["cpu", "cpuacct"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "7:cpu,cpuacct:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
|
||||
6 | ["perf_event"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "6:perf_event:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
|
||||
5 | ["freezer"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "5:freezer:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
|
||||
4 | ["devices"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "4:devices:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
|
||||
3 | ["blkio"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "3:blkio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
|
||||
2 | ["memory"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "2:memory:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
|
||||
1 | ["name=systemd"] | "/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | "1:name=systemd:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"
|
||||
|
||||
//Reference impl examples
|
||||
1 | ["name=systemd"] | "/system.slice/docker-cde7c2bab394630a42d73dc610b9c57415dced996106665d427f6d0566594411.scope" | "cde7c2bab394630a42d73dc610b9c57415dced996106665d427f6d0566594411" | null | "1:name=systemd:/system.slice/docker-cde7c2bab394630a42d73dc610b9c57415dced996106665d427f6d0566594411.scope"
|
||||
1 | ["name=systemd"] | "/docker/051e2ee0bce99116029a13df4a9e943137f19f957f38ac02d6bad96f9b700f76/not_hex" | null | null | "1:name=systemd:/docker/051e2ee0bce99116029a13df4a9e943137f19f957f38ac02d6bad96f9b700f76/not_hex"
|
||||
1 | ["name=systemd"] | "/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod90d81341_92de_11e7_8cf2_507b9d4141fa.slice/crio-2227daf62df6694645fee5df53c1f91271546a9560e8600a525690ae252b7f63.scope" | "2227daf62df6694645fee5df53c1f91271546a9560e8600a525690ae252b7f63" | "90d81341_92de_11e7_8cf2_507b9d4141fa" | "1:name=systemd:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod90d81341_92de_11e7_8cf2_507b9d4141fa.slice/crio-2227daf62df6694645fee5df53c1f91271546a9560e8600a525690ae252b7f63.scope"
|
||||
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "Container info parsed from file content"() {
|
||||
when:
|
||||
ContainerInfo containerInfo = ContainerInfo.parse(content)
|
||||
|
||||
then:
|
||||
containerInfo.getContainerId() == containerId
|
||||
containerInfo.getPodId() == podId
|
||||
containerInfo.getCGroups().size() == size
|
||||
|
||||
where:
|
||||
containerId | podId | size | content
|
||||
// Docker
|
||||
"3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860" | null | 13 | """13:name=systemd:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
|
||||
12:pids:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
|
||||
11:hugetlb:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
|
||||
10:net_prio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
|
||||
9:perf_event:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
|
||||
8:net_cls:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
|
||||
7:freezer:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
|
||||
6:devices:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
|
||||
5:memory:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
|
||||
4:blkio:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
|
||||
3:cpuacct:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
|
||||
2:cpu:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860
|
||||
1:cpuset:/docker/3726184226f5d3147c25fdeab5b60097e378e8a720503a5e19ecfdf29f869860"""
|
||||
|
||||
// Kubernetes
|
||||
"3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1" | "3d274242-8ee0-11e9-a8a6-1e68d864ef1a" | 11 | """11:perf_event:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
|
||||
10:pids:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
|
||||
9:memory:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
|
||||
8:cpu,cpuacct:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
|
||||
7:blkio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
|
||||
6:cpuset:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
|
||||
5:devices:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
|
||||
4:freezer:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
|
||||
3:net_cls,net_prio:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
|
||||
2:hugetlb:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1
|
||||
1:name=systemd:/kubepods/besteffort/pod3d274242-8ee0-11e9-a8a6-1e68d864ef1a/3e74d3fd9db4c9dd921ae05c2502fb984d0cde1b36e581b13f79c639da4518a1"""
|
||||
|
||||
// ECS
|
||||
"38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce" | null | 9 | """9:perf_event:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
|
||||
8:memory:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
|
||||
7:hugetlb:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
|
||||
6:freezer:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
|
||||
5:devices:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
|
||||
4:cpuset:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
|
||||
3:cpuacct:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
|
||||
2:cpu:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce
|
||||
1:blkio:/ecs/haissam-ecs-classic/5a0d5ceddf6c44c1928d367a815d890f/38fac3e99302b3622be089dd41e7ccf38aff368a86cc339972075136ee2710ce"""
|
||||
|
||||
// Fargate
|
||||
"432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da" | null | 11 | """11:hugetlb:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
|
||||
10:pids:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
|
||||
9:cpuset:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
|
||||
8:net_cls,net_prio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
|
||||
7:cpu,cpuacct:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
|
||||
6:perf_event:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
|
||||
5:freezer:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
|
||||
4:devices:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
|
||||
3:blkio:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
|
||||
2:memory:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da
|
||||
1:name=systemd:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da"""
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,8 @@ import datadog.trace.api.Config
|
|||
import datadog.trace.api.DDTags
|
||||
import datadog.trace.api.sampling.PrioritySampling
|
||||
import datadog.trace.common.writer.ListWriter
|
||||
import io.opentracing.Scope
|
||||
import io.opentracing.noop.NoopSpan
|
||||
import spock.lang.Specification
|
||||
|
||||
import static datadog.opentracing.DDSpanContext.ORIGIN_KEY
|
||||
|
|
@ -173,6 +175,33 @@ class DDSpanBuilderTest extends Specification {
|
|||
actualContext.getTraceId() == spanId
|
||||
}
|
||||
|
||||
def "should link to parent span implicitly"() {
|
||||
setup:
|
||||
final Scope parent = noopParent ?
|
||||
tracer.scopeManager().activate(NoopSpan.INSTANCE, false) :
|
||||
tracer.buildSpan("parent")
|
||||
.startActive(false)
|
||||
|
||||
final String expectedParentId = noopParent ? "0" : parent.span().context().getSpanId()
|
||||
|
||||
final String expectedName = "fakeName"
|
||||
|
||||
final DDSpan span = tracer
|
||||
.buildSpan(expectedName)
|
||||
.start()
|
||||
|
||||
final DDSpanContext actualContext = span.context()
|
||||
|
||||
expect:
|
||||
actualContext.getParentId() == expectedParentId
|
||||
|
||||
cleanup:
|
||||
parent.close()
|
||||
|
||||
where:
|
||||
noopParent << [false, true]
|
||||
}
|
||||
|
||||
def "should inherit the DD parent attributes"() {
|
||||
setup:
|
||||
def expectedName = "fakeName"
|
||||
|
|
|
|||
|
|
@ -96,19 +96,19 @@ class ScopeManagerTest extends Specification {
|
|||
def "sets parent as current upon close"() {
|
||||
setup:
|
||||
def parentScope = tracer.buildSpan("parent").startActive(finishSpan)
|
||||
def childScope = tracer.buildSpan("parent").startActive(finishSpan)
|
||||
def childScope = noopChild ? tracer.scopeManager().activate(NoopSpan.INSTANCE, finishSpan) : tracer.buildSpan("parent").startActive(finishSpan)
|
||||
|
||||
expect:
|
||||
scopeManager.active() == childScope
|
||||
childScope.span().context().parentId == parentScope.span().context().spanId
|
||||
childScope.span().context().trace == parentScope.span().context().trace
|
||||
noopChild || childScope.span().context().parentId == parentScope.span().context().spanId
|
||||
noopChild || childScope.span().context().trace == parentScope.span().context().trace
|
||||
|
||||
when:
|
||||
childScope.close()
|
||||
|
||||
then:
|
||||
scopeManager.active() == parentScope
|
||||
spanFinished(childScope.span()) == finishSpan
|
||||
noopChild || spanFinished(childScope.span()) == finishSpan
|
||||
!spanFinished(parentScope.span())
|
||||
writer == []
|
||||
|
||||
|
|
@ -116,13 +116,17 @@ class ScopeManagerTest extends Specification {
|
|||
parentScope.close()
|
||||
|
||||
then:
|
||||
spanFinished(childScope.span()) == finishSpan
|
||||
noopChild || spanFinished(childScope.span()) == finishSpan
|
||||
spanFinished(parentScope.span()) == finishSpan
|
||||
writer == [[parentScope.span(), childScope.span()]] || !finishSpan
|
||||
writer == [[parentScope.span(), childScope.span()]] || !finishSpan || noopChild
|
||||
scopeManager.active() == null
|
||||
|
||||
where:
|
||||
finishSpan << [true, false]
|
||||
finishSpan | noopChild
|
||||
true | false
|
||||
false | false
|
||||
true | true
|
||||
false | true
|
||||
}
|
||||
|
||||
def "ContinuableScope only creates continuations when propagation is set"() {
|
||||
|
|
@ -568,7 +572,7 @@ class ScopeManagerTest extends Specification {
|
|||
}
|
||||
|
||||
boolean spanFinished(Span span) {
|
||||
return ((DDSpan) span).isFinished()
|
||||
return ((DDSpan) span)?.isFinished()
|
||||
}
|
||||
|
||||
class AtomicReferenceScope extends AtomicReference<Span> implements ScopeContext, Scope {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ include ':utils:gc-utils'
|
|||
include ':dd-smoke-tests:play'
|
||||
include ':dd-smoke-tests:springboot'
|
||||
include ':dd-smoke-tests:wildfly'
|
||||
include ':dd-smoke-tests:cli'
|
||||
|
||||
// instrumentation:
|
||||
include ':dd-java-agent:instrumentation:akka-http-10.0'
|
||||
|
|
@ -41,6 +42,7 @@ include ':dd-java-agent:instrumentation:elasticsearch:transport-5'
|
|||
include ':dd-java-agent:instrumentation:elasticsearch:transport-5.3'
|
||||
include ':dd-java-agent:instrumentation:elasticsearch:transport-6'
|
||||
include ':dd-java-agent:instrumentation:glassfish'
|
||||
include ':dd-java-agent:instrumentation:google-http-client'
|
||||
include ':dd-java-agent:instrumentation:grpc-1.5'
|
||||
include ':dd-java-agent:instrumentation:hibernate'
|
||||
include ':dd-java-agent:instrumentation:hibernate:core-3.3'
|
||||
|
|
|
|||
Loading…
Reference in New Issue