-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
OTLP metrics sender interface #5691
Changes from all commits
406df9c
ef01385
e01c127
6bc58df
086ddb8
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 |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/* | ||
* 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.docs.metrics; | ||
|
||
import io.micrometer.common.lang.NonNullApi; | ||
import io.micrometer.core.ipc.http.OkHttpSender; | ||
import io.micrometer.registry.otlp.OtlpConfig; | ||
import io.micrometer.registry.otlp.OtlpHttpMetricsSender; | ||
import io.micrometer.registry.otlp.OtlpMeterRegistry; | ||
import io.micrometer.registry.otlp.OtlpMetricsSender; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import java.util.Map; | ||
|
||
class OtlpMeterRegistryCustomizationTest { | ||
|
||
@Test | ||
void customizeHttpSender() { | ||
// tag::customizeHttpSender[] | ||
OtlpConfig config = OtlpConfig.DEFAULT; | ||
OtlpHttpMetricsSender httpMetricsSender = new OtlpHttpMetricsSender(new OkHttpSender(), config); | ||
OtlpMeterRegistry meterRegistry = OtlpMeterRegistry.builder(config).metricsSender(httpMetricsSender).build(); | ||
// end::customizeHttpSender[] | ||
} | ||
|
||
@Test | ||
void customizeOtlpSender() { | ||
// tag::customGrpcSender[] | ||
OtlpConfig config = OtlpConfig.DEFAULT; | ||
OtlpMetricsSender metricsSender = new OtlpGrpcMetricsSender(); | ||
OtlpMeterRegistry meterRegistry = OtlpMeterRegistry.builder(config).metricsSender(metricsSender).build(); | ||
// end::customGrpcSender[] | ||
} | ||
|
||
@NonNullApi | ||
private static class OtlpGrpcMetricsSender implements OtlpMetricsSender { | ||
|
||
@Override | ||
public void send(byte[] metricsData, Map<String, String> headers) { | ||
} | ||
|
||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/* | ||
* 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 { | ||
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. This is just to demonstrate what this might look like if we try to expand its scope outside of the OTLP registry. |
||
|
||
/** | ||
* 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(byte[] metricsData, Map<String, String> headers); | ||
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 encoding the data should be the responsibility of the sender, e.g.: users should be able to provide a sender that uses json and another one that use protobuf. But we can discuss it and change it in the next milestone if this is valid. 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. Encoding and sending are separate concerns. The existing HttpSender does not handle encoding, and encoding is specific to the backend/format, whereas a sender is re-usable across registries as is. I had a version of changes at one point that introduced encoding generically in PushMeterRegistry, but it felt like too much abstraction and too wide scope for the purpose of achieving this in the OTLP registry. We could add it just for the OTLP registry, but I was aiming to make the least amount of necessary change to achieve the actual use cases we have feedback requesting, which I didn't see any asking for e.g. JSON support. Keeping things as implementation details makes it easier for us to support it as well. |
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/* | ||
* 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 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 OtlpConfig config; | ||
|
||
private final String userAgentHeader; | ||
|
||
public OtlpHttpMetricsSender(HttpSender httpSender, OtlpConfig config) { | ||
this.httpSender = httpSender; | ||
this.config = config; | ||
this.userAgentHeader = getUserAgentHeader(); | ||
} | ||
|
||
@Override | ||
public void send(byte[] metricsData, Map<String, String> headers) { | ||
HttpSender.Request.Builder httpRequest = this.httpSender.post(config.url()) | ||
.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); | ||
} | ||
} | ||
|
||
private String getUserAgentHeader() { | ||
String userAgent = "Micrometer-OTLP-Exporter-Java"; | ||
String version = getClass().getPackage().getImplementationVersion(); | ||
if (version != null) { | ||
userAgent += "/" + version; | ||
} | ||
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(); | ||
} | ||
|
||
} |
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'm not sure what the right wording would be but from this it sounds like to me that gRPC does not use HTTP (it does use HTTP/2).