From d7daaefffe0d74b5334a45273191fa2ad227f88d Mon Sep 17 00:00:00 2001 From: Jonatan Ivanov Date: Fri, 11 Oct 2024 15:26:11 -0700 Subject: [PATCH] Set user agent header in OTLP registry Co-authored-by: Joao Grassi <5938087+joaopgrassi@users.noreply.github.com> Co-authored-by: Jonatan Ivanov --- .../micrometer-registry-otlp/build.gradle | 1 + .../registry/otlp/OtlpMeterRegistry.java | 14 +++++++- .../registry/otlp/OtlpMeterRegistryTest.java | 35 +++++++++++++++++-- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/implementations/micrometer-registry-otlp/build.gradle b/implementations/micrometer-registry-otlp/build.gradle index 4d7bc0e06a..89843fed4b 100644 --- a/implementations/micrometer-registry-otlp/build.gradle +++ b/implementations/micrometer-registry-otlp/build.gradle @@ -10,6 +10,7 @@ dependencies { testImplementation 'io.rest-assured:rest-assured' testImplementation 'org.testcontainers:junit-jupiter' testImplementation 'org.awaitility:awaitility' + testImplementation libs.mockitoCore5 } dockerTest { diff --git a/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpMeterRegistry.java b/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpMeterRegistry.java index 98821bec88..30468b5f49 100644 --- a/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpMeterRegistry.java +++ b/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpMeterRegistry.java @@ -89,6 +89,8 @@ public class OtlpMeterRegistry extends PushMeterRegistry { private long deltaAggregationTimeUnixNano = 0L; + private final String userAgentHeader; + // Time when the last scheduled rollOver has started. Applicable only for delta // flavour. private volatile long lastMeterRolloverStartTime = -1; @@ -104,9 +106,10 @@ public OtlpMeterRegistry(OtlpConfig config, Clock clock) { this(config, clock, new HttpUrlConnectionSender()); } + // VisibleForTesting // not public until we decide what we want to expose in public API // HttpSender may not be a good idea if we will support a non-HTTP transport - private OtlpMeterRegistry(OtlpConfig config, Clock clock, HttpSender httpSender) { + OtlpMeterRegistry(OtlpConfig config, Clock clock, HttpSender httpSender) { super(config, clock); this.config = config; this.baseTimeUnit = config.baseTimeUnit(); @@ -115,6 +118,7 @@ private OtlpMeterRegistry(OtlpConfig config, Clock clock, HttpSender httpSender) this.otlpAggregationTemporality = AggregationTemporality .toOtlpAggregationTemporality(config.aggregationTemporality()); setDeltaAggregationTimeUnixNano(); + this.userAgentHeader = getUserAgentHeader(); config().namingConvention(NamingConvention.dot); start(DEFAULT_THREAD_FACTORY); } @@ -163,6 +167,7 @@ protected void publish() { .build()) .build(); HttpSender.Request.Builder httpRequest = this.httpSender.post(this.config.url()) + .withHeader("User-Agent", this.userAgentHeader) .withContent("application/x-protobuf", request.toByteArray()); this.config.headers().forEach(httpRequest::withHeader); HttpSender.Response response = httpRequest.send(); @@ -572,4 +577,11 @@ static double[] getSloWithPositiveInf(DistributionStatisticConfig distributionSt return sloWithPositiveInf; } + private String getUserAgentHeader() { + if (this.getClass().getPackage().getImplementationVersion() == null) { + return "Micrometer-OTLP-Exporter-Java"; + } + return "Micrometer-OTLP-Exporter-Java/" + this.getClass().getPackage().getImplementationVersion(); + } + } diff --git a/implementations/micrometer-registry-otlp/src/test/java/io/micrometer/registry/otlp/OtlpMeterRegistryTest.java b/implementations/micrometer-registry-otlp/src/test/java/io/micrometer/registry/otlp/OtlpMeterRegistryTest.java index 57bb988d6b..f41079b78b 100644 --- a/implementations/micrometer-registry-otlp/src/test/java/io/micrometer/registry/otlp/OtlpMeterRegistryTest.java +++ b/implementations/micrometer-registry-otlp/src/test/java/io/micrometer/registry/otlp/OtlpMeterRegistryTest.java @@ -15,12 +15,15 @@ */ package io.micrometer.registry.otlp; +import io.micrometer.core.Issue; import io.micrometer.core.instrument.*; import io.micrometer.core.instrument.config.NamingConvention; import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; +import io.micrometer.core.ipc.http.HttpSender; import io.opentelemetry.proto.metrics.v1.HistogramDataPoint; import io.opentelemetry.proto.metrics.v1.Metric; import io.opentelemetry.proto.metrics.v1.NumberDataPoint; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -31,6 +34,8 @@ import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.assertArg; +import static org.mockito.Mockito.*; import static uk.org.webcompere.systemstubs.SystemStubs.withEnvironmentVariables; /** @@ -47,12 +52,21 @@ abstract class OtlpMeterRegistryTest { protected static final Tag meterTag = Tag.of("key", "value"); - protected MockClock clock = new MockClock(); + protected MockClock clock; - protected OtlpMeterRegistry registry = new OtlpMeterRegistry(otlpConfig(), clock); + protected OtlpMeterRegistry registry; + + private HttpSender mockHttpSender; abstract OtlpConfig otlpConfig(); + @BeforeEach + void setUp() { + this.clock = new MockClock(); + this.mockHttpSender = mock(HttpSender.class); + this.registry = new OtlpMeterRegistry(otlpConfig(), this.clock, this.mockHttpSender); + } + // If the service.name was not specified, SDKs MUST fallback to 'unknown_service' @Test void unknownServiceByDefault() { @@ -106,6 +120,23 @@ void timeGauge() { + " time_unix_nano: 1000000\n" + " as_double: 0.024\n" + " }\n" + "}\n"); } + @Issue("#5577") + @Test + void httpHeaders() throws Throwable { + HttpSender.Request.Builder builder = HttpSender.Request.build(otlpConfig().url(), this.mockHttpSender); + when(mockHttpSender.post(otlpConfig().url())).thenReturn(builder); + + when(mockHttpSender.send(isA(HttpSender.Request.class))).thenReturn(new HttpSender.Response(200, "")); + + writeToMetric(TimeGauge.builder("gauge.time", this, TimeUnit.MICROSECONDS, o -> 24).register(registry)); + registry.publish(); + + verify(this.mockHttpSender).send(assertArg(request -> { + assertThat(request.getRequestHeaders().get("User-Agent")).startsWith("Micrometer-OTLP-Exporter-Java"); + assertThat(request.getRequestHeaders()).containsEntry("Content-Type", "application/x-protobuf"); + })); + } + @Test void distributionWithPercentileShouldWriteSummary() { Timer timer = Timer.builder("timer")