Skip to content

Commit

Permalink
Add User-Agent header to OTLP exporter requests (#4784)
Browse files Browse the repository at this point in the history
* Add User-Agent header to OTLP exporter requests

* PR feedback

* Make OtlpUserAgent final

* Add user agent note to managed channel
  • Loading branch information
jack-berg committed Oct 19, 2022
1 parent 9a1996c commit 1e67b05
Show file tree
Hide file tree
Showing 11 changed files with 125 additions and 1 deletion.
Expand Up @@ -9,6 +9,7 @@
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;
Expand Down Expand Up @@ -38,6 +39,7 @@ public final class OtlpHttpMetricExporterBuilder {

OtlpHttpMetricExporterBuilder() {
delegate = new OkHttpExporterBuilder<>("otlp", "metric", DEFAULT_ENDPOINT);
OtlpUserAgent.addUserAgentHeader(delegate::addHeader);
}

/**
Expand Down
Expand Up @@ -10,6 +10,7 @@

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;
Expand All @@ -27,6 +28,7 @@ public final class OtlpHttpSpanExporterBuilder {

OtlpHttpSpanExporterBuilder() {
delegate = new OkHttpExporterBuilder<>("otlp", "span", DEFAULT_ENDPOINT);
OtlpUserAgent.addUserAgentHeader(delegate::addHeader);
}

/**
Expand Down
Expand Up @@ -11,6 +11,7 @@
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;
Expand Down Expand Up @@ -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
Expand Down
Expand Up @@ -12,6 +12,7 @@
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;
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion exporters/otlp/common/build.gradle.kts
Expand Up @@ -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 {
Expand Down
@@ -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\\..*");
}
}
Expand Up @@ -10,6 +10,7 @@

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;
Expand All @@ -23,6 +24,7 @@ public final class OtlpHttpLogRecordExporterBuilder {

OtlpHttpLogRecordExporterBuilder() {
delegate = new OkHttpExporterBuilder<>("otlp", "log", DEFAULT_ENDPOINT);
OtlpUserAgent.addUserAgentHeader(delegate::addHeader);
}

/**
Expand Down
Expand Up @@ -12,6 +12,7 @@
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;
Expand Down Expand Up @@ -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
Expand Down
Expand Up @@ -216,6 +216,14 @@ void export() {
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
Expand Down
Expand Up @@ -10,6 +10,7 @@
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;
Expand Down Expand Up @@ -51,6 +52,10 @@ public TelemetryExporterBuilder<T> setEndpoint(String endpoint) {
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;
}

Expand Down

0 comments on commit 1e67b05

Please sign in to comment.