Restlet instrumentation (#3946)

* server instrumentation with first tests

* migrate to instrumenter API, move TracingFilter to library, rename module, other review refinements

* change name to 1.0 and rebase

* review, add ServerSpanNaming, create RestletTracing

* codenarc fix

* review

* fix TracingFilter behaviour on exception, inline HeadersAdapter's methods

* move instrumentation to doHandle, add StatusFilter in library test
This commit is contained in:
Anna Nosek 2021-09-14 07:39:25 +02:00 committed by GitHub
parent 1c3c3795dc
commit 473f16fa68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 850 additions and 0 deletions

View File

@ -0,0 +1,28 @@
plugins {
id("otel.javaagent-instrumentation")
}
muzzle {
pass {
group.set("org.restlet")
module.set("org.restlet")
versions.set("[1.0.0, 1.2-M1)")
assertInverse.set(true)
}
}
repositories {
mavenCentral()
maven("https://maven.restlet.talend.com/")
mavenLocal()
}
dependencies {
api(project(":instrumentation:restlet:restlet-1.0:library"))
library("org.restlet:org.restlet:1.1.5")
library("com.noelios.restlet:com.noelios.restlet:1.1.5")
implementation(project(":instrumentation:restlet:restlet-1.0:library"))
testImplementation(project(":instrumentation:restlet:restlet-1.0:testing"))
}

View File

@ -0,0 +1,25 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.restlet.v1_0;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.Arrays;
import java.util.List;
@AutoService(InstrumentationModule.class)
public class RestletInstrumentationModule extends InstrumentationModule {
public RestletInstrumentationModule() {
super("restlet", "restlet-1.0");
}
@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Arrays.asList(new ServerInstrumentation(), new RouteInstrumentation());
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.restlet.v1_0;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.restlet.v1_0.RestletTracing;
import org.restlet.data.Request;
import org.restlet.data.Response;
public final class RestletSingletons {
private static final Instrumenter<Request, Response> INSTRUMENTER =
RestletTracing.create(GlobalOpenTelemetry.get()).getServerInstrumenter();
public static Instrumenter<Request, Response> instrumenter() {
return INSTRUMENTER;
}
private RestletSingletons() {}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.restlet.v1_0;
import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.CONTROLLER;
import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming;
import io.opentelemetry.instrumentation.restlet.v1_0.internal.RestletServerSpanNaming;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.restlet.Route;
import org.restlet.data.Request;
public class RouteInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.restlet.Route");
}
@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod()
.and(named("beforeHandle"))
.and(takesArgument(0, named("org.restlet.data.Request")))
.and(takesArgument(1, named("org.restlet.data.Response"))),
this.getClass().getName() + "$RouteBeforeHandleAdvice");
}
@SuppressWarnings("unused")
public static class RouteBeforeHandleAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void getRouteInfo(@Advice.This Route route, @Advice.Argument(0) Request request) {
String pattern = route.getTemplate().getPattern();
ServerSpanNaming.updateServerSpanName(
currentContext(), CONTROLLER, RestletServerSpanNaming.SERVER_SPAN_NAME, pattern);
}
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.restlet.v1_0;
import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.instrumentation.restlet.v1_0.RestletSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.restlet.data.Request;
import org.restlet.data.Response;
public class ServerInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.restlet.Server");
}
@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod()
.and(named("handle"))
.and(takesArgument(0, named("org.restlet.data.Request")))
.and(takesArgument(1, named("org.restlet.data.Response"))),
this.getClass().getName() + "$ServerHandleAdvice");
}
@SuppressWarnings("unused")
public static class ServerHandleAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void beginRequest(
@Advice.Argument(0) Request request,
@Advice.Argument(1) Response response,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
Context parentContext = currentContext();
if (!instrumenter().shouldStart(parentContext, request)) {
return;
}
context = instrumenter().start(parentContext, request);
scope = context.makeCurrent();
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void finishRequest(
@Advice.Argument(0) Request request,
@Advice.Argument(1) Response response,
@Advice.Thrown Throwable exception,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
if (scope == null) {
return;
}
scope.close();
if (exception != null) {
instrumenter().end(context, request, response, exception);
return;
}
// Restlet suppresses exceptions and sets the throwable in status
Throwable statusThrowable = response.getStatus().getThrowable();
instrumenter().end(context, request, response, statusThrowable);
}
}
}

