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

Split ArmeriaTelemetry into client and server #12851

Merged
merged 11 commits into from
Dec 11, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
import com.linecorp.armeria.server.HttpService;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.config.internal.CommonConfig;
import io.opentelemetry.instrumentation.armeria.v1_3.ArmeriaTelemetry;
import io.opentelemetry.instrumentation.armeria.v1_3.ArmeriaTelemetryBuilder;
import io.opentelemetry.instrumentation.armeria.v1_3.ArmeriaClientTelemetry;
import io.opentelemetry.instrumentation.armeria.v1_3.ArmeriaClientTelemetryBuilder;
import io.opentelemetry.instrumentation.armeria.v1_3.ArmeriaServerTelemetry;
import io.opentelemetry.instrumentation.armeria.v1_3.ArmeriaServerTelemetryBuilder;
import io.opentelemetry.instrumentation.armeria.v1_3.internal.ArmeriaInstrumenterBuilderUtil;
import io.opentelemetry.javaagent.bootstrap.internal.AgentCommonConfig;
import java.util.function.Function;
Expand All @@ -23,15 +25,25 @@ public final class ArmeriaSingletons {
public static final Function<? super HttpService, ? extends HttpService> SERVER_DECORATOR;

static {
ArmeriaTelemetryBuilder builder = ArmeriaTelemetry.builder(GlobalOpenTelemetry.get());
CommonConfig config = AgentCommonConfig.get();
ArmeriaInstrumenterBuilderUtil.getClientBuilderExtractor().apply(builder).configure(config);
ArmeriaInstrumenterBuilderUtil.getServerBuilderExtractor().apply(builder).configure(config);
ArmeriaTelemetry telemetry = builder.build();

CLIENT_DECORATOR = telemetry.newClientDecorator();
ArmeriaClientTelemetryBuilder clientBuilder =
ArmeriaClientTelemetry.builder(GlobalOpenTelemetry.get());
ArmeriaInstrumenterBuilderUtil.getClientBuilderExtractor()
.apply(clientBuilder)
.configure(config);
ArmeriaClientTelemetry clientTelemetry = clientBuilder.build();

ArmeriaServerTelemetryBuilder serverBuilder =
ArmeriaServerTelemetry.builder(GlobalOpenTelemetry.get());
ArmeriaInstrumenterBuilderUtil.getServerBuilderExtractor()
.apply(serverBuilder)
.configure(config);
ArmeriaServerTelemetry serverTelemetry = serverBuilder.build();

CLIENT_DECORATOR = clientTelemetry.newDecorator();
Function<? super HttpService, ? extends HttpService> libraryDecorator =
telemetry.newServiceDecorator().compose(ResponseCustomizingDecorator::new);
serverTelemetry.newDecorator().compose(ResponseCustomizingDecorator::new);
SERVER_DECORATOR = service -> new ServerDecorator(service, libraryDecorator.apply(service));
}

Expand Down
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.armeria.v1_3;

import com.linecorp.armeria.client.ClientRequestContext;
import com.linecorp.armeria.client.HttpClient;
import com.linecorp.armeria.common.logging.RequestLog;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import java.util.function.Function;

/** Entrypoint for instrumenting Armeria clients. */
public final class ArmeriaClientTelemetry {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ktor has server and client instrumentations in separate packages, should we do the same here or change ktor?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good question... 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since they're small packages and we'd probably want to keep "Client" and "Server" in the class names not to conflict anyways, I think I'm leaning towards single package (and changing ktor)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one benefit for having a single package could be that when you initially have only server instrumentation, like in ktor-1, then adding client instrumentation wouldn't force changing the package (unless the author had the foresight to use the correct package from the start).


/**
* Returns a new {@link ArmeriaClientTelemetry} configured with the given {@link OpenTelemetry}.
*/
public static ArmeriaClientTelemetry create(OpenTelemetry openTelemetry) {
return builder(openTelemetry).build();
}

public static ArmeriaClientTelemetryBuilder builder(OpenTelemetry openTelemetry) {
return new ArmeriaClientTelemetryBuilder(openTelemetry);
}

private final Instrumenter<ClientRequestContext, RequestLog> instrumenter;

ArmeriaClientTelemetry(Instrumenter<ClientRequestContext, RequestLog> instrumenter) {
this.instrumenter = instrumenter;
}

/**
* Returns a new {@link HttpClient} decorator for use with methods like {@link
* com.linecorp.armeria.client.ClientBuilder#decorator(Function)}.
*/
public Function<? super HttpClient, ? extends HttpClient> newDecorator() {
return client -> new OpenTelemetryClient(client, instrumenter);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.armeria.v1_3;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.linecorp.armeria.client.ClientRequestContext;
import com.linecorp.armeria.common.logging.RequestLog;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpClientInstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesExtractorBuilder;
import io.opentelemetry.instrumentation.armeria.v1_3.internal.ArmeriaInstrumenterBuilderFactory;
import io.opentelemetry.instrumentation.armeria.v1_3.internal.ArmeriaInstrumenterBuilderUtil;
import io.opentelemetry.instrumentation.armeria.v1_3.internal.Experimental;
import java.util.List;
import java.util.Set;
import java.util.function.Function;

public final class ArmeriaClientTelemetryBuilder {

private final DefaultHttpClientInstrumenterBuilder<ClientRequestContext, RequestLog> builder;

static {
ArmeriaInstrumenterBuilderUtil.setClientBuilderExtractor(builder -> builder.builder);
Experimental.setSetEmitExperimentalClientTelemetry(
(builder, emit) -> builder.builder.setEmitExperimentalHttpClientMetrics(emit));
Experimental.setSetClientPeerService(
(builder, peerService) -> builder.builder.setPeerService(peerService));
}

ArmeriaClientTelemetryBuilder(OpenTelemetry openTelemetry) {
builder = ArmeriaInstrumenterBuilderFactory.getClientBuilder(openTelemetry);
}

/** Sets the status extractor for client spans. */
@CanIgnoreReturnValue
public ArmeriaClientTelemetryBuilder setStatusExtractor(
Function<
SpanStatusExtractor<? super ClientRequestContext, ? super RequestLog>,
? extends SpanStatusExtractor<? super ClientRequestContext, ? super RequestLog>>
statusExtractor) {
builder.setStatusExtractor(statusExtractor);
return this;
}

/**
* Adds an extra {@link AttributesExtractor} to invoke to set attributes to instrumented items.
* The {@link AttributesExtractor} will be executed after all default extractors.
*/
@CanIgnoreReturnValue
public ArmeriaClientTelemetryBuilder addAttributesExtractor(
AttributesExtractor<? super ClientRequestContext, ? super RequestLog> attributesExtractor) {
builder.addAttributesExtractor(attributesExtractor);
return this;
}

/**
* Configures the HTTP client request headers that will be captured as span attributes.
*
* @param requestHeaders A list of HTTP header names.
*/
@CanIgnoreReturnValue
public ArmeriaClientTelemetryBuilder setCapturedRequestHeaders(List<String> requestHeaders) {
builder.setCapturedRequestHeaders(requestHeaders);
return this;
}

/**
* Configures the HTTP client response headers that will be captured as span attributes.
*
* @param responseHeaders A list of HTTP header names.
*/
@CanIgnoreReturnValue
public ArmeriaClientTelemetryBuilder setCapturedResponseHeaders(List<String> responseHeaders) {
builder.setCapturedResponseHeaders(responseHeaders);
return this;
}

/**
* Configures the instrumentation to recognize an alternative set of HTTP request methods.
*
* <p>By default, this instrumentation defines "known" methods as the ones listed in <a
* href="https://www.rfc-editor.org/rfc/rfc9110.html#name-methods">RFC9110</a> and the PATCH
* method defined in <a href="https://www.rfc-editor.org/rfc/rfc5789.html">RFC5789</a>.
*
* <p>Note: calling this method <b>overrides</b> the default known method sets completely; it does
* not supplement it.
*
* @param knownMethods A set of recognized HTTP request methods.
* @see HttpClientAttributesExtractorBuilder#setKnownMethods(Set)
*/
@CanIgnoreReturnValue
public ArmeriaClientTelemetryBuilder setKnownMethods(Set<String> knownMethods) {
builder.setKnownMethods(knownMethods);
return this;
}

/** Sets custom client {@link SpanNameExtractor} via transform function. */
@CanIgnoreReturnValue
public ArmeriaClientTelemetryBuilder setSpanNameExtractor(
Function<
SpanNameExtractor<? super ClientRequestContext>,
? extends SpanNameExtractor<? super ClientRequestContext>>
clientSpanNameExtractor) {
builder.setSpanNameExtractor(clientSpanNameExtractor);
return this;
}

public ArmeriaClientTelemetry build() {
return new ArmeriaClientTelemetry(builder.build());
}
}
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.armeria.v1_3;

import com.linecorp.armeria.common.logging.RequestLog;
import com.linecorp.armeria.server.HttpService;
import com.linecorp.armeria.server.ServiceRequestContext;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import java.util.function.Function;

/** Entrypoint for instrumenting Armeria services. */
public final class ArmeriaServerTelemetry {

/**
* Returns a new {@link ArmeriaServerTelemetry} configured with the given {@link OpenTelemetry}.
*/
public static ArmeriaServerTelemetry create(OpenTelemetry openTelemetry) {
return builder(openTelemetry).build();
}

public static ArmeriaServerTelemetryBuilder builder(OpenTelemetry openTelemetry) {
return new ArmeriaServerTelemetryBuilder(openTelemetry);
}

private final Instrumenter<ServiceRequestContext, RequestLog> instrumenter;

ArmeriaServerTelemetry(Instrumenter<ServiceRequestContext, RequestLog> instrumenter) {
this.instrumenter = instrumenter;
}

/**
* Returns a new {@link HttpService} decorator for use with methods like {@link
* HttpService#decorate(Function)}.
*/
public Function<? super HttpService, ? extends HttpService> newDecorator() {
return service -> new OpenTelemetryService(service, instrumenter);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.armeria.v1_3;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.linecorp.armeria.common.logging.RequestLog;
import com.linecorp.armeria.server.ServiceRequestContext;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpServerInstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesExtractorBuilder;
import io.opentelemetry.instrumentation.armeria.v1_3.internal.ArmeriaInstrumenterBuilderFactory;
import io.opentelemetry.instrumentation.armeria.v1_3.internal.ArmeriaInstrumenterBuilderUtil;
import io.opentelemetry.instrumentation.armeria.v1_3.internal.Experimental;
import java.util.List;
import java.util.Set;
import java.util.function.Function;

public final class ArmeriaServerTelemetryBuilder {

private final DefaultHttpServerInstrumenterBuilder<ServiceRequestContext, RequestLog> builder;

static {
ArmeriaInstrumenterBuilderUtil.setServerBuilderExtractor(builder -> builder.builder);
Experimental.setSetEmitExperimentalServerTelemetry(
(builder, emit) -> builder.builder.setEmitExperimentalHttpServerMetrics(emit));
}

ArmeriaServerTelemetryBuilder(OpenTelemetry openTelemetry) {
builder = ArmeriaInstrumenterBuilderFactory.getServerBuilder(openTelemetry);
}

/** Sets the status extractor for server spans. */
@CanIgnoreReturnValue
public ArmeriaServerTelemetryBuilder setStatusExtractor(
Function<
SpanStatusExtractor<? super ServiceRequestContext, ? super RequestLog>,
? extends SpanStatusExtractor<? super ServiceRequestContext, ? super RequestLog>>
statusExtractor) {
builder.setStatusExtractor(statusExtractor);
return this;
}

/**
* Adds an extra {@link AttributesExtractor} to invoke to set attributes to instrumented items.
* The {@link AttributesExtractor} will be executed after all default extractors.
*/
@CanIgnoreReturnValue
public ArmeriaServerTelemetryBuilder addAttributesExtractor(
AttributesExtractor<? super ServiceRequestContext, ? super RequestLog> attributesExtractor) {
builder.addAttributesExtractor(attributesExtractor);
return this;
}

/**
* Configures the HTTP server request headers that will be captured as span attributes.
*
* @param requestHeaders A list of HTTP header names.
*/
@CanIgnoreReturnValue
public ArmeriaServerTelemetryBuilder setCapturedRequestHeaders(List<String> requestHeaders) {
builder.setCapturedRequestHeaders(requestHeaders);
return this;
}

/**
* Configures the HTTP server response headers that will be captured as span attributes.
*
* @param responseHeaders A list of HTTP header names.
*/
@CanIgnoreReturnValue
public ArmeriaServerTelemetryBuilder setCapturedResponseHeaders(List<String> responseHeaders) {
builder.setCapturedResponseHeaders(responseHeaders);
return this;
}

/**
* Configures the instrumentation to recognize an alternative set of HTTP request methods.
*
* <p>By default, this instrumentation defines "known" methods as the ones listed in <a
* href="https://www.rfc-editor.org/rfc/rfc9110.html#name-methods">RFC9110</a> and the PATCH
* method defined in <a href="https://www.rfc-editor.org/rfc/rfc5789.html">RFC5789</a>.
*
* <p>Note: calling this method <b>overrides</b> the default known method sets completely; it does
* not supplement it.
*
* @param knownMethods A set of recognized HTTP request methods.
* @see HttpServerAttributesExtractorBuilder#setKnownMethods(Set)
*/
@CanIgnoreReturnValue
public ArmeriaServerTelemetryBuilder setKnownMethods(Set<String> knownMethods) {
builder.setKnownMethods(knownMethods);
return this;
}

/** Sets custom server {@link SpanNameExtractor} via transform function. */
@CanIgnoreReturnValue
public ArmeriaServerTelemetryBuilder setSpanNameExtractor(
Function<
SpanNameExtractor<? super ServiceRequestContext>,
? extends SpanNameExtractor<? super ServiceRequestContext>>
serverSpanNameExtractor) {
builder.setSpanNameExtractor(serverSpanNameExtractor);
return this;
}

public ArmeriaServerTelemetry build() {
return new ArmeriaServerTelemetry(builder.build());
}
}
Loading
Loading