Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename ServerSpanNaming to HttpRouteHolder #5211

Merged
merged 2 commits into from
Jan 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.http;

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

/** An interface for getting the {@code http.route} attribute. */
@FunctionalInterface
public interface HttpRouteBiGetter<T, U> {

/**
* Returns the {@code http.route} attribute extracted from {@code context}, {@code arg1} and
* {@code arg2}; or {@code null} if it was not found.
*/
@Nullable
String get(Context context, T arg1, U arg2);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.http;

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

/** An interface for getting the {@code http.route} attribute. */
@FunctionalInterface
public interface HttpRouteGetter<T> {

/**
* Returns the {@code http.route} attribute extracted from {@code context} and {@code arg}; or
* {@code null} if it was not found.
*/
@Nullable
String get(Context context, T arg);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.http;

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.server.ServerSpan;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import javax.annotation.Nullable;

/**
* A helper class that keeps track of the {@code http.route} attribute value during HTTP server
* request processing.
*
* <p>Usually the route is not accessible when the request processing starts; and needs to be set
* later, after the instrumented operation starts. This class provides several static methods that
* allow the isntrumentation author to provide the matching HTTP route to the instrumentation when
* it is discovered.
*/
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) {
return context;
}
return context.with(CONTEXT_KEY, new HttpRouteHolder());
};
}

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

private HttpRouteHolder() {}

/**
* Updates the {@code http.route} attribute in the received {@code context}.
*
* <p>If there is a server span in the context, and the context has been customized with a {@link
* HttpRouteHolder}, then this method will update the route using the provided {@link
* HttpRouteGetter} if and only if the last {@link HttpRouteSource} to update the route using this
* method has strictly lower priority than the provided {@link HttpRouteSource}, and the value
* returned from the {@link HttpRouteGetter} is non-null.
*
* <p>If there is a server span in the context, and the context has NOT been customized with a
* {@link HttpRouteHolder}, then this method will update the route using the provided {@link
* HttpRouteGetter} if the value returned from it is non-null.
*/
public static <T> void updateHttpRoute(
Context context, HttpRouteSource source, HttpRouteGetter<T> httpRouteGetter, T arg1) {
updateHttpRoute(context, source, OneArgAdapter.getInstance(), arg1, httpRouteGetter);
}

/**
* Updates the {@code http.route} attribute in the received {@code context}.
*
* <p>If there is a server span in the context, and the context has been customized with a {@link
* HttpRouteHolder}, then this method will update the route using the provided {@link
* HttpRouteBiGetter} if and only if the last {@link HttpRouteSource} to update the route using
* this method has strictly lower priority than the provided {@link HttpRouteSource}, and the
* value returned from the {@link HttpRouteBiGetter} is non-null.
*
* <p>If there is a server span in the context, and the context has NOT been customized with a
* {@code ServerSpanName}, then this method will update the route using the provided {@link
* HttpRouteBiGetter} if the value returned from it is non-null.
*/
public static <T, U> void updateHttpRoute(
Context context,
HttpRouteSource source,
HttpRouteBiGetter<T, U> httpRouteGetter,
T arg1,
U arg2) {
Span serverSpan = ServerSpan.fromContextOrNull(context);
// checking isRecording() is a helpful optimization for more expensive suppliers
// (e.g. Spring MVC instrumentation's HandlerAdapterInstrumentation)
if (serverSpan == null || !serverSpan.isRecording()) {
return;
}
HttpRouteHolder httpRouteHolder = context.get(CONTEXT_KEY);
if (httpRouteHolder == null) {
String httpRoute = httpRouteGetter.get(context, arg1, arg2);
if (httpRoute != null && !httpRoute.isEmpty()) {
updateSpanData(serverSpan, httpRoute);
}
return;
}
// 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) {
String route = httpRouteGetter.get(context, arg1, arg2);
if (route != null
&& !route.isEmpty()
&& (!onlyIfBetterRoute || httpRouteHolder.isBetterRoute(route))) {
updateSpanData(serverSpan, route);
httpRouteHolder.updatedBySourceOrder = source.order;
httpRouteHolder.route = route;
}
}
}

// TODO: instead of calling setAttribute() consider storing the route in context end retrieving it
// in the AttributesExtractor
private static void updateSpanData(Span serverSpan, String route) {
serverSpan.updateName(route);
serverSpan.setAttribute(SemanticAttributes.HTTP_ROUTE, 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;
int routeLength = route == null ? 0 : route.length();
return name.length() > routeLength;
}

// TODO: use that in HttpServerMetrics
/**
* Returns the {@code http.route} attribute value that's stored in the passed {@code context}, or
* null if it was not set before.
*/
@Nullable
public static String getRoute(Context context) {
HttpRouteHolder httpRouteHolder = context.get(CONTEXT_KEY);
return httpRouteHolder == null ? null : httpRouteHolder.route;
}

private static final class OneArgAdapter<T> implements HttpRouteBiGetter<T, HttpRouteGetter<T>> {

private static final OneArgAdapter<Object> INSTANCE = new OneArgAdapter<>();

@SuppressWarnings("unchecked")
static <T> OneArgAdapter<T> getInstance() {
return (OneArgAdapter<T>) INSTANCE;
}

@Override
@Nullable
public String get(Context context, T arg, HttpRouteGetter<T> httpRouteGetter) {
return httpRouteGetter.get(context, arg);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.http;

/** Represents the source that provided the {@code http.route} attribute. */
public enum HttpRouteSource {
// for servlet filters we try to find the best name which isn't necessarily from the first
// filter that is called
FILTER(1, /* useFirst= */ false),
SERVLET(2),
CONTROLLER(3),
// Some frameworks, e.g. JaxRS, allow for nested controller/paths and we want to select the
// longest one
NESTED_CONTROLLER(4, false);

final int order;
final boolean useFirst;

HttpRouteSource(int order) {
this(order, /* useFirst= */ true);
}

HttpRouteSource(int order, boolean useFirst) {
this.order = order;
this.useFirst = useFirst;
}
}

This file was deleted.

This file was deleted.

Loading