View File

@ -0,0 +1,19 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.restlet.v1_0
import io.opentelemetry.instrumentation.restlet.v1_0.AbstractRestletServerTest
import io.opentelemetry.instrumentation.test.AgentTestTrait
import org.restlet.Restlet
class RestletServerTest extends AbstractRestletServerTest implements AgentTestTrait {
@Override
Restlet wrapRestlet(Restlet restlet, String path){
return restlet
}
}

View File

@ -0,0 +1,20 @@
plugins {
id("otel.library-instrumentation")
}
repositories {
mavenCentral()
maven("https://maven.restlet.talend.com/")
mavenLocal()
}
dependencies {
compileOnly("com.google.auto.value:auto-value-annotations")
annotationProcessor("com.google.auto.value:auto-value")
library("org.restlet:org.restlet:1.1.5")
library("com.noelios.restlet:com.noelios.restlet:1.1.5")
testImplementation(project(":instrumentation:restlet:restlet-1.0:testing"))
}

View File

@ -0,0 +1,35 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.restlet.v1_0;
import io.opentelemetry.context.propagation.TextMapGetter;
import java.util.Locale;
import org.restlet.data.Form;
import org.restlet.data.Request;
final class RestletHeadersGetter implements TextMapGetter<Request> {
@Override
public Iterable<String> keys(Request carrier) {
return getHeaders(carrier).getNames();
}
@Override
public String get(Request carrier, String key) {
Form headers = getHeaders(carrier);
String value = headers.getFirstValue(key);
if (value != null) {
return value;
}
return headers.getFirstValue(key.toLowerCase(Locale.ROOT));
}
private static Form getHeaders(Request carrier) {
return (Form) carrier.getAttributes().get("org.restlet.http.headers");
}
}

View File

