Skip to content

Commit

Permalink
Restlet 2.0 instrumentation (#4535)
Browse files Browse the repository at this point in the history
* add restlet 2.0 instrumentation

* add restlet 2.0 instrumentation

* revies: testLibrary, create RestletInstrumenterFactory
  • Loading branch information
anosek-an authored Nov 9, 2021
1 parent 21d6648 commit 8b7c097
Show file tree
Hide file tree
Showing 19 changed files with 973 additions and 0 deletions.
42 changes: 42 additions & 0 deletions instrumentation/restlet/restlet-2.0/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("org.restlet")
module.set("org.restlet.jse")
versions.set("[2.0.0,)")
assertInverse.set(true)
}
}

repositories {
mavenCentral()
maven("https://maven.restlet.talend.com/")
mavenLocal()
}

dependencies {
api(project(":instrumentation:restlet:restlet-2.0:library"))

library("org.restlet.jse:org.restlet:2.0.2")

implementation(project(":instrumentation:restlet:restlet-2.0:library"))

testImplementation(project(":instrumentation:restlet:restlet-2.0:testing"))
testLibrary("org.restlet.jse:org.restlet.ext.jetty:2.0.2")
}

// restlet registers the first engine that is present on classpath, so we need to enforce the appropriate version
if (findProperty("testLatestDeps") as Boolean) {
configurations.configureEach {
resolutionStrategy {
eachDependency {
if (requested.group == "org.restlet.jse") {
useVersion("2.+")
}
}
}
}
}
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.v2_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-2.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.v2_0;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.restlet.v2_0.internal.RestletInstrumenterFactory;
import org.restlet.Request;
import org.restlet.Response;

public final class RestletSingletons {

private static final Instrumenter<Request, Response> INSTRUMENTER =
RestletInstrumenterFactory.newServerInstrumenter(GlobalOpenTelemetry.get());

public static Instrumenter<Request, Response> instrumenter() {
return INSTRUMENTER;
}

private RestletSingletons() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.restlet.v2_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.v2_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.Request;
import org.restlet.routing.TemplateRoute;

public class RouteInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.restlet.routing.TemplateRoute").or(named("org.restlet.routing.Route"));
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod()
.and(named("beforeHandle"))
.and(takesArgument(0, named("org.restlet.Request")))
.and(takesArgument(1, named("org.restlet.Response"))),
this.getClass().getName() + "$RouteBeforeHandleAdvice");
}

@SuppressWarnings("unused")
public static class RouteBeforeHandleAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void getRouteInfo(
@Advice.This TemplateRoute 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,94 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.restlet.v2_0;

import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.CONTROLLER;
import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.instrumentation.restlet.v2_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.instrumentation.api.servlet.ServerSpanNaming;
import io.opentelemetry.instrumentation.restlet.v2_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.Request;
import org.restlet.Response;
import org.restlet.data.Status;

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.Request")))
.and(takesArgument(1, named("org.restlet.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 (Status.CLIENT_ERROR_NOT_FOUND.equals(response.getStatus())) {
ServerSpanNaming.updateServerSpanName(
context, CONTROLLER, RestletServerSpanNaming.SERVER_SPAN_NAME, "/*");
}

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,13 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.restlet.v2_0

import io.opentelemetry.instrumentation.restlet.v2_0.AbstractRestletServerTest
import io.opentelemetry.instrumentation.test.AgentTestTrait

class RestletServerTest extends AbstractRestletServerTest implements AgentTestTrait {

}
29 changes: 29 additions & 0 deletions instrumentation/restlet/restlet-2.0/library/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
plugins {
id("otel.library-instrumentation")
}

repositories {
mavenCentral()
maven("https://maven.restlet.talend.com/")
mavenLocal()
}

dependencies {

library("org.restlet.jse:org.restlet:2.0.2")

testImplementation(project(":instrumentation:restlet:restlet-2.0:testing"))
testLibrary("org.restlet.jse:org.restlet.ext.jetty:2.0.2")
}
// restlet registers the first engine that is present on classpath, so we need to enforce the appropriate version
if (findProperty("testLatestDeps") as Boolean) {
configurations.configureEach {
resolutionStrategy {
eachDependency {
if (requested.group == "org.restlet.jse") {
useVersion("2.+")
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.restlet.v2_0;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.routing.Filter;

/** 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 builder(openTelemetry).build();
}

/**
* Returns a new {@link RestletTracingBuilder} configured with the given {@link OpenTelemetry}.
*/
public static RestletTracingBuilder builder(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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.restlet.v2_0;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeaders;
import io.opentelemetry.instrumentation.restlet.v2_0.internal.RestletInstrumenterFactory;
import java.util.ArrayList;
import java.util.List;
import org.restlet.Request;
import org.restlet.Response;

/** A builder of {@link RestletTracing}. */
public final class RestletTracingBuilder {

private final OpenTelemetry openTelemetry;
private final List<AttributesExtractor<Request, Response>> additionalExtractors =
new ArrayList<>();
private CapturedHttpHeaders capturedHttpHeaders = CapturedHttpHeaders.server(Config.get());

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;
}

/**
* Configure the instrumentation to capture chosen HTTP request and response headers as span
* attributes.
*
* @param capturedHttpHeaders An instance of {@link CapturedHttpHeaders} containing the configured
* HTTP request and response names.
*/
public RestletTracingBuilder captureHttpHeaders(CapturedHttpHeaders capturedHttpHeaders) {
this.capturedHttpHeaders = capturedHttpHeaders;
return this;
}

/**
* Returns a new {@link RestletTracing} with the settings of this {@link RestletTracingBuilder}.
*/
public RestletTracing build() {

Instrumenter<Request, Response> serverInstrumenter =
RestletInstrumenterFactory.newServerInstrumenter(
openTelemetry, capturedHttpHeaders, additionalExtractors);

return new RestletTracing(serverInstrumenter);
}
}
Loading

0 comments on commit 8b7c097

Please sign in to comment.