Skip to content

Commit

Permalink
use semantic convention APIs for opentelemetry
Browse files Browse the repository at this point in the history
Motivation:

Today the tags are manually handcrafted with strings, if the opentelemetry api changes, the tags will be outdated, using the instrumenter available from the io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-semconv we can make it all dynamic based on the specification without understanding the specification and its tags.

Result:

API will become more stable and evolve easier together with opentelemetry, no more handcrafted strings and the conventions will be driven by the instrumentation API.
  • Loading branch information
aleqi200 committed Aug 1, 2023
1 parent 67308bd commit 192afd3
Show file tree
Hide file tree
Showing 16 changed files with 834 additions and 205 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ jacksonVersion=2.14.3
openTracingVersion=0.33.0
zipkinReporterVersion=2.16.4
opentelemetryVersion=1.28.0
opentelemetryApiVersion=1.28.0-alpha

# gRPC
protobufGradlePluginVersion=0.9.4
Expand All @@ -73,7 +74,6 @@ assertJCoreVersion=3.24.2
hamcrestVersion=2.2
mockitoCoreVersion=4.11.0
spotbugsPluginVersion=5.0.13
opentelemetryInstrumentationVersion=1.9.2-alpha

apacheDirectoryServerVersion=1.5.7
commonsLangVersion=2.6
Expand Down
4 changes: 3 additions & 1 deletion servicetalk-opentelemetry-http/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dependencies {

api project(":servicetalk-http-api")
api "io.opentelemetry:opentelemetry-api:$opentelemetryVersion"
implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-semconv:$opentelemetryApiVersion")

implementation project(":servicetalk-annotations")
implementation project(":servicetalk-http-utils")
Expand All @@ -36,7 +37,8 @@ dependencies {
testImplementation project(":servicetalk-http-netty")
testImplementation project(":servicetalk-test-resources")
testImplementation "io.opentelemetry:opentelemetry-sdk-testing:$opentelemetryVersion"
testImplementation "io.opentelemetry.instrumentation:opentelemetry-log4j-2.13.2:$opentelemetryInstrumentationVersion"
testRuntimeOnly("io.opentelemetry.instrumentation:opentelemetry-log4j-context-data-2.17-autoconfigure:" +
"$opentelemetryApiVersion")
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.assertj:assertj-core:$assertJCoreVersion"
testImplementation "org.mockito:mockito-core:$mockitoCoreVersion"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,31 @@
import io.servicetalk.concurrent.api.Single;
import io.servicetalk.http.api.FilterableStreamingHttpClient;
import io.servicetalk.http.api.FilterableStreamingHttpConnection;
import io.servicetalk.http.api.HttpRequestMetaData;
import io.servicetalk.http.api.HttpResponseMetaData;
import io.servicetalk.http.api.StreamingHttpClientFilter;
import io.servicetalk.http.api.StreamingHttpClientFilterFactory;
import io.servicetalk.http.api.StreamingHttpConnectionFilter;
import io.servicetalk.http.api.StreamingHttpConnectionFilterFactory;
import io.servicetalk.http.api.StreamingHttpRequest;
import io.servicetalk.http.api.StreamingHttpRequester;
import io.servicetalk.http.api.StreamingHttpResponse;
import io.servicetalk.transport.api.HostAndPort;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesExtractor;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;

import java.util.function.UnaryOperator;

Expand All @@ -54,17 +63,42 @@
public final class OpenTelemetryHttpRequestFilter extends AbstractOpenTelemetryFilter
implements StreamingHttpClientFilterFactory, StreamingHttpConnectionFilterFactory {

private final String componentName;
private final Instrumenter<HttpRequestMetaData, HttpResponseMetaData> instrumenter;

/**
* Create a new instance.
*
* @param openTelemetry the {@link OpenTelemetry}.
* @param componentName The component name used during building new spans.
* @param openTelemetry the {@link OpenTelemetry}.
* @param componentName The component name used during building new spans.
* @param opentelemetryOptions extra options to create the opentelemetry filter.
*/
public OpenTelemetryHttpRequestFilter(final OpenTelemetry openTelemetry, String componentName) {
public OpenTelemetryHttpRequestFilter(final OpenTelemetry openTelemetry, String componentName,
OpentelemetryOptions opentelemetryOptions) {
super(openTelemetry);
this.componentName = componentName.trim();
SpanNameExtractor<HttpRequestMetaData> serverSpanNameExtractor =
HttpSpanNameExtractor.create(ServicetalkHttpClientCommonAttributesGetter.INSTANCE);
InstrumenterBuilder<HttpRequestMetaData, HttpResponseMetaData> clientInstrumenterBuilder =
Instrumenter.builder(openTelemetry, INSTRUMENTATION_SCOPE_NAME, serverSpanNameExtractor);
clientInstrumenterBuilder.setSpanStatusExtractor(ServicetalkSpanStatusExtractor.INSTANCE);

clientInstrumenterBuilder
.addAttributesExtractor(HttpClientAttributesExtractor
.builder(ServicetalkHttpClientCommonAttributesGetter.INSTANCE,
ServicetalkNetClientAttributesGetter.INSTANCE)
.setCapturedRequestHeaders(opentelemetryOptions.getCaptureRequestHeaders())
.setCapturedResponseHeaders(opentelemetryOptions.getCaptureResponseHeaders())
.build())
.addAttributesExtractor(
NetClientAttributesExtractor.create(ServicetalkNetClientAttributesGetter.INSTANCE));
if (opentelemetryOptions.isEnableMetrics()) {
clientInstrumenterBuilder.addOperationMetrics(HttpClientMetrics.get());
}
if (!componentName.trim().isEmpty()) {
clientInstrumenterBuilder.addAttributesExtractor(
AttributesExtractor.constant(SemanticAttributes.PEER_SERVICE, componentName));
}
instrumenter =
clientInstrumenterBuilder.buildClientInstrumenter(RequestHeadersPropagatorSetter.INSTANCE);
}

/**
Expand All @@ -73,15 +107,35 @@ public OpenTelemetryHttpRequestFilter(final OpenTelemetry openTelemetry, String
* @param componentName The component name used during building new spans.
*/
public OpenTelemetryHttpRequestFilter(String componentName) {
this(GlobalOpenTelemetry.get(), componentName);
this(GlobalOpenTelemetry.get(), componentName, OpentelemetryOptions.newBuilder().build());
}

/**
* Create a new instance, searching for any instance of an opentelemetry available.
*
* @param openTelemetry the {@link OpenTelemetry}.
* @param componentName The component name used during building new spans.
*/
public OpenTelemetryHttpRequestFilter(final OpenTelemetry openTelemetry, String componentName) {
this(openTelemetry, componentName, OpentelemetryOptions.newBuilder().build());
}

/**
* Create a new instance, searching for any instance of an opentelemetry available.
*
* @param componentName The component name used during building new spans.
* @param opentelemetryOptions extra options to create the opentelemetry filter
*/
public OpenTelemetryHttpRequestFilter(String componentName, OpentelemetryOptions opentelemetryOptions) {
this(GlobalOpenTelemetry.get(), componentName, opentelemetryOptions);
}

/**
* Create a new instance, searching for any instance of an opentelemetry available,
* using the hostname as the component name.
*/
public OpenTelemetryHttpRequestFilter() {
this(GlobalOpenTelemetry.get(), "");
this(GlobalOpenTelemetry.get(), "", OpentelemetryOptions.newBuilder().build());
}

@Override
Expand All @@ -108,34 +162,18 @@ public Single<StreamingHttpResponse> request(final StreamingHttpRequest request)

private Single<StreamingHttpResponse> trackRequest(final StreamingHttpRequester delegate,
final StreamingHttpRequest request) {
Context context = Context.current();
final Span span = RequestTagExtractor.reportTagsAndStart(tracer
.spanBuilder(getSpanName(request))
.setParent(context)
.setSpanKind(SpanKind.CLIENT), request);

final Scope scope = span.makeCurrent();
final ScopeTracker tracker = new ScopeTracker(scope, span);
final Context parentContext = Context.current();
Context context = instrumenter.start(parentContext, request);

final Scope scope = context.makeCurrent();
final ScopeTracker tracker = new ScopeTracker(scope, context, request, instrumenter);
Single<StreamingHttpResponse> response;
try {
propagators.getTextMapPropagator().inject(Context.current(), request.headers(),
HeadersPropagatorSetter.INSTANCE);
response = delegate.request(request);
} catch (Throwable t) {
tracker.onError(t);
return Single.failed(t);
}
return tracker.track(response);
}

private String getSpanName(StreamingHttpRequest request) {
if (!componentName.isEmpty()) {
return componentName;
}
HostAndPort hostAndPort = request.effectiveHostAndPort();
if (hostAndPort != null) {
return hostAndPort.hostName();
}
return request.requestTarget();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.servicetalk.concurrent.api.Publisher;
import io.servicetalk.concurrent.api.Single;
import io.servicetalk.http.api.HttpRequestMetaData;
import io.servicetalk.http.api.HttpResponseMetaData;
import io.servicetalk.http.api.HttpServiceContext;
import io.servicetalk.http.api.StreamingHttpRequest;
import io.servicetalk.http.api.StreamingHttpResponse;
Expand All @@ -30,10 +31,16 @@
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.net.NetServerAttributesExtractor;

import java.util.function.UnaryOperator;

Expand All @@ -52,21 +59,62 @@
*/
public final class OpenTelemetryHttpServerFilter extends AbstractOpenTelemetryFilter
implements StreamingHttpServiceFilterFactory {
private final Instrumenter<HttpRequestMetaData, HttpResponseMetaData> instrumenter;

/**
* Create a new instance.
*
* @param openTelemetry the {@link OpenTelemetry}.
* @param openTelemetry the {@link OpenTelemetry}.
* @param opentelemetryOptions extra options to create the opentelemetry filter.
*/
public OpenTelemetryHttpServerFilter(final OpenTelemetry openTelemetry) {
public OpenTelemetryHttpServerFilter(final OpenTelemetry openTelemetry, OpentelemetryOptions opentelemetryOptions) {
super(openTelemetry);
SpanNameExtractor<HttpRequestMetaData> serverSpanNameExtractor =
HttpSpanNameExtractor.create(ServicetalkHttpServerCommonAttributesGetter.INSTANCE);
InstrumenterBuilder<HttpRequestMetaData, HttpResponseMetaData> serverInstrumenterBuilder =
Instrumenter.builder(openTelemetry, INSTRUMENTATION_SCOPE_NAME, serverSpanNameExtractor);
serverInstrumenterBuilder.setSpanStatusExtractor(ServicetalkSpanStatusExtractor.INSTANCE);

serverInstrumenterBuilder
.addAttributesExtractor(HttpServerAttributesExtractor
.builder(ServicetalkHttpServerCommonAttributesGetter.INSTANCE,
ServicetalkNetServerAttributesGetter.INSTANCE)
.setCapturedRequestHeaders(opentelemetryOptions.getCaptureRequestHeaders())
.setCapturedResponseHeaders(opentelemetryOptions.getCaptureResponseHeaders())
.build())
.addAttributesExtractor(
NetServerAttributesExtractor.create(ServicetalkNetServerAttributesGetter.INSTANCE));
if (opentelemetryOptions.isEnableMetrics()) {
serverInstrumenterBuilder.addOperationMetrics(HttpServerMetrics.get());
}

instrumenter =
serverInstrumenterBuilder.buildServerInstrumenter(RequestHeadersPropagatorGetter.INSTANCE);
}

/**
* Create a new Instance, searching for any instance of an opentelemetry available.
*/
public OpenTelemetryHttpServerFilter() {
this(GlobalOpenTelemetry.get());
this(GlobalOpenTelemetry.get(), OpentelemetryOptions.newBuilder().build());
}

/**
* Create a new instance.
*
* @param opentelemetryOptions extra options to create the opentelemetry filter
*/
public OpenTelemetryHttpServerFilter(OpentelemetryOptions opentelemetryOptions) {
this(GlobalOpenTelemetry.get(), opentelemetryOptions);
}

/**
* Create a new instance.
*
* @param openTelemetry the {@link OpenTelemetry}.
*/
public OpenTelemetryHttpServerFilter(final OpenTelemetry openTelemetry) {
this(openTelemetry, OpentelemetryOptions.newBuilder().build());
}

@Override
Expand All @@ -85,26 +133,15 @@ private Single<StreamingHttpResponse> trackRequest(final StreamingHttpService de
final HttpServiceContext ctx,
final StreamingHttpRequest request,
final StreamingHttpResponseFactory responseFactory) {
final Context context = Context.root();
io.opentelemetry.context.Context tracingContext =
propagators.getTextMapPropagator().extract(context, request.headers(), HeadersPropagatorGetter.INSTANCE);

final Span span = RequestTagExtractor.reportTagsAndStart(tracer
.spanBuilder(getOperationName(request))
.setParent(tracingContext)
.setSpanKind(SpanKind.SERVER), request);
final Context parentContext = Context.current();
if (!instrumenter.shouldStart(parentContext, request)) {
return delegate.handle(ctx, request, responseFactory);
}
Context context = instrumenter.start(parentContext, request);

final Scope scope = span.makeCurrent();
final ScopeTracker tracker = new ScopeTracker(scope, span) {
@Override
protected void tagStatusCode() {
super.tagStatusCode();
if (metaData != null) {
propagators.getTextMapPropagator().inject(Context.current(), metaData.headers(),
HeadersPropagatorSetter.INSTANCE);
}
}
};
final Scope scope = context.makeCurrent();
final ScopeTracker tracker = new ScopeTracker(scope, context, request, instrumenter);
Single<StreamingHttpResponse> response;
try {
response = delegate.handle(ctx, request, responseFactory);
Expand All @@ -114,14 +151,4 @@ protected void tagStatusCode() {
}
return tracker.track(response);
}

/**
* Get the operation name to build the span with.
*
* @param metaData The {@link HttpRequestMetaData}.
* @return the operation name to build the span with.
*/
private static String getOperationName(HttpRequestMetaData metaData) {
return metaData.method().name() + ' ' + metaData.path();
}
}
Loading

0 comments on commit 192afd3

Please sign in to comment.