Skip to content

Commit f99efa6

Browse files
OTLP metrics sender interface (#5691)
* Add capability for anyone to define their implementation of OTLP metrics sending * Fix formatting * Remove otlp proto from API and introduce Builder Keeps the API more generic without a dependency on API from the opentelemetry-proto-java project. Also adds a Builder to avoid the need for more public constructors that may change in the future. * Polish * Add docs --------- Co-authored-by: Tommy Ludwig <[email protected]>
1 parent 2948805 commit f99efa6

File tree

8 files changed

+299
-49
lines changed

8 files changed

+299
-49
lines changed

docs/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,12 @@ dependencies {
4747

4848
testImplementation project(':micrometer-test')
4949
testImplementation project(':micrometer-observation-test')
50+
testImplementation project(':micrometer-registry-otlp')
5051
testImplementation libs.aspectjweaver
5152
testImplementation libs.junitJupiter
5253
testImplementation 'org.assertj:assertj-core'
54+
// needed for OtlpMeterRegistryCustomizationTest
55+
testRuntimeOnly libs.okhttp
5356
testImplementation(libs.spring6.context)
5457
testImplementation 'io.projectreactor:reactor-core'
5558
testImplementation 'io.projectreactor:reactor-test'

docs/modules/ROOT/pages/implementations/otlp.adoc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,27 @@ management:
6666

6767
If this config is empty, resource attributes are loaded from the `OTEL_RESOURCE_ATTRIBUTES` environmental variable. You can override `service.name` by setting the `OTEL_SERVICE_NAME` environmental variable, and this takes precedence over other configs.
6868

69+
== Customize metrics sender
70+
71+
The `OtlpMeterRegistry` has an `OtlpMetricsSender` abstraction for sending batches of metrics in OTLP protobuf format.
72+
73+
By default, metrics from the OTLP registry are sent via HTTP to the URL specified by `OtlpConfig#url` using an `HttpUrlConnectionSender`.
74+
You may use a different `HttpSender` implementation by creating and configuring an instance of `OtlpHttpMetricsSender` such as in the following example using the `OkHttpSender` instead of the default `HttpUrlConnectionSender`:
75+
76+
[source,java,subs=+attributes]
77+
-----
78+
include::{include-java}/metrics/OtlpMeterRegistryCustomizationTest.java[tags=customizeHttpSender, indent=0]
79+
-----
80+
81+
You can also provide a custom implementation of `OtlpMetricsSender` that does not use HTTP at all.
82+
For instance, if you made a gRPC implementation, you could configure it in the following way.
83+
Micrometer does not currently provide a gRPC implementation of `OtlpMetricsSender`.
84+
85+
[source,java,subs=+attributes]
86+
-----
87+
include::{include-java}/metrics/OtlpMeterRegistryCustomizationTest.java[tags=customGrpcSender, indent=0]
88+
-----
89+
6990
== Supported metrics
7091
https://opentelemetry.io/docs/specs/otel/metrics/data-model/#metric-points[Metric points, window=_blank] define the different data points that are supported in OTLP. Micrometer supports exporting the below data points in OTLP format,
7192

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2025 VMware, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micrometer.docs.metrics;
17+
18+
import io.micrometer.common.lang.NonNullApi;
19+
import io.micrometer.core.ipc.http.OkHttpSender;
20+
import io.micrometer.registry.otlp.OtlpConfig;
21+
import io.micrometer.registry.otlp.OtlpHttpMetricsSender;
22+
import io.micrometer.registry.otlp.OtlpMeterRegistry;
23+
import io.micrometer.registry.otlp.OtlpMetricsSender;
24+
import org.junit.jupiter.api.Test;
25+
26+
import java.util.Map;
27+
28+
class OtlpMeterRegistryCustomizationTest {
29+
30+
@Test
31+
void customizeHttpSender() {
32+
// tag::customizeHttpSender[]
33+
OtlpConfig config = OtlpConfig.DEFAULT;
34+
OtlpHttpMetricsSender httpMetricsSender = new OtlpHttpMetricsSender(new OkHttpSender(), config);
35+
OtlpMeterRegistry meterRegistry = OtlpMeterRegistry.builder(config).metricsSender(httpMetricsSender).build();
36+
// end::customizeHttpSender[]
37+
}
38+
39+
@Test
40+
void customizeOtlpSender() {
41+
// tag::customGrpcSender[]
42+
OtlpConfig config = OtlpConfig.DEFAULT;
43+
OtlpMetricsSender metricsSender = new OtlpGrpcMetricsSender();
44+
OtlpMeterRegistry meterRegistry = OtlpMeterRegistry.builder(config).metricsSender(metricsSender).build();
45+
// end::customGrpcSender[]
46+
}
47+
48+
@NonNullApi
49+
private static class OtlpGrpcMetricsSender implements OtlpMetricsSender {
50+
51+
@Override
52+
public void send(byte[] metricsData, Map<String, String> headers) {
53+
}
54+
55+
}
56+
57+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2025 VMware, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micrometer.registry.otlp;
17+
18+
import java.util.Map;
19+
20+
// intentionally not public while we incubate this concept
21+
// if we want to use this in other registries, it should move to micrometer-core and become public API
22+
interface MetricsSender {
23+
24+
/**
25+
* Send encoded metrics data from a {@link io.micrometer.core.instrument.MeterRegistry
26+
* MeterRegistry}.
27+
* @param metricsData encoded batch of metrics
28+
* @param headers metadata to send as headers with the metrics data
29+
*/
30+
void send(byte[] metricsData, Map<String, String> headers);
31+
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2025 VMware, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micrometer.registry.otlp;
17+
18+
import io.micrometer.common.util.internal.logging.InternalLogger;
19+
import io.micrometer.common.util.internal.logging.InternalLoggerFactory;
20+
import io.micrometer.core.ipc.http.HttpSender;
21+
22+
import java.util.Map;
23+
24+
/**
25+
* An implementation of {@link OtlpMetricsSender} that uses an {@link HttpSender}.
26+
*
27+
* @since 1.15.0
28+
*/
29+
public class OtlpHttpMetricsSender implements OtlpMetricsSender {
30+
31+
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OtlpHttpMetricsSender.class);
32+
33+
private final HttpSender httpSender;
34+
35+
private final OtlpConfig config;
36+
37+
private final String userAgentHeader;
38+
39+
public OtlpHttpMetricsSender(HttpSender httpSender, OtlpConfig config) {
40+
this.httpSender = httpSender;
41+
this.config = config;
42+
this.userAgentHeader = getUserAgentHeader();
43+
}
44+
45+
@Override
46+
public void send(byte[] metricsData, Map<String, String> headers) {
47+
HttpSender.Request.Builder httpRequest = this.httpSender.post(config.url())
48+
.withHeader("User-Agent", userAgentHeader)
49+
.withContent("application/x-protobuf", metricsData);
50+
headers.forEach(httpRequest::withHeader);
51+
try {
52+
HttpSender.Response response = httpRequest.send();
53+
if (!response.isSuccessful()) {
54+
logger.warn(
55+
"Failed to publish metrics (context: {}). Server responded with HTTP status code {} and body {}",
56+
getConfigurationContext(), response.code(), response.body());
57+
}
58+
}
59+
catch (Throwable e) {
60+
logger.warn("Failed to publish metrics (context: {}) ", getConfigurationContext(), e);
61+
}
62+
}
63+
64+
private String getUserAgentHeader() {
65+
String userAgent = "Micrometer-OTLP-Exporter-Java";
66+
String version = getClass().getPackage().getImplementationVersion();
67+
if (version != null) {
68+
userAgent += "/" + version;
69+
}
70+
return userAgent;
71+
}
72+
73+
/**
74+
* Get the configuration context.
75+
* @return A message containing enough information for the log reader to figure out
76+
* what configuration details may have contributed to the failure.
77+
*/
78+
private String getConfigurationContext() {
79+
// While other values may contribute to failures, these two are most common
80+
return "url=" + config.url() + ", resource-attributes=" + config.resourceAttributes();
81+
}
82+
83+
}

0 commit comments

Comments
 (0)