Skip to content
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
4 changes: 4 additions & 0 deletions docs/content/otel/otlp.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ By default, the `OpenTelemetryExporter` will push metrics every 60 seconds to
the [OpenTelemetryExporter.Builder][builder-javadoc], or at runtime via
[`io.prometheus.exporter.opentelemetry.*`][otel-properties] properties.

The OpenTelemetry exporter also honors the shared [`io.prometheus.exporter.filter.*`][exporter-filter-properties] metric-name
filter properties.

In addition to the Prometheus Java client configuration, the exporter also recognizes standard
OpenTelemetry configuration. For example, you can set
the [OTEL_EXPORTER_OTLP_METRICS_ENDPOINT](https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/#otel_exporter_otlp_metrics_endpoint)
Expand All @@ -62,4 +65,5 @@ OTel collector, and a Prometheus server.
[builder-javadoc]: /client_java/api/io/prometheus/metrics/exporter/opentelemetry/OpenTelemetryExporter.Builder.html
[opentelemetry-example]: https://github.com/prometheus/client_java/tree/main/examples/example-exporter-opentelemetry
[otel-pipeline]: /client_java/images/otel-pipeline.png
[exporter-filter-properties]: {{< relref "../config/config.md#exporter-filter-properties" >}}
[otel-properties]: {{< relref "../config/config.md#exporter-opentelemetry-properties" >}}
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ static MetricReader createReader(
MetricReader reader = requireNonNull(readerRef.get());
boolean preserveNames = resolvePreserveNames(builder, config);
reader.register(
new PrometheusMetricProducer(
registry, instrumentationScopeInfo, getResourceField(sdk), preserveNames));
PrometheusMetricProducer.builder(
registry, instrumentationScopeInfo, getResourceField(sdk), preserveNames)
.exporterFilterProperties(config.getExporterFilterProperties())
.build());
return reader;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import io.opentelemetry.sdk.metrics.export.CollectionRegistration;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.resources.ResourceBuilder;
import io.prometheus.metrics.config.ExporterFilterProperties;
import io.prometheus.metrics.exporter.opentelemetry.otelmodel.MetricDataFactory;
import io.prometheus.metrics.model.registry.MetricNameFilter;
import io.prometheus.metrics.model.registry.PrometheusRegistry;
import io.prometheus.metrics.model.snapshots.CounterSnapshot;
import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
Expand All @@ -22,6 +24,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
import javax.annotation.Nullable;

class PrometheusMetricProducer implements CollectionRegistration {
Expand All @@ -30,29 +33,65 @@ class PrometheusMetricProducer implements CollectionRegistration {
private final Resource resource;
private final InstrumentationScopeInfo instrumentationScopeInfo;
private final boolean preserveNames;
@Nullable private final Predicate<String> nameFilter;

public PrometheusMetricProducer(
private PrometheusMetricProducer(
PrometheusRegistry registry,
InstrumentationScopeInfo instrumentationScopeInfo,
Resource resource,
boolean preserveNames) {
boolean preserveNames,
@Nullable Predicate<String> nameFilter) {
this.registry = registry;
this.instrumentationScopeInfo = instrumentationScopeInfo;
this.resource = resource;
this.preserveNames = preserveNames;
this.nameFilter = nameFilter;
}

/**
* Creates a builder for a producer with no metric name filtering by default, i.e. all metrics in
* {@code registry} are exported unless filter properties are configured on the builder.
*/
static Builder builder(
PrometheusRegistry registry,
InstrumentationScopeInfo instrumentationScopeInfo,
Resource resource,
boolean preserveNames) {
return new Builder(registry, instrumentationScopeInfo, resource, preserveNames);
}

/**
* Builds a name filter from {@code io.prometheus.exporter.filter.*} properties, mirroring how
* {@code PrometheusScrapeHandler} builds its filter for the Servlet/HTTPServer exporters so that
* filtering config behaves consistently across exporters.
*
* <p>OpenTelemetry's own Views API also supports filtering and aggregation, and may be preferable
* for OpenTelemetry-specific deployments; this filter is intended for users who want the same
* {@code io.prometheus.exporter.filter.*} config to apply regardless of which exporter they use.
*
* @return {@code null} if no filter properties are set, to avoid the overhead of testing every
* metric name against a filter that matches everything.
*/
@Nullable
private static Predicate<String> makeNameFilter(ExporterFilterProperties props) {
if (props.getAllowedMetricNames() == null
&& props.getExcludedMetricNames() == null
&& props.getAllowedMetricNamePrefixes() == null
&& props.getExcludedMetricNamePrefixes() == null) {
return null;
}
return MetricNameFilter.builder()
.nameMustBeEqualTo(props.getAllowedMetricNames())
.nameMustNotBeEqualTo(props.getExcludedMetricNames())
.nameMustStartWith(props.getAllowedMetricNamePrefixes())
.nameMustNotStartWith(props.getExcludedMetricNamePrefixes())
.build();
}

@Override
public Collection<MetricData> collectAllMetrics() {
// Note: Currently all metrics from the registry are exported. To add metric filtering
// similar to the Servlet exporter, one could:
// 1. Add filter properties to ExporterOpenTelemetryProperties (allowedNames, excludedNames,
// etc.)
// 2. Convert these properties to a Predicate<String> using MetricNameFilter.builder()
// 3. Call registry.scrape(filter) instead of registry.scrape()
// OpenTelemetry also provides its own Views API for filtering and aggregation, which may be
// preferred for OpenTelemetry-specific deployments.
MetricSnapshots snapshots = registry.scrape();
MetricSnapshots snapshots =
nameFilter != null ? registry.scrape(nameFilter) : registry.scrape();
Resource resourceWithTargetInfo = resource.merge(resourceFromTargetInfo(snapshots));
InstrumentationScopeInfo scopeFromInfo = instrumentationScopeFromOtelScopeInfo(snapshots);
List<MetricData> result = new ArrayList<>(snapshots.size());
Expand Down Expand Up @@ -142,4 +181,37 @@ private void addUnlessNull(List<MetricData> result, @Nullable MetricData data) {
result.add(data);
}
}

static class Builder {
private final PrometheusRegistry registry;
private final Resource resource;
private final InstrumentationScopeInfo instrumentationScopeInfo;
private final boolean preserveNames;
private ExporterFilterProperties filterProperties = ExporterFilterProperties.builder().build();

private Builder(
PrometheusRegistry registry,
InstrumentationScopeInfo instrumentationScopeInfo,
Resource resource,
boolean preserveNames) {
this.registry = registry;
this.instrumentationScopeInfo = instrumentationScopeInfo;
this.resource = resource;
this.preserveNames = preserveNames;
}

Builder exporterFilterProperties(ExporterFilterProperties filterProperties) {
this.filterProperties = filterProperties;
return this;
}

PrometheusMetricProducer build() {
return new PrometheusMetricProducer(
registry,
instrumentationScopeInfo,
resource,
preserveNames,
makeNameFilter(filterProperties));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions;
import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension;
import io.prometheus.metrics.config.ExporterFilterProperties;
import io.prometheus.metrics.core.metrics.Counter;
import io.prometheus.metrics.core.metrics.Gauge;
import io.prometheus.metrics.core.metrics.Histogram;
Expand Down Expand Up @@ -46,11 +47,12 @@ void setUp() throws IllegalAccessException, NoSuchFieldException {
MetricReader reader = (MetricReader) field.get(testing);

PrometheusMetricProducer prometheusMetricProducer =
new PrometheusMetricProducer(
registry,
InstrumentationScopeInfo.create("test"),
Resource.create(Attributes.builder().put("staticRes", "value").build()),
false);
PrometheusMetricProducer.builder(
registry,
InstrumentationScopeInfo.create("test"),
Resource.create(Attributes.builder().put("staticRes", "value").build()),
false)
.build();

reader.register(prometheusMetricProducer);
}
Expand Down Expand Up @@ -332,11 +334,12 @@ void preserveNamesWithUnit() {
InMemoryMetricReader reader = InMemoryMetricReader.create();
PrometheusRegistry preserveRegistry = new PrometheusRegistry();
reader.register(
new PrometheusMetricProducer(
preserveRegistry,
InstrumentationScopeInfo.create("test"),
Resource.create(Attributes.builder().put("staticRes", "value").build()),
true));
PrometheusMetricProducer.builder(
preserveRegistry,
InstrumentationScopeInfo.create("test"),
Resource.create(Attributes.builder().put("staticRes", "value").build()),
true)
.build());

Counter.builder().name("req").unit(Unit.BYTES).register(preserveRegistry).inc();

Expand All @@ -350,11 +353,12 @@ void preserveNamesWithUnitAlreadyInName() {
InMemoryMetricReader reader = InMemoryMetricReader.create();
PrometheusRegistry preserveRegistry = new PrometheusRegistry();
reader.register(
new PrometheusMetricProducer(
preserveRegistry,
InstrumentationScopeInfo.create("test"),
Resource.create(Attributes.builder().put("staticRes", "value").build()),
true));
PrometheusMetricProducer.builder(
preserveRegistry,
InstrumentationScopeInfo.create("test"),
Resource.create(Attributes.builder().put("staticRes", "value").build()),
true)
.build());

Counter.builder().name("req_bytes").unit(Unit.BYTES).register(preserveRegistry).inc();

Expand All @@ -368,11 +372,12 @@ void preserveNamesWithoutUnit() {
InMemoryMetricReader reader = InMemoryMetricReader.create();
PrometheusRegistry preserveRegistry = new PrometheusRegistry();
reader.register(
new PrometheusMetricProducer(
preserveRegistry,
InstrumentationScopeInfo.create("test"),
Resource.create(Attributes.builder().put("staticRes", "value").build()),
true));
PrometheusMetricProducer.builder(
preserveRegistry,
InstrumentationScopeInfo.create("test"),
Resource.create(Attributes.builder().put("staticRes", "value").build()),
true)
.build());

Counter.builder().name("events_total").register(preserveRegistry).inc();

Expand All @@ -381,6 +386,50 @@ void preserveNamesWithoutUnit() {
OpenTelemetryAssertions.assertThat(metrics.get(0)).hasName("events_total");
}

@Test
void metricNameFilterExcludedNames() {
InMemoryMetricReader reader = InMemoryMetricReader.create();
PrometheusRegistry filteredRegistry = new PrometheusRegistry();
reader.register(
PrometheusMetricProducer.builder(
filteredRegistry,
InstrumentationScopeInfo.create("test"),
Resource.create(Attributes.builder().put("staticRes", "value").build()),
false)
.exporterFilterProperties(
ExporterFilterProperties.builder().excludedNames("secret_total").build())
.build());

Counter.builder().name("secret").register(filteredRegistry).inc();
Counter.builder().name("public").register(filteredRegistry).inc();

List<MetricData> metrics = new ArrayList<>(reader.collectAllMetrics());
assertThat(metrics).hasSize(1);
OpenTelemetryAssertions.assertThat(metrics.get(0)).hasName("public");
}

@Test
void metricNameFilterAllowedPrefixes() {
InMemoryMetricReader reader = InMemoryMetricReader.create();
PrometheusRegistry filteredRegistry = new PrometheusRegistry();
reader.register(
PrometheusMetricProducer.builder(
filteredRegistry,
InstrumentationScopeInfo.create("test"),
Resource.create(Attributes.builder().put("staticRes", "value").build()),
false)
.exporterFilterProperties(
ExporterFilterProperties.builder().allowedPrefixes("http_").build())
.build());

Counter.builder().name("http_requests").register(filteredRegistry).inc();
Counter.builder().name("jvm_threads").register(filteredRegistry).inc();

List<MetricData> metrics = new ArrayList<>(reader.collectAllMetrics());
assertThat(metrics).hasSize(1);
OpenTelemetryAssertions.assertThat(metrics.get(0)).hasName("http_requests");
}

private MetricAssert metricAssert() {
List<MetricData> metrics = testing.getMetrics();
assertThat(metrics).hasSize(1);
Expand Down
Loading