@ -0,0 +1,99 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.restlet.v1_0;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpAttributesExtractor;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.restlet.data.Reference;
import org.restlet.data.Request;
import org.restlet.data.Response;
final class RestletHttpAttributesExtractor extends HttpAttributesExtractor<Request, Response> {
@Override
protected String method(Request request) {
return request.getMethod().toString();
}
@Override
protected String url(Request request) {
return request.getOriginalRef().toString();
}
@Override
protected @Nullable String target(Request request) {
Reference ref = request.getOriginalRef();
String path = ref.getPath();
return ref.hasQuery() ? path + "?" + ref.getQuery() : path;
}
@Override
protected @Nullable String host(Request request) {
return null;
}
@Override
protected @Nullable String route(Request request) {
return null;
}
@Override
protected @Nullable String scheme(Request request) {
return request.getOriginalRef().getScheme();
}
@Override
protected @Nullable String userAgent(Request request) {
return request.getClientInfo().getAgent();
}
@Override
protected @Nullable Long requestContentLength(Request request, @Nullable Response response) {
return null;
}
@Override
protected @Nullable Long requestContentLengthUncompressed(
Request request, @Nullable Response response) {
return null;
}
@Override
protected @Nullable String flavor(Request request, @Nullable Response response) {
String version = (String) request.getAttributes().get("org.restlet.http.version");
switch (version) {
case "HTTP/1.0":
return SemanticAttributes.HttpFlavorValues.HTTP_1_0;
case "HTTP/1.1":
return SemanticAttributes.HttpFlavorValues.HTTP_1_1;
case "HTTP/2.0":
return SemanticAttributes.HttpFlavorValues.HTTP_2_0;
default:
// fall through
}
return null;
}
@Override
protected @Nullable String serverName(Request request, @Nullable Response response) {
return null;
}
@Override
protected Integer statusCode(Request request, Response response) {
return response.getStatus().getCode();
}
@Override
protected @Nullable Long responseContentLength(Request request, Response response) {
return null;
}
@Override
protected @Nullable Long responseContentLengthUncompressed(Request request, Response response) {
return null;
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.restlet.v1_0;
import io.opentelemetry.instrumentation.api.instrumenter.net.NetAttributesExtractor;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.restlet.data.Request;
import org.restlet.data.Response;
final class RestletNetAttributesExtractor extends NetAttributesExtractor<Request, Response> {
@Override
public String transport(Request request) {
return SemanticAttributes.NetTransportValues.IP_TCP;
}
@Override
public @Nullable String peerName(Request request, @Nullable Response response) {
return null;
}
@Override
public Integer peerPort(Request request, @Nullable Response response) {
return request.getClientInfo().getPort();
}
@Override
public @Nullable String peerIp(Request request, @Nullable Response response) {
return request.getClientInfo().getAddress();
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.restlet.v1_0;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import org.restlet.Filter;
import org.restlet.data.Request;
import org.restlet.data.Response;
/** Entrypoint for tracing Restlet servers. */
public final class RestletTracing {
/** Returns a new {@link RestletTracing} configured with the given {@link OpenTelemetry}. */
public static RestletTracing create(OpenTelemetry openTelemetry) {
return newBuilder(openTelemetry).build();
}
/**
* Returns a new {@link RestletTracingBuilder} configured with the given {@link OpenTelemetry}.
*/
public static RestletTracingBuilder newBuilder(OpenTelemetry openTelemetry) {
return new RestletTracingBuilder(openTelemetry);
}
private final Instrumenter<Request, Response> serverInstrumenter;
RestletTracing(Instrumenter<Request, Response> serverInstrumenter) {
this.serverInstrumenter = serverInstrumenter;
}
/**
* Returns a new {@link Filter} which can be used to wrap {@link org.restlet.Restlet}
* implementations.
*/
public Filter newFilter(String path) {
return new TracingFilter(serverInstrumenter, path);
}
/** Returns a server {@link Instrumenter}. */
public Instrumenter<Request, Response> getServerInstrumenter() {
return serverInstrumenter;
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.restlet.v1_0;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.net.NetAttributesExtractor;
import java.util.ArrayList;
import java.util.List;
import org.restlet.data.Request;
import org.restlet.data.Response;
/** A builder of {@link RestletTracing}. */
public final class RestletTracingBuilder {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.restlet-1.0";
private final OpenTelemetry openTelemetry;
private final List<AttributesExtractor<Request, Response>> additionalExtractors =
new ArrayList<>();
RestletTracingBuilder(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
}
/**
* Adds an additional {@link AttributesExtractor} to invoke to set attributes to instrumented
* items.
*/
public RestletTracingBuilder addAttributesExtractor(
AttributesExtractor<Request, Response> attributesExtractor) {
additionalExtractors.add(attributesExtractor);
return this;
}
/**
* Returns a new {@link RestletTracing} with the settings of this {@link RestletTracingBuilder}.
*/
public RestletTracing build() {
HttpAttributesExtractor<Request, Response> httpAttributesExtractor =
new RestletHttpAttributesExtractor();
SpanNameExtractor<Request> spanNameExtractor =
HttpSpanNameExtractor.create(httpAttributesExtractor);
SpanStatusExtractor<Request, Response> spanStatusExtractor =
HttpSpanStatusExtractor.create(httpAttributesExtractor);
NetAttributesExtractor<Request, Response> netAttributesExtractor =
new RestletNetAttributesExtractor();
Instrumenter<Request, Response> instrumenter =
Instrumenter.<Request, Response>newBuilder(
openTelemetry, INSTRUMENTATION_NAME, spanNameExtractor)
.setSpanStatusExtractor(spanStatusExtractor)
.addAttributesExtractor(httpAttributesExtractor)
.addAttributesExtractor(netAttributesExtractor)
.addAttributesExtractors(additionalExtractors)
.newServerInstrumenter(new RestletHeadersGetter());
return new RestletTracing(instrumenter);
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.restlet.v1_0;
import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.CONTROLLER;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming;
import io.opentelemetry.instrumentation.restlet.v1_0.internal.RestletServerSpanNaming;
import org.restlet.Filter;
import org.restlet.data.Request;
import org.restlet.data.Response;
final class TracingFilter extends Filter {
private final Instrumenter<Request, Response> instrumenter;
private final String path;
public TracingFilter(Instrumenter<Request, Response> instrumenter, String path) {
this.instrumenter = instrumenter;
this.path = path;
}
@Override
public int doHandle(Request request, Response response) {
Context parentContext = Context.current();
Context context = parentContext;
Scope scope = null;
if (instrumenter.shouldStart(parentContext, request)) {
context = instrumenter.start(parentContext, request);
scope = context.makeCurrent();
}
ServerSpanNaming.updateServerSpanName(
context, CONTROLLER, RestletServerSpanNaming.SERVER_SPAN_NAME, path);
Throwable statusThrowable = null;
try {
super.doHandle(request, response);
} catch (Throwable t) {
statusThrowable = t;
}
if (scope == null) {
return CONTINUE;
}
scope.close();
if (response.getStatus() != null && response.getStatus().isError()) {
statusThrowable = response.getStatus().getThrowable();
}
instrumenter.end(context, request, response, statusThrowable);
return CONTINUE;
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.restlet.v1_0.internal;
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNameSupplier;
import io.opentelemetry.instrumentation.api.servlet.ServletContextPath;
public final class RestletServerSpanNaming {
public static final ServerSpanNameSupplier<String> SERVER_SPAN_NAME =
(context, pattern) -> {
if (pattern == null || pattern.equals("")) {
return null;
}
return ServletContextPath.prepend(context, pattern);
};
private RestletServerSpanNaming() {}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opententelemetry.instrumentation.restlet.v1_0
import com.noelios.restlet.StatusFilter
import io.opentelemetry.instrumentation.restlet.v1_0.AbstractRestletServerTest
import io.opentelemetry.instrumentation.restlet.v1_0.RestletTracing
import io.opentelemetry.instrumentation.test.LibraryTestTrait
import org.restlet.Restlet
class RestletServerTest extends AbstractRestletServerTest implements LibraryTestTrait {
@Override
Restlet wrapRestlet(Restlet restlet, String path){
RestletTracing tracing = RestletTracing.create(openTelemetry)
def tracingFilter = tracing.newFilter(path)
def statusFilter = new StatusFilter(component.getContext(), false, null, null)
tracingFilter.setNext(statusFilter)
statusFilter.setNext(restlet)
return tracingFilter
}
}

View File

@ -0,0 +1,20 @@
plugins {
id("otel.java-conventions")
}
repositories {
mavenCentral()
maven("https://maven.restlet.talend.com/")
mavenLocal()
}
dependencies {
api(project(":testing-common"))
implementation("org.restlet:org.restlet:1.1.5")
implementation("com.noelios.restlet:com.noelios.restlet:1.1.5")
implementation("org.codehaus.groovy:groovy-all")
implementation("io.opentelemetry:opentelemetry-api")
implementation("org.spockframework:spock-core")
}

View File

@ -0,0 +1,172 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.restlet.v1_0
import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.instrumentation.test.base.HttpServerTest
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
import org.restlet.Component
import org.restlet.Context
import org.restlet.Redirector
import org.restlet.Restlet
import org.restlet.Server
import org.restlet.VirtualHost
import org.restlet.data.MediaType
import org.restlet.data.Protocol
import org.restlet.data.Request
import org.restlet.data.Response
import org.restlet.data.Status
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.ERROR
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.INDEXED_CHILD
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS
abstract class AbstractRestletServerTest extends HttpServerTest<Server> {
Component component
VirtualHost host
@Override
Server startServer(int port) {
component = new Component()
def server = component.getServers().add(Protocol.HTTP, port)
host = component.getDefaultHost()
attachRestlets()
component.start()
return server
}
@Override
void stopServer(Server server) {
component.stop()
}
def attachAndWrap(path, restlet){
host.attach(path, wrapRestlet(restlet, path))
}
def attachRestlets(){
attachAndWrap(SUCCESS.path, new Restlet() {
@Override
void handle(Request request, Response response) {
controller(SUCCESS) {
response.setEntity(SUCCESS.body, MediaType.TEXT_PLAIN)
response.setStatus(Status.valueOf(SUCCESS.status), SUCCESS.body)
}
}
})
attachAndWrap(REDIRECT.path, new Redirector(Context.getCurrent(), REDIRECT.body, Redirector.MODE_CLIENT_FOUND) {
@Override
void handle(Request request, Response response){
super.handle(request, response)
controller(REDIRECT){
} //TODO: check why handle fails inside controller
}
})
attachAndWrap(ERROR.path, new Restlet(){
@Override
void handle(Request request, Response response){
controller(ERROR){
response.setStatus(Status.valueOf(ERROR.getStatus()), ERROR.getBody())
}
}
})
attachAndWrap(EXCEPTION.path, new Restlet(){
@Override
void handle(Request request, Response response){
controller(EXCEPTION){
throw new Exception(EXCEPTION.getBody())
}
}
})
attachAndWrap(QUERY_PARAM.path, new Restlet() {
@Override
void handle(Request request, Response response){
controller(QUERY_PARAM){
response.setEntity(QUERY_PARAM.getBody(), MediaType.TEXT_PLAIN)
response.setStatus(Status.valueOf(QUERY_PARAM.getStatus()), QUERY_PARAM.getBody())
}
}
})
attachAndWrap(NOT_FOUND.path, new Restlet() {
@Override
void handle(Request request, Response response){
controller(NOT_FOUND){
response.setEntity(NOT_FOUND.getBody(), MediaType.TEXT_PLAIN)
response.setStatus(Status.valueOf(NOT_FOUND.getStatus()), NOT_FOUND.getBody())
}
}
})
attachAndWrap("/path/{id}/param", new Restlet(){
@Override
void handle(Request request, Response response) {
controller(PATH_PARAM) {
response.setEntity(PATH_PARAM.getBody(), MediaType.TEXT_PLAIN)
response.setStatus(Status.valueOf(PATH_PARAM.getStatus()), PATH_PARAM.getBody())
}
}
})
attachAndWrap(INDEXED_CHILD.path, new Restlet() {
@Override
void handle(Request request, Response response) {
controller(INDEXED_CHILD) {
INDEXED_CHILD.collectSpanAttributes {request.getOriginalRef().getQueryAsForm().getFirst(it).getValue() }
response.setStatus(Status.valueOf(INDEXED_CHILD.status))
}
}
})
}
@Override
List<AttributeKey<?>> extraAttributes() {
[
SemanticAttributes.HTTP_TARGET,
SemanticAttributes.HTTP_SCHEME,
SemanticAttributes.NET_TRANSPORT,
]
}
@Override
boolean testPathParam() {
true
}
@Override
boolean testConcurrency() {
true
}
@Override
String expectedServerSpanName(ServerEndpoint endpoint) {
switch (endpoint) {
case PATH_PARAM:
return getContextPath() + "/path/{id}/param"
case NOT_FOUND:
return getContextPath() + "/notFound"
default:
return endpoint.resolvePath(address).path
}
}
abstract Restlet wrapRestlet(Restlet restlet, String path)
}

View File

@ -279,6 +279,9 @@ include(":instrumentation:reactor-netty:reactor-netty-0.9:javaagent")
include(":instrumentation:reactor-netty:reactor-netty-1.0:javaagent")
include(":instrumentation:rediscala-1.8:javaagent")
include(":instrumentation:redisson-3.0:javaagent")
include(":instrumentation:restlet:restlet-1.0:javaagent")
include(":instrumentation:restlet:restlet-1.0:library")
include(":instrumentation:restlet:restlet-1.0:testing")
include(":instrumentation:rmi:bootstrap")
include(":instrumentation:rmi:javaagent")
include(":instrumentation:rocketmq-client-4.8:javaagent")