Skip to content

Commit 646892e

Browse files
adinauercodex
andcommitted
feat(metrics): [Micrometer 5] Add ignored metric names
Let applications suppress noisy metrics before event processing using case-insensitive names or full regular expressions. Support external configuration, Android manifest metadata, and Spring Boot properties. Default Spring Boot applications to excluding Logback and Log4j2 counters to prevent logging-driven queue overload. Preserve explicit lists, including empty lists, and allow user callbacks to append, replace, or clear defaults. Keep plain Java and Android defaults unfiltered. Reuse the core matcher in a Sentry-local Micrometer MeterFilter to avoid registration and recording overhead without affecting other registries. Preserve partial exports for meters with multiple possible output names. Cover configuration, matching, name conversion, and filtering behavior with tests. Verify that filtering logging counters prevents queue overflow and preserves business metrics during a 100,000-log-per-second workload. Co-Authored-By: Codex <noreply@openai.com>
1 parent 86f7936 commit 646892e

25 files changed

Lines changed: 1135 additions & 8 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,27 @@
55
### Features
66

77
- Add a Micrometer metrics integration with opt-in Spring Boot support ([#6116](https://github.com/getsentry/sentry-java/pull/6116))
8+
- Add `options.getMetrics().setIgnoredMetrics(...)` to filter metric names before processing, including early Micrometer filtering
9+
- Configure names or full regular-expression patterns through the Java API, Android manifest, `sentry.properties`, or Spring Boot's `application.properties`. Use `[.]` to match a literal dot in a pattern.
10+
- **Spring Boot 2, 3, and 4 default:** ignore `logback.events` and `log4j2.events` metrics to avoid logging-driven metric queue overload. An explicit list replaces these defaults; an empty list disables filtering. Outside Spring Boot, no metric names are ignored by default. Actual log messages are unaffected.
11+
- **AndroidManifest.xml:** add `io.sentry.metrics.ignored-metrics` under `<application>`:
12+
```xml
13+
<meta-data
14+
android:name="io.sentry.metrics.ignored-metrics"
15+
android:value="noisy[.]metric" />
16+
```
17+
- **sentry.properties:** use `metrics.ignored-metrics` (requires external configuration to be enabled with `options.setEnableExternalConfiguration(true)`):
18+
```properties
19+
metrics.ignored-metrics=noisy[.]metric
20+
```
21+
- **Spring Boot application.properties:** use `sentry.metrics.ignored-metrics`. Include the logging patterns to retain the defaults while adding another exclusion:
22+
```properties
23+
sentry.metrics.ignored-metrics=logback[.]events,log4j2[.]events,noisy[.]metric
24+
```
25+
To disable all name filters, including the Spring Boot defaults:
26+
```properties
27+
sentry.metrics.ignored-metrics=
28+
```
829

930
## 8.56.0
1031

‎sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ final class ManifestMetadataReader {
168168
static final String ENABLE_LOGS = "io.sentry.logs.enabled";
169169

170170
static final String ENABLE_METRICS = "io.sentry.metrics.enabled";
171+
static final String IGNORED_METRICS = "io.sentry.metrics.ignored-metrics";
171172

172173
static final String ENABLE_AUTO_TRACE_ID_GENERATION =
173174
"io.sentry.traces.enable-auto-id-generation";
@@ -717,6 +718,10 @@ static void applyMetadata(
717718
.getMetrics()
718719
.setEnabled(
719720
readBool(metadata, logger, ENABLE_METRICS, options.getMetrics().isEnabled()));
721+
final @Nullable List<String> ignoredMetrics = readList(metadata, logger, IGNORED_METRICS);
722+
if (ignoredMetrics != null) {
723+
options.getMetrics().setIgnoredMetrics(ignoredMetrics);
724+
}
720725

721726
final @NotNull SentryFeedbackOptions feedbackOptions = options.getFeedbackOptions();
722727
feedbackOptions.setNameRequired(

‎sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2016,6 +2016,48 @@ class ManifestMetadataReaderTest {
20162016
assertTrue(fixture.options.logs.isEnabled)
20172017
}
20182018

2019+
@Test
2020+
fun `applyMetadata reads ignored metrics`() {
2021+
val context =
2022+
fixture.getContext(
2023+
metaData = bundleOf(ManifestMetadataReader.IGNORED_METRICS to "logback.events,jvm[.].*")
2024+
)
2025+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
2026+
assertThat(fixture.options.metrics.ignoredMetrics)
2027+
.containsExactly(FilterString("logback.events"), FilterString("jvm[.].*"))
2028+
}
2029+
2030+
@Test
2031+
fun `applyMetadata does not ignore any metrics by default`() {
2032+
ManifestMetadataReader.applyMetadata(
2033+
fixture.getContext(),
2034+
fixture.options,
2035+
fixture.buildInfoProvider,
2036+
)
2037+
assertThat(fixture.options.metrics.ignoredMetrics).isNull()
2038+
}
2039+
2040+
@Test
2041+
fun `absent manifest ignored metrics preserve configured filters`() {
2042+
fixture.options.metrics.addIgnoredMetric("logback.events")
2043+
ManifestMetadataReader.applyMetadata(
2044+
fixture.getContext(),
2045+
fixture.options,
2046+
fixture.buildInfoProvider,
2047+
)
2048+
assertThat(fixture.options.metrics.ignoredMetrics)
2049+
.containsExactly(FilterString("logback.events"))
2050+
}
2051+
2052+
@Test
2053+
fun `empty manifest ignored metrics clear configured filters`() {
2054+
fixture.options.metrics.addIgnoredMetric("logback.events")
2055+
val context =
2056+
fixture.getContext(metaData = bundleOf(ManifestMetadataReader.IGNORED_METRICS to ""))
2057+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
2058+
assertThat(fixture.options.metrics.ignoredMetrics).isEmpty()
2059+
}
2060+
20192061
@Test
20202062
fun `applyMetadata reads metrics enabled and keep default value if not found`() {
20212063
// Arrange

‎sentry-micrometer/README.md‎

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@ Auto-configuration requires Sentry to be initialized and backs off when the appl
4848
its own `SentryMeterRegistry` bean. Spring Boot adds the registry to its primary composite registry,
4949
applies compatible `MeterRegistryCustomizer` beans, and closes it with the application context.
5050
Supported metrics registered automatically by Spring Boot Actuator—including HTTP server, JVM,
51-
process, and logging metrics—are forwarded through the same registry. Set the polling interval to
52-
zero to keep immediate forwarding enabled without a polling worker; passive auto-generated meters
53-
then remain registered but are not sent.
51+
and process metrics—are forwarded through the same registry. Sentry's Spring Boot auto-configuration
52+
ignores Logback and Log4j2 logging counters by default; see [Filtering and volume](#filtering-and-volume).
53+
Set the polling interval to zero to keep immediate forwarding enabled without a polling worker;
54+
passive auto-generated meters then remain registered but are not sent.
5455

5556
## Metric mappings
5657

@@ -108,9 +109,60 @@ context that changed a backing value.
108109

109110
## Filtering and volume
110111

111-
Each active timer or distribution-summary recording creates one Sentry metric before the existing
112-
Sentry metrics batch processor batches it for transport. Apply Micrometer `MeterFilter`s directly
113-
to the Sentry registry to control volume and cardinality without affecting other registries:
112+
Each counter increment, timer recording, or distribution-summary recording creates one Sentry
113+
metric before the metrics batch processor batches it for transport. This includes each log event
114+
counted by Micrometer's Logback or Log4j2 binders. High logging volume can fill the shared metrics
115+
queue and cause other metrics to be dropped.
116+
117+
Outside Spring Boot, no metric names are ignored by default. Configure ignored names before
118+
registering meters:
119+
120+
```java
121+
options.getMetrics().setIgnoredMetrics(Arrays.asList("logback[.]events", "log4j2[.]events"));
122+
```
123+
124+
Sentry's Spring Boot 2, 3, and 4 auto-configuration defaults to `logback[.]events` and
125+
`log4j2[.]events` when the ignored-metrics list is unset. These patterns match the logging counters
126+
`logback.events` and `log4j2.events` without treating the dots as regex wildcards. This filters only
127+
metrics, not actual log messages, Sentry Logs, breadcrumbs, or error events.
128+
129+
An explicit list replaces these defaults. Include them to retain logging exclusions alongside
130+
custom filters:
131+
132+
```properties
133+
sentry.metrics.ignored-metrics=logback[.]events,log4j2[.]events,my.noisy.metric
134+
```
135+
136+
To disable all name filters, including the logging defaults:
137+
138+
```properties
139+
sentry.metrics.ignored-metrics=
140+
```
141+
142+
Defaults are applied before `Sentry.OptionsConfiguration` callbacks. A callback can append filters
143+
with `options.getMetrics().addIgnoredMetric(...)`, replace them with `setIgnoredMetrics(...)`, or
144+
clear them with an empty list or `null`. Enabled external configuration is merged afterward.
145+
These Boot defaults apply even when Micrometer export is disabled, so manually recorded Sentry
146+
metrics with the same names are also filtered.
147+
148+
For `sentry.properties`, use `metrics.ignored-metrics`; the environment variable is
149+
`SENTRY_METRICS_IGNORED_METRICS`. Android supports the manifest metadata key
150+
`io.sentry.metrics.ignored-metrics` with a comma-separated string value.
151+
152+
Patterns match final exported Sentry names after Micrometer naming conventions, using
153+
case-insensitive exact matches or full regular-expression matches. Derived names such as
154+
`task.active`, `task.duration`, `task.count`, and `task.total_time` are matched individually.
155+
The option applies to manual metrics too, but does not affect other Micrometer registries.
156+
All Micrometer metrics share `auto.metrics.micrometer` as their origin, so origin cannot
157+
select just logging metrics.
158+
159+
Sentry checks ignored names before creating metric events. The registry also denies registration
160+
when all possible exported names of a meter are ignored, avoiding recording and polling overhead.
161+
Changing the list later still filters captured metrics, but does not remove existing meters or
162+
reactivate previously returned no-op meters. Configure filters before registration for the lowest
163+
overhead. Intentional ignores do not generate client reports.
164+
165+
Apply Micrometer `MeterFilter`s directly to the Sentry registry for Micrometer-only filtering:
114166

115167
```java
116168
sentryRegistry.config().meterFilter(MeterFilter.denyNameStartsWith("jvm.buffer"));
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package io.sentry.micrometer;
2+
3+
import io.micrometer.core.instrument.Meter;
4+
import io.micrometer.core.instrument.config.MeterFilter;
5+
import io.micrometer.core.instrument.config.MeterFilterReply;
6+
import io.sentry.FilterString;
7+
import io.sentry.Sentry;
8+
import io.sentry.util.MetricsUtils;
9+
import java.util.List;
10+
import java.util.Locale;
11+
import org.jetbrains.annotations.NotNull;
12+
import org.jetbrains.annotations.Nullable;
13+
14+
final class SentryIgnoredMetricsFilter implements MeterFilter {
15+
private final @NotNull SentryMeterRegistry registry;
16+
17+
SentryIgnoredMetricsFilter(final @NotNull SentryMeterRegistry registry) {
18+
this.registry = registry;
19+
}
20+
21+
@Override
22+
public @NotNull MeterFilterReply accept(final @NotNull Meter.Id id) {
23+
final @Nullable List<FilterString> ignoredMetrics =
24+
Sentry.getCurrentScopes().getOptions().getMetrics().getIgnoredMetrics();
25+
if (ignoredMetrics == null || ignoredMetrics.isEmpty()) {
26+
return MeterFilterReply.NEUTRAL;
27+
}
28+
final boolean ignored;
29+
switch (id.getType()) {
30+
case LONG_TASK_TIMER:
31+
final String longTaskName = getTimeMeterName(id);
32+
ignored =
33+
MetricsUtils.isIgnored(ignoredMetrics, longTaskName + ".active")
34+
&& MetricsUtils.isIgnored(ignoredMetrics, longTaskName + ".duration");
35+
break;
36+
case TIMER:
37+
// Timer and FunctionTimer share a type. Keep the meter unless all possible outputs
38+
// are ignored; MetricsApi filters each individual output when it is captured.
39+
final String timerName = getTimeMeterName(id);
40+
ignored =
41+
MetricsUtils.isIgnored(ignoredMetrics, timerName)
42+
&& MetricsUtils.isIgnored(ignoredMetrics, timerName + ".count")
43+
&& MetricsUtils.isIgnored(ignoredMetrics, timerName + ".total_time");
44+
break;
45+
case GAUGE:
46+
// Gauge and TimeGauge also share a type, but only TimeGauge changes the base unit.
47+
ignored =
48+
MetricsUtils.isIgnored(
49+
ignoredMetrics, id.getConventionName(registry.config().namingConvention()))
50+
&& MetricsUtils.isIgnored(ignoredMetrics, getTimeMeterName(id));
51+
break;
52+
default:
53+
ignored =
54+
MetricsUtils.isIgnored(
55+
ignoredMetrics, id.getConventionName(registry.config().namingConvention()));
56+
break;
57+
}
58+
return ignored ? MeterFilterReply.DENY : MeterFilterReply.NEUTRAL;
59+
}
60+
61+
private @NotNull String getTimeMeterName(final @NotNull Meter.Id id) {
62+
// MeterRegistry normalizes time-meter IDs after its filters run, using the default locale.
63+
return id.withBaseUnit(registry.getBaseTimeUnit().toString().toLowerCase(Locale.getDefault()))
64+
.getConventionName(registry.config().namingConvention());
65+
}
66+
}

‎sentry-micrometer/src/main/java/io/sentry/micrometer/SentryMeterRegistry.java‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,10 @@ public SentryMeterRegistry(final long pollIntervalMillis) {
7777
"A scheduler is required when passive polling is enabled.");
7878
}
7979
this.scheduler = pollIntervalMillis == 0 ? null : scheduler;
80-
config().namingConvention(NamingConvention.identity).onMeterRemoved(this::onMeterRemoved);
80+
config()
81+
.namingConvention(NamingConvention.identity)
82+
.meterFilter(new SentryIgnoredMetricsFilter(this))
83+
.onMeterRemoved(this::onMeterRemoved);
8184
addIntegrationToSdkVersion(INTEGRATION_NAME);
8285
if (this.scheduler == null) {
8386
pollingTask = null;

0 commit comments

Comments
 (0)