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:
parent
1c3c3795dc
commit
473f16fa68
|
@ -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"))
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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"))
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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)
|
||||
|
||||
}
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue