Skip to content

Further enhancement to OtlpMetricsSender #6025

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

Merged
merged 1 commit into from
Mar 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
import io.micrometer.registry.otlp.OtlpMetricsSender;
import org.junit.jupiter.api.Test;

import java.util.Map;

class OtlpMeterRegistryCustomizationTest {

@Test
Expand All @@ -49,7 +47,7 @@ void customizeOtlpSender() {
private static class OtlpGrpcMetricsSender implements OtlpMetricsSender {

@Override
public void send(String address, byte[] metricsData, Map<String, String> headers) {
public void send(Request request) {
}

}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,45 +15,47 @@
*/
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}.
*
* @since 1.15.0
*/
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();
}

/**
* 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<String, String> 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);
Copy link
Member Author

Choose a reason for hiding this comment

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

Since the HttpSender throws Throwable, we need to catch it here. Wrapping in Exception feels weird but I'm not sure what's better to do.

}
if (!response.isSuccessful()) {
throw new OtlpHttpMetricsSendUnsuccessfulException(String
.format("Server responded with HTTP status code %d and body %s", response.code(), response.body()));
Expand All @@ -71,7 +73,7 @@ private String getUserAgentHeader() {

private static class OtlpHttpMetricsSendUnsuccessfulException extends RuntimeException {

public OtlpHttpMetricsSendUnsuccessfulException(String message) {
private OtlpHttpMetricsSendUnsuccessfulException(String message) {
super(message);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> headers) {
this.headers = headers;
return this;
}

public Request build() {
return new Request(address, headers, metricsData);
}

}

}

}