Add User-Agent header to OTLP exporter requests (#4784)

* Add User-Agent header to OTLP exporter requests

* PR feedback

* Make OtlpUserAgent final

* Add user agent note to managed channel
This commit is contained in:
jack-berg 2022-10-19 10:48:43 -05:00 committed by GitHub
parent 9a1996c6c1
commit 1e67b056b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 125 additions and 1 deletions

View File

@ -9,6 +9,7 @@ import static io.opentelemetry.api.internal.Utils.checkArgument;
import static java.util.Objects.requireNonNull;
import io.opentelemetry.exporter.internal.okhttp.OkHttpExporterBuilder;
import io.opentelemetry.exporter.internal.otlp.OtlpUserAgent;
import io.opentelemetry.exporter.internal.otlp.metrics.MetricsRequestMarshaler;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector;
@ -38,6 +39,7 @@ public final class OtlpHttpMetricExporterBuilder {
OtlpHttpMetricExporterBuilder() {
delegate = new OkHttpExporterBuilder<>("otlp", "metric", DEFAULT_ENDPOINT);
OtlpUserAgent.addUserAgentHeader(delegate::addHeader);
}
/**

View File

@ -10,6 +10,7 @@ import static java.util.Objects.requireNonNull;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.exporter.internal.okhttp.OkHttpExporterBuilder;
import io.opentelemetry.exporter.internal.otlp.OtlpUserAgent;
import io.opentelemetry.exporter.internal.otlp.traces.TraceRequestMarshaler;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
@ -27,6 +28,7 @@ public final class OtlpHttpSpanExporterBuilder {
OtlpHttpSpanExporterBuilder() {
delegate = new OkHttpExporterBuilder<>("otlp", "span", DEFAULT_ENDPOINT);
OtlpUserAgent.addUserAgentHeader(delegate::addHeader);
}
/**

View File

@ -11,6 +11,7 @@ import static java.util.Objects.requireNonNull;
import io.grpc.ManagedChannel;
import io.opentelemetry.exporter.internal.grpc.GrpcExporter;
import io.opentelemetry.exporter.internal.grpc.GrpcExporterBuilder;
import io.opentelemetry.exporter.internal.otlp.OtlpUserAgent;
import io.opentelemetry.exporter.internal.otlp.metrics.MetricsRequestMarshaler;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector;
@ -56,12 +57,17 @@ public final class OtlpGrpcMetricExporterBuilder {
DEFAULT_ENDPOINT,
() -> MarshalerMetricsServiceGrpc::newFutureStub,
GRPC_ENDPOINT_PATH);
OtlpUserAgent.addUserAgentHeader(delegate::addHeader);
}
/**
* Sets the managed chanel to use when communicating with the backend. Takes precedence over
* {@link #setEndpoint(String)} if both are called.
*
* <p>Note: calling this overrides the spec compliant {@code User-Agent} header. To ensure spec
* compliance, set {@link io.grpc.ManagedChannelBuilder#userAgent(String)} to {@link
* OtlpUserAgent#getUserAgent()} when building the channel.
*
* @param channel the channel to use
* @return this builder's instance
* @deprecated Use {@link #setEndpoint(String)}. If you have a use case not satisfied by the

View File

@ -12,6 +12,7 @@ import io.grpc.ManagedChannel;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.exporter.internal.grpc.GrpcExporter;
import io.opentelemetry.exporter.internal.grpc.GrpcExporterBuilder;
import io.opentelemetry.exporter.internal.otlp.OtlpUserAgent;
import io.opentelemetry.exporter.internal.otlp.traces.TraceRequestMarshaler;
import java.net.URI;
import java.time.Duration;
@ -41,12 +42,17 @@ public final class OtlpGrpcSpanExporterBuilder {
DEFAULT_ENDPOINT,
() -> MarshalerTraceServiceGrpc::newFutureStub,
GRPC_ENDPOINT_PATH);
OtlpUserAgent.addUserAgentHeader(delegate::addHeader);
}
/**
* Sets the managed chanel to use when communicating with the backend. Takes precedence over
* {@link #setEndpoint(String)} if both are called.
*
* <p>Note: calling this overrides the spec compliant {@code User-Agent} header. To ensure spec
* compliance, set {@link io.grpc.ManagedChannelBuilder#userAgent(String)} to {@link
* OtlpUserAgent#getUserAgent()} when building the channel.
*
* @param channel the channel to use
* @return this builder's instance
* @deprecated Use {@link #setEndpoint(String)}. If you have a use case not satisfied by the

View File

@ -9,7 +9,7 @@ plugins {
}
description = "OpenTelemetry Protocol Exporter"
otelJava.moduleName.set("io.opentelemetry.exporter.otlp.internal")
otelJava.moduleName.set("io.opentelemetry.exporter.internal.otlp")
val versions: Map<String, String> by project
dependencies {

View File

@ -0,0 +1,55 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.exporter.internal.otlp;
import java.util.Properties;
import java.util.function.BiConsumer;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public final class OtlpUserAgent {
private static final String userAgent = "OTel OTLP Exporter Java/" + readVersion();
private static String readVersion() {
Properties properties = new Properties();
try {
properties.load(OtlpUserAgent.class.getResourceAsStream("version.properties"));
} catch (Exception e) {
// we left the attribute empty
return "unknown";
}
return properties.getProperty("sdk.version", "unknown");
}
/**
* Return an OTLP {@code User-Agent} header value of the form {@code "OTel OTLP Exporter
* Java/{version}"}.
*
* @see <a
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#user-agent">OTLP
* Exporter User Agent</a>
*/
public static String getUserAgent() {
return userAgent;
}
/**
* Call the {@code consumer with} an OTLP {@code User-Agent} header value of the form {@code "OTel
* OTLP Exporter Java/{version}"}.
*
* @see <a
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#user-agent">OTLP
* Exporter User Agent</a>
*/
public static void addUserAgentHeader(BiConsumer<String, String> consumer) {
consumer.accept("User-Agent", userAgent);
}
private OtlpUserAgent() {}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.exporter.internal.otlp;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
class OtlpUserAgentTest {
@Test
void getUserAgent() {
assertThat(OtlpUserAgent.getUserAgent()).matches("OTel OTLP Exporter Java/1\\..*");
}
@Test
void addUserAgentHeader() {
AtomicReference<String> keyRef = new AtomicReference<>();
AtomicReference<String> valueRef = new AtomicReference<>();
OtlpUserAgent.addUserAgentHeader(
(key, value) -> {
keyRef.set(key);
valueRef.set(value);
});
assertThat(keyRef.get()).isEqualTo("User-Agent");
assertThat(valueRef.get()).matches("OTel OTLP Exporter Java/1\\..*");
}
}

View File

@ -10,6 +10,7 @@ import static java.util.Objects.requireNonNull;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.exporter.internal.okhttp.OkHttpExporterBuilder;
import io.opentelemetry.exporter.internal.otlp.OtlpUserAgent;
import io.opentelemetry.exporter.internal.otlp.logs.LogsRequestMarshaler;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
@ -23,6 +24,7 @@ public final class OtlpHttpLogRecordExporterBuilder {
OtlpHttpLogRecordExporterBuilder() {
delegate = new OkHttpExporterBuilder<>("otlp", "log", DEFAULT_ENDPOINT);
OtlpUserAgent.addUserAgentHeader(delegate::addHeader);
}
/**

View File

@ -12,6 +12,7 @@ import io.grpc.ManagedChannel;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.exporter.internal.grpc.GrpcExporter;
import io.opentelemetry.exporter.internal.grpc.GrpcExporterBuilder;
import io.opentelemetry.exporter.internal.otlp.OtlpUserAgent;
import io.opentelemetry.exporter.internal.otlp.logs.LogsRequestMarshaler;
import java.net.URI;
import java.time.Duration;
@ -41,12 +42,17 @@ public final class OtlpGrpcLogRecordExporterBuilder {
DEFAULT_ENDPOINT,
() -> MarshalerLogsServiceGrpc::newFutureStub,
GRPC_ENDPOINT_PATH);
OtlpUserAgent.addUserAgentHeader(delegate::addHeader);
}
/**
* Sets the managed chanel to use when communicating with the backend. Takes precedence over
* {@link #setEndpoint(String)} if both are called.
*
* <p>Note: calling this overrides the spec compliant {@code User-Agent} header. To ensure spec
* compliance, set {@link io.grpc.ManagedChannelBuilder#userAgent(String)} to {@link
* OtlpUserAgent#getUserAgent()} when building the channel.
*
* @param channel the channel to use
* @return this builder's instance
* @deprecated Use {@link #setEndpoint(String)}. If you have a use case not satisfied by the

View File

@ -216,6 +216,14 @@ public abstract class AbstractGrpcTelemetryExporterTest<T, U extends Message> {
assertThat(exporter.export(telemetry).join(10, TimeUnit.SECONDS).isSuccess()).isTrue();
List<U> expectedResourceTelemetry = toProto(telemetry);
assertThat(exportedResourceTelemetry).containsExactlyElementsOf(expectedResourceTelemetry);
// Assert request contains OTLP spec compliant User-Agent header
assertThat(httpRequests)
.singleElement()
.satisfies(
req -> {
assertThat(req.headers().get("User-Agent")).matches("OTel OTLP Exporter Java/1\\..*");
});
}
@Test

View File

@ -10,6 +10,7 @@ import static java.util.Objects.requireNonNull;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.opentelemetry.exporter.internal.grpc.ManagedChannelUtil;
import io.opentelemetry.exporter.internal.otlp.OtlpUserAgent;
import io.opentelemetry.exporter.internal.retry.RetryPolicy;
import io.opentelemetry.sdk.common.CompletableResultCode;
import java.net.URI;
@ -51,6 +52,10 @@ public final class ManagedChannelTelemetryExporterBuilder<T>
if (!uri.getScheme().equals("https")) {
channelBuilder.usePlaintext();
}
// User-Agent can only be set at the channel level with upstream gRPC client. If a user wants
// the User-Agent to be spec compliant they must manually set the user agent when building
// their channel.
channelBuilder.userAgent(OtlpUserAgent.getUserAgent());
return this;
}