Skip to content

Commit cc3cdec

Browse files
authored
Merge pull request DataDog#2709 from DataDog/charliegracie/restlet
2 parents fb05542 + eea48b8 commit cc3cdec

17 files changed

+763
-0
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
muzzle {
2+
pass {
3+
group = "org.restlet.jse"
4+
module = "org.restlet"
5+
versions = "[2.2.0,)"
6+
assertInverse = false
7+
}
8+
}
9+
10+
apply from: "$rootDir/gradle/java.gradle"
11+
12+
apply plugin: 'org.unbroken-dome.test-sets'
13+
14+
testSets {
15+
latestDepTest {
16+
dirName = 'test'
17+
}
18+
}
19+
20+
dependencies {
21+
compileOnly group: 'org.restlet.jse', name: 'org.restlet', version: '2.2.0'
22+
23+
testCompile group: 'org.restlet.jse', name: 'org.restlet', version: '2.2.0'
24+
25+
latestDepTestCompile group: 'org.restlet.jse', name: 'org.restlet', version: '2.4+'
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package datadog.trace.instrumentation.restlet;
2+
3+
import com.sun.net.httpserver.HttpExchange;
4+
import com.sun.net.httpserver.HttpsExchange;
5+
import datadog.trace.bootstrap.instrumentation.api.URIDataAdapter;
6+
import java.net.InetSocketAddress;
7+
8+
final class HttpExchangeURIDataAdapter implements URIDataAdapter {
9+
10+
private final HttpExchange exchange;
11+
12+
HttpExchangeURIDataAdapter(HttpExchange exchange) {
13+
this.exchange = exchange;
14+
}
15+
16+
@Override
17+
public String scheme() {
18+
// scheme is not available in getRequestURI
19+
if (exchange instanceof HttpsExchange) {
20+
return "https";
21+
}
22+
return "http";
23+
}
24+
25+
@Override
26+
public String host() {
27+
// host is not available in getRequestURI
28+
InetSocketAddress inetSocketAddress = exchange.getLocalAddress();
29+
if (inetSocketAddress.isUnresolved()) {
30+
return inetSocketAddress.getHostString();
31+
} else {
32+
return inetSocketAddress.getHostName();
33+
}
34+
}
35+
36+
@Override
37+
public int port() {
38+
// port is not available in getRequestURI
39+
return exchange.getLocalAddress().getPort();
40+
}
41+
42+
@Override
43+
public String path() {
44+
return exchange.getRequestURI().getPath();
45+
}
46+
47+
@Override
48+
public String fragment() {
49+
// fragment is not available in getRequestURI
50+
return "";
51+
}
52+
53+
@Override
54+
public String query() {
55+
return exchange.getRequestURI().getQuery();
56+
}
57+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package datadog.trace.instrumentation.restlet;
2+
3+
import static datadog.trace.bootstrap.instrumentation.decorator.RouteHandlerDecorator.ROUTE_HANDLER_DECORATOR;
4+
5+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
6+
import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes;
7+
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
8+
import datadog.trace.bootstrap.instrumentation.decorator.BaseDecorator;
9+
import java.lang.reflect.Method;
10+
import org.restlet.engine.header.Header;
11+
import org.restlet.resource.ServerResource;
12+
import org.restlet.util.Series;
13+
14+
public class ResourceDecorator extends BaseDecorator {
15+
16+
public static final CharSequence RESTLET_CONTROLLER =
17+
UTF8BytesString.create("restlet-controller");
18+
19+
public static final String RESTLET_ROUTE = "datadog.trace.instrumentation.restlet.route";
20+
21+
public static ResourceDecorator DECORATE = new ResourceDecorator();
22+
23+
@Override
24+
protected String[] instrumentationNames() {
25+
return new String[] {"restlet-http"};
26+
}
27+
28+
@Override
29+
protected CharSequence spanType() {
30+
return null;
31+
}
32+
33+
@Override
34+
protected CharSequence component() {
35+
return RESTLET_CONTROLLER;
36+
}
37+
38+
public void onRestletSpan(
39+
final AgentSpan span,
40+
final AgentSpan parent,
41+
final ServerResource serverResource,
42+
final Method method) {
43+
Series<Header> headers =
44+
(Series<Header>)
45+
serverResource.getRequest().getAttributes().get("org.restlet.http.headers");
46+
String route = headers.getFirstValue(RESTLET_ROUTE);
47+
48+
span.setSpanType(InternalSpanTypes.HTTP_SERVER);
49+
50+
// When restlet-http is the root, we want to name using the path, otherwise use
51+
// class.method.
52+
final boolean isRootScope = parent == null;
53+
if (isRootScope) {
54+
ROUTE_HANDLER_DECORATOR.withRoute(span, serverResource.getMethod().getName(), route);
55+
} else {
56+
span.setResourceName(DECORATE.spanNameForMethod(method));
57+
58+
if (parent == parent.getLocalRootSpan()) {
59+
ROUTE_HANDLER_DECORATOR.withRoute(parent, serverResource.getMethod().getName(), route);
60+
}
61+
}
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package datadog.trace.instrumentation.restlet;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.DDElementMatchers.extendsClass;
4+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
5+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
6+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan;
7+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
8+
import static datadog.trace.instrumentation.restlet.ResourceDecorator.DECORATE;
9+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
10+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
11+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
12+
13+
import com.google.auto.service.AutoService;
14+
import datadog.trace.agent.tooling.Instrumenter;
15+
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
16+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
17+
import net.bytebuddy.asm.Advice;
18+
import net.bytebuddy.description.type.TypeDescription;
19+
import net.bytebuddy.matcher.ElementMatcher;
20+
import org.restlet.engine.resource.AnnotationInfo;
21+
import org.restlet.resource.ServerResource;
22+
23+
@AutoService(Instrumenter.class)
24+
public final class ResourceInstrumentation extends Instrumenter.Tracing {
25+
26+
private static final String RESTLET_HTTP_OPERATION_NAME = "restlet.request";
27+
28+
public ResourceInstrumentation() {
29+
super("restlet-http");
30+
}
31+
32+
@Override
33+
public ElementMatcher<TypeDescription> typeMatcher() {
34+
return named("org.restlet.resource.ServerResource");
35+
}
36+
37+
@Override
38+
public void adviceTransformations(AdviceTransformation transformation) {
39+
transformation.applyAdvice(
40+
isMethod()
41+
.and(named("doHandle"))
42+
.and(takesArguments(2))
43+
// In 2.2 this parameter is of type AnnotationInfo. In 2.3+ it is of type
44+
// MethodAnnotationInfo, which is a subclass of AnnotationInfo
45+
.and(
46+
takesArgument(0, extendsClass(named("org.restlet.engine.resource.AnnotationInfo"))))
47+
.and(takesArgument(1, named("org.restlet.representation.Variant"))),
48+
getClass().getName() + "$ResourceHandleAdvice");
49+
}
50+
51+
@Override
52+
public String[] helperClassNames() {
53+
return new String[] {
54+
packageName + ".ResourceDecorator",
55+
};
56+
}
57+
58+
public static class ResourceHandleAdvice {
59+
@Advice.OnMethodEnter(suppress = Throwable.class)
60+
public static AgentScope beginRequest(
61+
@Advice.This final ServerResource serverResource,
62+
@Advice.Argument(0) final AnnotationInfo annotationInfo) {
63+
final AgentSpan parent = activeSpan();
64+
65+
final AgentSpan span = startSpan(RESTLET_HTTP_OPERATION_NAME);
66+
span.setMeasured(true);
67+
DECORATE.onRestletSpan(span, parent, serverResource, annotationInfo.getJavaMethod());
68+
DECORATE.afterStart(span);
69+
70+
return activateSpan(span);
71+
}
72+
73+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
74+
public static void finishRequest(
75+
@Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable error) {
76+
AgentSpan span = scope.span();
77+
78+
if (null != error) {
79+
DECORATE.onError(span, error);
80+
}
81+
82+
DECORATE.beforeFinish(span);
83+
scope.close();
84+
span.finish();
85+
}
86+
}
87+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package datadog.trace.instrumentation.restlet;
2+
3+
import com.sun.net.httpserver.HttpExchange;
4+
import datadog.trace.bootstrap.instrumentation.api.URIDataAdapter;
5+
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
6+
import datadog.trace.bootstrap.instrumentation.decorator.HttpServerDecorator;
7+
8+
public class RestletDecorator
9+
extends HttpServerDecorator<HttpExchange, HttpExchange, HttpExchange> {
10+
public static final CharSequence RESTLET_REQUEST = UTF8BytesString.create("restlet-http.request");
11+
public static final CharSequence RESTLET_HTTP_SERVER =
12+
UTF8BytesString.create("restlet-http-server");
13+
public static final RestletDecorator DECORATE = new RestletDecorator();
14+
15+
@Override
16+
protected String[] instrumentationNames() {
17+
return new String[] {"restlet-http", "restlet-http-server"};
18+
}
19+
20+
@Override
21+
protected CharSequence component() {
22+
return RESTLET_HTTP_SERVER;
23+
}
24+
25+
@Override
26+
protected String method(final HttpExchange exchange) {
27+
return exchange.getRequestMethod();
28+
}
29+
30+
@Override
31+
protected URIDataAdapter url(final HttpExchange exchange) {
32+
return new HttpExchangeURIDataAdapter(exchange);
33+
}
34+
35+
@Override
36+
protected String peerHostIP(final HttpExchange exchange) {
37+
return exchange.getRemoteAddress().getAddress().getHostAddress();
38+
}
39+
40+
@Override
41+
protected int peerPort(final HttpExchange exchange) {
42+
return exchange.getRemoteAddress().getPort();
43+
}
44+
45+
@Override
46+
protected int status(final HttpExchange exchange) {
47+
return exchange.getResponseCode();
48+
}
49+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package datadog.trace.instrumentation.restlet;
2+
3+
import com.sun.net.httpserver.Headers;
4+
import com.sun.net.httpserver.HttpExchange;
5+
import datadog.trace.bootstrap.instrumentation.api.AgentPropagation;
6+
import java.util.List;
7+
import java.util.Map;
8+
9+
public class RestletExtractAdapter implements AgentPropagation.ContextVisitor<HttpExchange> {
10+
public static final RestletExtractAdapter GETTER = new RestletExtractAdapter();
11+
12+
@Override
13+
public void forEachKey(HttpExchange exchange, AgentPropagation.KeyClassifier classifier) {
14+
Headers headers = exchange.getRequestHeaders();
15+
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
16+
List<String> values = entry.getValue();
17+
if (!values.isEmpty() && !classifier.accept(entry.getKey(), values.get(0))) {
18+
return;
19+
}
20+
}
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package datadog.trace.instrumentation.restlet;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
5+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.propagate;
6+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
7+
import static datadog.trace.instrumentation.restlet.RestletDecorator.DECORATE;
8+
import static datadog.trace.instrumentation.restlet.RestletDecorator.RESTLET_REQUEST;
9+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
10+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
11+
12+
import com.google.auto.service.AutoService;
13+
import com.sun.net.httpserver.HttpExchange;
14+
import datadog.trace.agent.tooling.Instrumenter;
15+
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
16+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
17+
import net.bytebuddy.asm.Advice;
18+
import net.bytebuddy.description.type.TypeDescription;
19+
import net.bytebuddy.matcher.ElementMatcher;
20+
21+
@AutoService(Instrumenter.class)
22+
public final class RestletInstrumentation extends Instrumenter.Tracing {
23+
24+
public RestletInstrumentation() {
25+
super("restlet-http", "restlet-http-server");
26+
}
27+
28+
@Override
29+
public ElementMatcher<TypeDescription> typeMatcher() {
30+
return named("org.restlet.engine.connector.HttpServerHelper$1")
31+
.or(named("org.restlet.engine.connector.HttpsServerHelper$2"));
32+
}
33+
34+
@Override
35+
public void adviceTransformations(AdviceTransformation transformation) {
36+
transformation.applyAdvice(
37+
isMethod()
38+
.and(named("handle"))
39+
.and(takesArgument(0, named("com.sun.net.httpserver.HttpExchange"))),
40+
getClass().getName() + "$RestletHandleAdvice");
41+
}
42+
43+
@Override
44+
public String[] helperClassNames() {
45+
return new String[] {
46+
packageName + ".RestletDecorator",
47+
packageName + ".RestletExtractAdapter",
48+
packageName + ".HttpExchangeURIDataAdapter"
49+
};
50+
}
51+
52+
public static class RestletHandleAdvice {
53+
@Advice.OnMethodEnter(suppress = Throwable.class)
54+
public static AgentScope beginRequest(@Advice.Argument(0) final HttpExchange exchange) {
55+
AgentSpan.Context.Extracted context =
56+
propagate().extract(exchange, RestletExtractAdapter.GETTER);
57+
58+
AgentSpan span = startSpan(RESTLET_REQUEST, context);
59+
span.setMeasured(true);
60+
DECORATE.afterStart(span);
61+
DECORATE.onRequest(span, exchange, exchange, context);
62+
DECORATE.onPeerConnection(span, exchange.getRemoteAddress());
63+
64+
return activateSpan(span);
65+
}
66+
67+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
68+
public static void finishRequest(
69+
@Advice.Enter final AgentScope scope,
70+
@Advice.Argument(0) final HttpExchange exchange,
71+
@Advice.Thrown final Throwable error) {
72+
if (null == scope) {
73+
return;
74+
}
75+
76+
AgentSpan span = scope.span();
77+
DECORATE.onResponse(span, exchange);
78+
79+
if (null != error) {
80+
DECORATE.onError(span, error);
81+
}
82+
83+
DECORATE.beforeFinish(span);
84+
scope.close();
85+
span.finish();
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)