Skip to content

Commit

Permalink
Restlet instrumentation (#3946)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
anosek-an authored Sep 14, 2021
1 parent 1c3c379 commit 473f16f
Show file tree
Hide file tree
Showing 18 changed files with 850 additions and 0 deletions.
28 changes: 28 additions & 0 deletions instrumentation/restlet/restlet-1.0/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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"))
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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() {}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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
}

}
20 changes: 20 additions & 0 deletions instrumentation/restlet/restlet-1.0/library/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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"))
}
Original file line number Diff line number Diff line change
@@ -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");
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit 473f16f

Please sign in to comment.