From 7fe776c0cb0ac762cfa1b8aecaddb144f3e2e4ea Mon Sep 17 00:00:00 2001 From: Tommy Ludwig <8924140+shakuzen@users.noreply.github.com> Date: Fri, 14 Mar 2025 19:01:49 +0900 Subject: [PATCH] Further enhancement to OtlpMetricsSender Introduces an immutable Request object and builder. --- .../OtlpMeterRegistryCustomizationTest.java | 4 +- .../registry/otlp/MetricsSender.java | 35 ------ .../registry/otlp/OtlpHttpMetricsSender.java | 34 +++--- .../registry/otlp/OtlpMeterRegistry.java | 7 +- .../registry/otlp/OtlpMetricsSender.java | 107 ++++++++++++++++-- 5 files changed, 122 insertions(+), 65 deletions(-) delete mode 100644 implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/MetricsSender.java diff --git a/docs/src/test/java/io/micrometer/docs/metrics/OtlpMeterRegistryCustomizationTest.java b/docs/src/test/java/io/micrometer/docs/metrics/OtlpMeterRegistryCustomizationTest.java index 9e6eb81fae..62f65fcb21 100644 --- a/docs/src/test/java/io/micrometer/docs/metrics/OtlpMeterRegistryCustomizationTest.java +++ b/docs/src/test/java/io/micrometer/docs/metrics/OtlpMeterRegistryCustomizationTest.java @@ -23,8 +23,6 @@ import io.micrometer.registry.otlp.OtlpMetricsSender; import org.junit.jupiter.api.Test; -import java.util.Map; - class OtlpMeterRegistryCustomizationTest { @Test @@ -49,7 +47,7 @@ void customizeOtlpSender() { private static class OtlpGrpcMetricsSender implements OtlpMetricsSender { @Override - public void send(String address, byte[] metricsData, Map headers) { + public void send(Request request) { } } diff --git a/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/MetricsSender.java b/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/MetricsSender.java deleted file mode 100644 index 2829819bd3..0000000000 --- a/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/MetricsSender.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2025 VMware, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.micrometer.registry.otlp; - -import java.util.Map; - -// intentionally not public while we incubate this concept -// if we want to use this in other registries, it should move to micrometer-core and become public API -interface MetricsSender { - - /** - * Send encoded metrics data from a {@link io.micrometer.core.instrument.MeterRegistry - * MeterRegistry}. - * @param address where to send the metrics - * @param metricsData encoded batch of metrics - * @param headers metadata to send as headers with the metrics data - * @throws Throwable when there is an exception in sending the metrics; the caller - * should handle this in some way such as logging the exception - */ - void send(String address, byte[] metricsData, Map headers) throws Throwable; - -} diff --git a/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpHttpMetricsSender.java b/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpHttpMetricsSender.java index 2e09257490..704107e029 100644 --- a/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpHttpMetricsSender.java +++ b/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpHttpMetricsSender.java @@ -15,12 +15,8 @@ */ package io.micrometer.registry.otlp; -import io.micrometer.common.util.internal.logging.InternalLogger; -import io.micrometer.common.util.internal.logging.InternalLoggerFactory; import io.micrometer.core.ipc.http.HttpSender; -import java.util.Map; - /** * An implementation of {@link OtlpMetricsSender} that uses an {@link HttpSender}. * @@ -28,12 +24,14 @@ */ public class OtlpHttpMetricsSender implements OtlpMetricsSender { - private static final InternalLogger logger = InternalLoggerFactory.getInstance(OtlpHttpMetricsSender.class); - private final HttpSender httpSender; private final String userAgentHeader; + /** + * Metrics sender using the given {@link HttpSender}. + * @param httpSender client to use to send metrics + */ public OtlpHttpMetricsSender(HttpSender httpSender) { this.httpSender = httpSender; this.userAgentHeader = getUserAgentHeader(); @@ -41,19 +39,23 @@ public OtlpHttpMetricsSender(HttpSender httpSender) { /** * Send a batch of OTLP Protobuf format metrics to an OTLP HTTP receiver. - * @param address address of the OTLP HTTP receiver to which metrics will be sent - * @param metricsData OTLP protobuf encoded batch of metrics - * @param headers metadata to send as headers with the metrics data - * @throws Throwable when there is an exception in sending the metrics; the caller + * @param request metrics request to publish + * @throws Exception when there is an exception in sending the metrics; the caller * should handle this in some way such as logging the exception */ @Override - public void send(String address, byte[] metricsData, Map headers) throws Throwable { - HttpSender.Request.Builder httpRequest = this.httpSender.post(address) + public void send(Request request) throws Exception { + HttpSender.Request.Builder httpRequest = this.httpSender.post(request.getAddress()) .withHeader("User-Agent", userAgentHeader) - .withContent("application/x-protobuf", metricsData); - headers.forEach(httpRequest::withHeader); - HttpSender.Response response = httpRequest.send(); + .withContent("application/x-protobuf", request.getMetricsData()); + request.getHeaders().forEach(httpRequest::withHeader); + HttpSender.Response response; + try { + response = httpRequest.send(); + } + catch (Throwable e) { + throw new Exception(e); + } if (!response.isSuccessful()) { throw new OtlpHttpMetricsSendUnsuccessfulException(String .format("Server responded with HTTP status code %d and body %s", response.code(), response.body())); @@ -71,7 +73,7 @@ private String getUserAgentHeader() { private static class OtlpHttpMetricsSendUnsuccessfulException extends RuntimeException { - public OtlpHttpMetricsSendUnsuccessfulException(String message) { + private OtlpHttpMetricsSendUnsuccessfulException(String message) { super(message); } 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 425738cd8b..b38646cd7a 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 @@ -181,9 +181,12 @@ protected void publish() { .build()) .build(); - metricsSender.send(config.url(), request.toByteArray(), config.headers()); + metricsSender.send(OtlpMetricsSender.Request.builder(request.toByteArray()) + .address(config.url()) + .headers(config.headers()) + .build()); } - catch (Throwable e) { + catch (Exception e) { logger.warn(String.format("Failed to publish metrics to OTLP receiver (context: %s)", getConfigurationContext()), e); } diff --git a/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpMetricsSender.java b/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpMetricsSender.java index 3b2150c43f..c2999106bd 100644 --- a/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpMetricsSender.java +++ b/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpMetricsSender.java @@ -15,25 +15,114 @@ */ package io.micrometer.registry.otlp; +import io.micrometer.common.lang.Nullable; + +import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.Map; +import java.util.Objects; /** - * This is responsible for sending OTLP format metrics to a compatible location. Specific - * implementations can use different transports or clients for sending the metrics. + * This is responsible for sending OTLP protobuf format metrics to a compatible location. + * Specific implementations can use different transports or clients for sending the + * metrics. * * @since 1.15.0 */ -public interface OtlpMetricsSender extends MetricsSender { +public interface OtlpMetricsSender { /** * Send a batch of OTLP Protobuf format metrics to an OTLP receiver. - * @param address address of the OTLP receiver to which metrics will be sent - * @param metricsData OTLP protobuf encoded batch of metrics - * @param headers metadata to send as headers with the metrics data - * @throws Throwable when there is an exception in sending the metrics; the caller + * @param request metrics request to publish + * @throws Exception when there is an exception in sending the metrics; the caller * should handle this in some way such as logging the exception */ - @Override - void send(String address, byte[] metricsData, Map headers) throws Throwable; + void send(Request request) throws Exception; + + /** + * Immutable representation of a payload of metrics to use with an + * {@link OtlpMetricsSender}. + * + * @since 1.15.0 + */ + class Request { + + @Nullable + private final String address; + + private final Map headers; + + private final byte[] metricsData; + + /** + * Represents a payload of metrics to be sent. + * @param address where to send the metrics + * @param headers metadata to send as headers with the metrics data + * @param metricsData OTLP protobuf encoded batch of metrics + */ + private Request(@Nullable String address, Map headers, byte[] metricsData) { + this.address = address; + this.headers = headers; + this.metricsData = metricsData; + } + + @Nullable + public String getAddress() { + return address; + } + + public byte[] getMetricsData() { + return metricsData; + } + + public Map getHeaders() { + return headers; + } + + @Override + public String toString() { + return "OtlpMetricsSender.Request for address: " + address + ", headers: " + headers + ", metricsData: " + + new String(metricsData, StandardCharsets.UTF_8); + } + + /** + * Get a builder for a request. + * @param metricsData OTLP protobuf encoded batch of metrics + * @return builder + */ + public static Builder builder(byte[] metricsData) { + return new Builder(metricsData); + } + + public static class Builder { + + private final byte[] metricsData; + + @Nullable + private String address; + + private Map headers = Collections.emptyMap(); + + private Builder(byte[] metricsData) { + this.metricsData = Objects.requireNonNull(metricsData); + } + + public Builder address(String address) { + this.address = address; + return this; + } + + public Builder headers(Map headers) { + this.headers = headers; + return this; + } + + public Request build() { + return new Request(address, headers, metricsData); + } + + } + + } }