Skip to content

Commit b7a344c

Browse files
Log warning about "function" meter re-registration (#6070)
Includes other "function" meters in the double registration check in addition to the existing check for Gauges. See gh-5616 See gh-5688 See gh-5617
1 parent 8149b4d commit b7a344c

File tree

5 files changed

+171
-9
lines changed

5 files changed

+171
-9
lines changed

docs/modules/ROOT/pages/concepts/counters.adoc

+8
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,11 @@ FunctionCounter counter = FunctionCounter
117117
.tags("region", "test") // optional
118118
.register(registry);
119119
----
120+
121+
WARNING: Attempting to construct a function-tracking counter with a primitive number or one of its `java.lang` object forms is always incorrect. These numbers are immutable. Thus, the function-tracking counter cannot ever be changed. Attempting to "re-register" the function-tracking counter with a different number does not work, as the registry maintains only one meter for each unique combination of name and tags. "Re-registering" a function-tracking counter can happen indirectly for example as the result of a `MeterFilter` modifying the name and/or the tags of two different function-tracking counter so that they will be the same after the filter is applied.
122+
123+
Attempting to "re-register" a function-tracking counter will result in a warning like this:
124+
[source]
125+
----
126+
WARNING: This FunctionCounter has been already registered (MeterId{name='my.fc', tags=[]}), the registration will be ignored. Note that subsequent logs will be logged at debug level.
127+
----

docs/modules/ROOT/pages/concepts/gauges.adoc

+6
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ This pattern should be less common than the `DoubleFunction` form. Remember that
4444

4545
WARNING: Attempting to construct a gauge with a primitive number or one of its `java.lang` object forms is always incorrect. These numbers are immutable. Thus, the gauge cannot ever be changed. Attempting to "re-register" the gauge with a different number does not work, as the registry maintains only one meter for each unique combination of name and tags. "Re-registering" a gauge can happen indirectly for example as the result of a `MeterFilter` modifying the name and/or the tags of two different gauges so that they will be the same after the filter is applied.
4646

47+
Attempting to "re-register" a gauge will result in a warning like this:
48+
[source]
49+
----
50+
WARNING: This Gauge has been already registered (MeterId{name='my.gauge', tags=[]}), the registration will be ignored. Note that subsequent logs will be logged at debug level.
51+
----
52+
4753
== Gauge Fluent Builder
4854

4955
The interface contains a fluent builder for gauges:

docs/modules/ROOT/pages/concepts/timers.adoc

+8
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,14 @@ FunctionTimer.builder("cache.gets.latency", cache,
182182
.register(registry);
183183
----
184184

185+
WARNING: Attempting to construct a function-tracking timer with a primitive number or one of its `java.lang` object forms is always incorrect. These numbers are immutable. Thus, the function-tracking timer cannot ever be changed. Attempting to "re-register" the function-tracking timer with a different number does not work, as the registry maintains only one meter for each unique combination of name and tags. "Re-registering" a function-tracking timer can happen indirectly for example as the result of a `MeterFilter` modifying the name and/or the tags of two different function-tracking timer so that they will be the same after the filter is applied.
186+
187+
Attempting to "re-register" a function-tracking timer will result in a warning like this:
188+
[source]
189+
----
190+
WARNING: This FunctionTimer has been already registered (MeterId{name='my.ft', tags=[]}), the registration will be ignored. Note that subsequent logs will be logged at debug level.
191+
----
192+
185193
== Pause Detection
186194

187195
Micrometer uses the `LatencyUtils` package to compensate for https://highscalability.com/blog/2015/10/5/your-load-generator-is-probably-lying-to-you-take-the-red-pi.html[coordinated omission] -- extra latency arising from system and VM pauses that skew your latency statistics downward. Distribution statistics, such as percentiles and SLO counts, are influenced by a pause detector implementation that adds additional latency here and there to compensate for pauses.

micrometer-core/src/main/java/io/micrometer/core/instrument/MeterRegistry.java

+17-9
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@
6767
*/
6868
public abstract class MeterRegistry {
6969

70-
private static final WarnThenDebugLogger gaugeDoubleRegistrationLogger = new WarnThenDebugLogger(
71-
MeterRegistry.class);
70+
private static final WarnThenDebugLogger doubleRegistrationLogger = new WarnThenDebugLogger(MeterRegistry.class);
7271

7372
// @formatter:off
7473
private static final EnumMap<TimeUnit, String> BASE_TIME_UNIT_STRING_CACHE = Arrays.stream(TimeUnit.values())
@@ -640,7 +639,7 @@ private Meter getOrCreateMeter(@Nullable DistributionStatisticConfig config,
640639

641640
Meter m = preFilterIdToMeterMap.get(originalId);
642641
if (m != null && !isStaleId(originalId)) {
643-
checkAndWarnAboutGaugeDoubleRegistration(m);
642+
checkAndWarnAboutDoubleRegistration(m);
644643
return m;
645644
}
646645

@@ -653,7 +652,7 @@ private Meter getOrCreateMeter(@Nullable DistributionStatisticConfig config,
653652
if (isStaleId(originalId)) {
654653
unmarkStaleId(originalId);
655654
}
656-
checkAndWarnAboutGaugeDoubleRegistration(m);
655+
checkAndWarnAboutDoubleRegistration(m);
657656
}
658657
else {
659658
if (isClosed()) {
@@ -712,12 +711,21 @@ private boolean unmarkStaleId(Id originalId) {
712711
return !stalePreFilterIds.isEmpty() && stalePreFilterIds.remove(originalId);
713712
}
714713

715-
private void checkAndWarnAboutGaugeDoubleRegistration(Meter meter) {
716-
if (meter instanceof Gauge) {
717-
gaugeDoubleRegistrationLogger.log(() -> String.format(
718-
"This Gauge has been already registered (%s), the Gauge registration will be ignored.",
719-
meter.getId()));
714+
private void checkAndWarnAboutDoubleRegistration(Meter meter) {
715+
if (meter instanceof Gauge) { // also TimeGauge
716+
warnAboutDoubleRegistration("Gauge", meter.getId());
720717
}
718+
else if (meter instanceof FunctionCounter) {
719+
warnAboutDoubleRegistration("FunctionCounter", meter.getId());
720+
}
721+
else if (meter instanceof FunctionTimer) {
722+
warnAboutDoubleRegistration("FunctionTimer", meter.getId());
723+
}
724+
}
725+
726+
private void warnAboutDoubleRegistration(String type, Meter.Id id) {
727+
doubleRegistrationLogger.log(() -> String
728+
.format("This %s has been already registered (%s), the registration will be ignored.", type, id));
721729
}
722730

723731
private boolean accept(Meter.Id id) {

micrometer-test/src/main/java/io/micrometer/core/tck/MeterRegistryCompatibilityKit.java

+132
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,138 @@ void gaugesCannotBeRegisteredEffectivelyTwice() {
472472

473473
}
474474

475+
@DisplayName("functionCounters")
476+
@Nested
477+
class FunctionCounterTck {
478+
479+
@Test
480+
@DisplayName("functionCounters cannot be registered twice")
481+
void functionCountersCannotBeRegisteredTwice() {
482+
FunctionCounter.builder("my.fc", this, x -> 1).register(registry);
483+
FunctionCounter.builder("my.fc", this, x -> 2).register(registry);
484+
clock(registry).add(step());
485+
486+
assertThat(registry.get("my.fc").functionCounters()).hasSize(1);
487+
assertThat(registry.get("my.fc").functionCounter().count()).isEqualTo(1);
488+
}
489+
490+
@Test
491+
@DisplayName("functionCounters cannot be registered effectively twice")
492+
void functionCountersCannotBeRegisteredEffectivelyTwice() {
493+
registry.config().meterFilter(MeterFilter.ignoreTags("ignored"));
494+
FunctionCounter.builder("my.fc", this, x -> 3).tags("ignored", "3").register(registry);
495+
FunctionCounter.builder("my.fc", this, x -> 4).tags("ignored", "4").register(registry);
496+
clock(registry).add(step());
497+
498+
assertThat(registry.get("my.fc").functionCounters()).hasSize(1);
499+
assertThat(registry.get("my.fc").functionCounter().count()).isEqualTo(3);
500+
}
501+
502+
}
503+
504+
@DisplayName("functionTimers")
505+
@Nested
506+
class FunctionTimerTck {
507+
508+
@Test
509+
@DisplayName("functionTimers cannot be registered twice")
510+
void functionTimersCannotBeRegisteredTwice() {
511+
FunctionTimer.builder("my.ft", this, x -> 1, x -> 100, TimeUnit.MILLISECONDS).register(registry);
512+
FunctionTimer.builder("my.ft", this, x -> 2, x -> 200, TimeUnit.MILLISECONDS).register(registry);
513+
clock(registry).add(step());
514+
515+
assertThat(registry.get("my.ft").functionTimers()).hasSize(1);
516+
assertThat(registry.get("my.ft").functionTimer().count()).isEqualTo(1);
517+
assertThat(registry.get("my.ft").functionTimer().totalTime(TimeUnit.MILLISECONDS)).isEqualTo(100);
518+
}
519+
520+
@Test
521+
@DisplayName("functionTimers cannot be registered effectively twice")
522+
void functionTimersCannotBeRegisteredEffectivelyTwice() {
523+
registry.config().meterFilter(MeterFilter.ignoreTags("ignored"));
524+
FunctionTimer.builder("my.ft", this, x -> 3, x -> 300, TimeUnit.MILLISECONDS)
525+
.tags("ignored", "3")
526+
.register(registry);
527+
FunctionTimer.builder("my.ft", this, x -> 4, x -> 400, TimeUnit.MILLISECONDS)
528+
.tags("ignored", "4")
529+
.register(registry);
530+
clock(registry).add(step());
531+
532+
assertThat(registry.get("my.ft").functionTimers()).hasSize(1);
533+
assertThat(registry.get("my.ft").functionTimer().count()).isEqualTo(3);
534+
assertThat(registry.get("my.ft").functionTimer().totalTime(TimeUnit.MILLISECONDS)).isEqualTo(300);
535+
}
536+
537+
}
538+
539+
@DisplayName("timeGauges")
540+
@Nested
541+
class TimeGaugeTck {
542+
543+
@Test
544+
@DisplayName("timeGauges cannot be registered twice")
545+
void timeGaugesCannotBeRegisteredTwice() {
546+
TimeGauge.builder("my.tg", () -> 100, TimeUnit.MILLISECONDS).register(registry);
547+
TimeGauge.builder("my.tg", () -> 200, TimeUnit.MILLISECONDS).register(registry);
548+
549+
assertThat(registry.get("my.tg").timeGauges()).hasSize(1);
550+
assertThat(registry.get("my.tg").timeGauge().value(TimeUnit.MILLISECONDS)).isEqualTo(100);
551+
}
552+
553+
@Test
554+
@DisplayName("timeGauges cannot be registered effectively twice")
555+
void timeGaugesCannotBeRegisteredEffectivelyTwice() {
556+
registry.config().meterFilter(MeterFilter.ignoreTags("ignored"));
557+
TimeGauge.builder("my.tg", () -> 300, TimeUnit.MILLISECONDS).tags("ignored", "3").register(registry);
558+
TimeGauge.builder("my.tg", () -> 400, TimeUnit.MILLISECONDS).tags("ignored", "4").register(registry);
559+
560+
assertThat(registry.get("my.tg").timeGauges()).hasSize(1);
561+
assertThat(registry.get("my.tg").timeGauge().value(TimeUnit.MILLISECONDS)).isEqualTo(300);
562+
}
563+
564+
}
565+
566+
@DisplayName("multiGauges")
567+
@Nested
568+
class MultiGaugeTck {
569+
570+
@Test
571+
@DisplayName("multiGauges can be registered twice because they take care about de-registering the previous Gauge if overwrite=true")
572+
void multiGaugesCanBeRegisteredTwice() {
573+
MultiGauge gauges = MultiGauge.builder("my.gauge").register(registry);
574+
gauges.register(Collections.singletonList(MultiGauge.Row.of(Tags.of("test", "true"), 1)));
575+
576+
// no re-registration since overwrite is false by default
577+
gauges.register(Collections.singletonList(MultiGauge.Row.of(Tags.of("test", "true"), 2)));
578+
assertThat(registry.get("my.gauge").tags("test", "true").gauges()).hasSize(1);
579+
assertThat(registry.get("my.gauge").tags("test", "true").gauge().value()).isEqualTo(1);
580+
581+
// re-registration since overwrite is set to true
582+
gauges.register(Collections.singletonList(MultiGauge.Row.of(Tags.of("test", "true"), 3)), true);
583+
assertThat(registry.get("my.gauge").tags("test", "true").gauges()).hasSize(1);
584+
assertThat(registry.get("my.gauge").tags("test", "true").gauge().value()).isEqualTo(3);
585+
}
586+
587+
@Test
588+
@DisplayName("multiGauges can be registered effectively twice because they take care about de-registering the previous Gauge if overwrite=true")
589+
void multiGaugesCanBeRegisteredEffectivelyTwice() {
590+
registry.config().meterFilter(MeterFilter.ignoreTags("ignored"));
591+
MultiGauge gauges = MultiGauge.builder("my.gauge").register(registry);
592+
gauges.register(Collections.singletonList(MultiGauge.Row.of(Tags.of("ignored", "true"), 4)));
593+
594+
// no re-registration since overwrite is false by default
595+
gauges.register(Collections.singletonList(MultiGauge.Row.of(Tags.of("ignored", "true"), 5)));
596+
assertThat(registry.get("my.gauge").gauges()).hasSize(1);
597+
assertThat(registry.get("my.gauge").gauge().value()).isEqualTo(4);
598+
599+
// re-registration since overwrite is set to true
600+
gauges.register(Collections.singletonList(MultiGauge.Row.of(Tags.of("ignored", "true"), 6)), true);
601+
assertThat(registry.get("my.gauge").gauges()).hasSize(1);
602+
assertThat(registry.get("my.gauge").gauge().value()).isEqualTo(6);
603+
}
604+
605+
}
606+
475607
@DisplayName("long task timers")
476608
@Nested
477609
class LongTaskTimerTck {

0 commit comments

Comments
 (0)