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:
parent
9a1996c6c1
commit
1e67b056b4
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
}
|
||||
|
|
@ -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\\..*");
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue