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

Add User-Agent header to OTLP exporter requests #4784

Merged
merged 4 commits into from Oct 19, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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,6 +57,7 @@ public final class OtlpGrpcMetricExporterBuilder {
DEFAULT_ENDPOINT,
() -> MarshalerMetricsServiceGrpc::newFutureStub,
GRPC_ENDPOINT_PATH);
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.traces.TraceRequestMarshaler;
import java.net.URI;
import java.time.Duration;
Expand Down Expand Up @@ -41,6 +42,7 @@ public final class OtlpGrpcSpanExporterBuilder {
DEFAULT_ENDPOINT,
() -> MarshalerTraceServiceGrpc::newFutureStub,
GRPC_ENDPOINT_PATH);
OtlpUserAgent.addUserAgentHeader(delegate::addHeader);
}

/**
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")
Copy link
Contributor

Choose a reason for hiding this comment

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

what affect does this have?

Copy link
Member Author

Choose a reason for hiding this comment

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

The moduleName is used to set the location of the version.properties file. It should reflect the base package of the artifact, so in this case it was wrong. If its not correct, then fetching a relative resource like OtlpUserAgent.class.getResourceAsStream("version.properties") will fail.


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,6 +42,7 @@ public final class OtlpGrpcLogRecordExporterBuilder {
DEFAULT_ENDPOINT,
() -> MarshalerLogsServiceGrpc::newFutureStub,
GRPC_ENDPOINT_PATH);
OtlpUserAgent.addUserAgentHeader(delegate::addHeader);
}

/**
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.
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
channelBuilder.userAgent(OtlpUserAgent.getUserAgent());
return this;
}

Expand Down