-
Notifications
You must be signed in to change notification settings - Fork 1k
Improve OtlpMetricsSender API #5994
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -24,9 +24,12 @@ 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(byte[] metricsData, Map<String, String> headers); | ||||||
void send(String address, byte[] metricsData, Map<String, String> headers) throws Throwable; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be the address, headers, data order more logical?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure. I think arguments could be made for a variety of orders. Switching to a builder pattern may help avoid the matter of order entirely. |
||||||
|
||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,32 +32,31 @@ public class OtlpHttpMetricsSender implements OtlpMetricsSender { | |
|
||
private final HttpSender httpSender; | ||
|
||
private final OtlpConfig config; | ||
|
||
private final String userAgentHeader; | ||
|
||
public OtlpHttpMetricsSender(HttpSender httpSender, OtlpConfig config) { | ||
public OtlpHttpMetricsSender(HttpSender httpSender) { | ||
this.httpSender = httpSender; | ||
this.config = config; | ||
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 | ||
* should handle this in some way such as logging the exception | ||
*/ | ||
@Override | ||
public void send(byte[] metricsData, Map<String, String> headers) { | ||
HttpSender.Request.Builder httpRequest = this.httpSender.post(config.url()) | ||
public void send(String address, byte[] metricsData, Map<String, String> headers) throws Throwable { | ||
HttpSender.Request.Builder httpRequest = this.httpSender.post(address) | ||
.withHeader("User-Agent", userAgentHeader) | ||
.withContent("application/x-protobuf", metricsData); | ||
headers.forEach(httpRequest::withHeader); | ||
try { | ||
HttpSender.Response response = httpRequest.send(); | ||
if (!response.isSuccessful()) { | ||
logger.warn( | ||
"Failed to publish metrics (context: {}). Server responded with HTTP status code {} and body {}", | ||
getConfigurationContext(), response.code(), response.body()); | ||
} | ||
} | ||
catch (Throwable e) { | ||
logger.warn("Failed to publish metrics (context: {}) ", getConfigurationContext(), e); | ||
HttpSender.Response response = httpRequest.send(); | ||
if (!response.isSuccessful()) { | ||
throw new OtlpHttpMetricsSendUnsuccessfulException(String | ||
.format("Server responded with HTTP status code %d and body %s", response.code(), response.body())); | ||
} | ||
} | ||
|
||
|
@@ -70,14 +69,12 @@ private String getUserAgentHeader() { | |
return userAgent; | ||
} | ||
|
||
/** | ||
* Get the configuration context. | ||
* @return A message containing enough information for the log reader to figure out | ||
* what configuration details may have contributed to the failure. | ||
*/ | ||
private String getConfigurationContext() { | ||
// While other values may contribute to failures, these two are most common | ||
return "url=" + config.url() + ", resource-attributes=" + config.resourceAttributes(); | ||
private static class OtlpHttpMetricsSendUnsuccessfulException extends RuntimeException { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm thinking if we should make this more general and public so that other implementations can reuse it and users can get the same exception class for the different implementation. |
||
|
||
public OtlpHttpMetricsSendUnsuccessfulException(String message) { | ||
super(message); | ||
} | ||
|
||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,11 +26,14 @@ | |
public interface OtlpMetricsSender extends MetricsSender { | ||
|
||
/** | ||
* Send a batch of OTLP Protobuf format metrics to a receiver. | ||
* 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 | ||
* should handle this in some way such as logging the exception | ||
*/ | ||
@Override | ||
void send(byte[] metricsData, Map<String, String> headers); | ||
void send(String address, byte[] metricsData, Map<String, String> headers) throws Throwable; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think taking the address as a String is generic enough to work for other implementations than the HTTP one, but let me know if anyone has a concern about this. The implementation is responsible for parsing the address String. The OtlpMeterRegistry will always send the OtlpConfig#url as the address, so while it may semantically be weird if the address isn't actually a URL but the configured sender implementation can handle it (since it is a String that isn't necessarily a URL), this should work nonetheless. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm always cautious of adding extra parameters to methods. Would the API be cleaner if Something like this (pseudocode incoming) interface MetricsSender {
/**
* Send encoded metrics data from a {@link io.micrometer.core.instrument.MeterRegistry
* MeterRegistry}.
* @param metricsData encoded batch of metrics
* @param headers metadata to send as headers with the metrics data
*/
void send(MetricsSenderPayload payload);
class MetricsSenderPayload {
private final String address;
private final byte[] payload;
private final Map<String, String> headers;
// constructor and accessors omitted
} and then MetricsSender.MetricsSenderPayload payload = new MetricsSender.MetricsSenderPayload(
this.config.url(),
request.toByteArray(),
this.config.headers()
);
metricsSender.send(payload); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the review. We could do something like that, and there's precedent in the codebase with HttpSender taking a Request object. I can take a closer look at it tomorrow. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've played around with this a bit locally, but there are a number of choices, especially given the package-private MetricsSender (I would probably get rid of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm totally fine calling it |
||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't mention it in the PR description but
throws Throwable
was added here and elsewhere. The point was to ensure that the caller (MeterRegistry publish implementation) handles the exception case, usually with logging.HttpSender
similarly declaresthrows Throwable
. That said, it may be worth looking at doing things differently here because catching Throwable includes things like OOME, as pointed out by @marcingrzejszczak in an offline review.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can catch
Exception
instead.