Skip to content

Commit

Permalink
Add javaagent<->application context bridge for HttpRouteHolder (#5838)
Browse files Browse the repository at this point in the history
* Add javaagent<->application context bridge for HttpRouteHolder

* remove comments

* fix broken http.route bridge

* spotless

* Move to a separate module
  • Loading branch information
Mateusz Rzeszutek committed Apr 22, 2022
1 parent 2882572 commit 2bb7873
Show file tree
Hide file tree
Showing 21 changed files with 942 additions and 474 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan;
import io.opentelemetry.instrumentation.api.internal.HttpRouteState;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import javax.annotation.Nullable;

Expand All @@ -25,25 +25,19 @@
*/
public final class HttpRouteHolder {

private static final ContextKey<HttpRouteHolder> CONTEXT_KEY =
ContextKey.named("opentelemetry-http-server-route-key");

/**
* Returns a {@link ContextCustomizer} that initializes a {@link HttpRouteHolder} in the {@link
* Context} returned from {@link Instrumenter#start(Context, Object)}.
*/
public static <REQUEST> ContextCustomizer<REQUEST> get() {
return (context, request, startAttributes) -> {
if (context.get(CONTEXT_KEY) != null) {
if (HttpRouteState.fromContextOrNull(context) != null) {
return context;
}
return context.with(CONTEXT_KEY, new HttpRouteHolder());
return context.with(HttpRouteState.create(0, null));
};
}

private volatile int updatedBySourceOrder = 0;
@Nullable private volatile String route;

private HttpRouteHolder() {}

/**
Expand Down Expand Up @@ -107,8 +101,8 @@ public static <T, U> void updateHttpRoute(
if (serverSpan == null) {
return;
}
HttpRouteHolder httpRouteHolder = context.get(CONTEXT_KEY);
if (httpRouteHolder == null) {
HttpRouteState httpRouteState = HttpRouteState.fromContextOrNull(context);
if (httpRouteState == null) {
String httpRoute = httpRouteGetter.get(context, arg1, arg2);
if (httpRoute != null && !httpRoute.isEmpty()) {
// update both span name and attribute, since there's no HttpRouteHolder in the context
Expand All @@ -120,27 +114,26 @@ public static <T, U> void updateHttpRoute(
// special case for servlet filters, even when we have a route from previous filter see whether
// the new route is better and if so use it instead
boolean onlyIfBetterRoute =
!source.useFirst && source.order == httpRouteHolder.updatedBySourceOrder;
if (source.order > httpRouteHolder.updatedBySourceOrder || onlyIfBetterRoute) {
!source.useFirst && source.order == httpRouteState.getUpdatedBySourceOrder();
if (source.order > httpRouteState.getUpdatedBySourceOrder() || onlyIfBetterRoute) {
String route = httpRouteGetter.get(context, arg1, arg2);
if (route != null
&& !route.isEmpty()
&& (!onlyIfBetterRoute || httpRouteHolder.isBetterRoute(route))) {
&& (!onlyIfBetterRoute || isBetterRoute(httpRouteState, route))) {

// update just the span name - the attribute will be picked up by the
// HttpServerAttributesExtractor at the end of request processing
serverSpan.updateName(route);

httpRouteHolder.updatedBySourceOrder = source.order;
httpRouteHolder.route = route;
httpRouteState.update(context, source.order, route);
}
}
}

// This is used when setting route from a servlet filter to pick the most descriptive (longest)
// route.
private boolean isBetterRoute(String name) {
String route = this.route;
private static boolean isBetterRoute(HttpRouteState httpRouteState, String name) {
String route = httpRouteState.getRoute();
int routeLength = route == null ? 0 : route.length();
return name.length() > routeLength;
}
Expand All @@ -151,8 +144,8 @@ private boolean isBetterRoute(String name) {
*/
@Nullable
static String getRoute(Context context) {
HttpRouteHolder httpRouteHolder = context.get(CONTEXT_KEY);
return httpRouteHolder == null ? null : httpRouteHolder.route;
HttpRouteState httpRouteState = HttpRouteState.fromContextOrNull(context);
return httpRouteState == null ? null : httpRouteState.getRoute();
}

private static final class OneArgAdapter<T> implements HttpRouteBiGetter<T, HttpRouteGetter<T>> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.internal;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.context.ImplicitContextKeyed;
import javax.annotation.Nullable;

/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public final class HttpRouteState implements ImplicitContextKeyed {

private static final ContextKey<HttpRouteState> KEY =
ContextKey.named("opentelemetry-http-server-route-key");

@Nullable
public static HttpRouteState fromContextOrNull(Context context) {
return context.get(KEY);
}

public static HttpRouteState create(int updatedBySourceOrder, @Nullable String route) {
return new HttpRouteState(updatedBySourceOrder, route);
}

private volatile int updatedBySourceOrder;
@Nullable private volatile String route;

private HttpRouteState(int updatedBySourceOrder, @Nullable String route) {
this.updatedBySourceOrder = updatedBySourceOrder;
this.route = route;
}

@Override
public Context storeInContext(Context context) {
return context.with(KEY, this);
}

public int getUpdatedBySourceOrder() {
return updatedBySourceOrder;
}

@Nullable
public String getRoute() {
return route;
}

public void update(
@SuppressWarnings("unused")
Context context, // context is used by the javaagent bridge instrumentation
int updatedBySourceOrder,
String route) {
this.updatedBySourceOrder = updatedBySourceOrder;
this.route = route;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,39 +42,4 @@ dependencies {
// @WithSpan annotation is used to generate spans in ContextBridgeTest
testImplementation("io.opentelemetry:opentelemetry-extension-annotations")
testInstrumentation(project(":instrumentation:opentelemetry-annotations-1.0:javaagent"))

testImplementation(project(":instrumentation:opentelemetry-api:opentelemetry-api-1.0:testing"))
testInstrumentation(project(":instrumentation:opentelemetry-api:opentelemetry-api-1.0:testing"))
}

testing {
suites {
val testOldInstrumentationApi by registering(JvmTestSuite::class) {
dependencies {
implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-semconv")
implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api")
implementation(project(":instrumentation:opentelemetry-api:opentelemetry-api-1.0:testing"))
}
}
}
}

configurations.configureEach {
if (name.contains("testOldInstrumentationApi")) {
resolutionStrategy {
dependencySubstitution {
// version 1.13.0 contains the old ServerSpan implementation that uses SERVER_KEY context key
substitute(module("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-semconv"))
.using(module("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-semconv:1.13.0-alpha"))
substitute(module("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api"))
.using(module("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:1.13.0-alpha"))
}
}
}
}

tasks {
check {
dependsOn(testing.suites)
}
}
Loading

0 comments on commit 2bb7873

Please sign in to comment.