Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix micrometer bridge auto configuration annotation #13083

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

zeitlinger
Copy link
Member

Fixes #12719 - hopefully

@zeitlinger zeitlinger requested a review from a team as a code owner January 22, 2025 13:05
@zeitlinger zeitlinger changed the title fix annotation fix micrometer bridge auto configuration annotation Jan 22, 2025
Copy link
Member

@trask trask left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you think a test could be added for this?

@zeitlinger
Copy link
Member Author

do you think a test could be added for this?

yes - I even found the test that I broke with this PR.

@zeitlinger zeitlinger self-assigned this Jan 23, 2025
@zeitlinger zeitlinger force-pushed the micrometer-bridge-auto-config branch from a888249 to b9a2ab1 Compare January 27, 2025 13:43
Comment on lines +25 to +33
testing {
suites {
val testPrometheus by registering(JvmTestSuite::class) {
dependencies {
runtimeOnly("io.micrometer:micrometer-registry-prometheus:1.14.3")
}
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the failure for this test without your change to OpenTelemetryMeterRegistry?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just tried... there is no failure - which confirms that the error does not always appear

but I've improved the test

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the failure now (without your change to OpenTelemetryMeterRegistry)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trask the test is still passing with the unchanged code

I don't know how to force spring to use a different resolution order from the provided constraints

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the test is still passing the old code - but I don't have an idea how to make a better test - you'd have to force spring to randomize the bean order within the provided constraints

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, makes sense

@AutoConfigureAfter(SimpleMetricsExportAutoConfiguration.class)
@AutoConfigureAfter({
SimpleMetricsExportAutoConfiguration.class,
PrometheusMetricsExportAutoConfiguration.class
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a way to do this generically for all other MeterRegistries? (not only simple and prometheus?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you want to add more different configs that load different metric registries? sounds too much - @jeanbisutti what's your take?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, asking if there's a way to do it generically, so that it loads after all other MeterRegistries, without having to hard-code specific meter registries

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the problem is that each metric registry is typically in it's own library - see https://mvnrepository.com/artifact/io.micrometer

If you don't add any of those, you only have the simple meter registry.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could try approaching this differently. Instead of providing the otel MeterRegistry bean and relying on spring getting the order right you could call add(MicrometerSingletons.meterRegistry()) on the CompositeMeterRegistry bean.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are multiple ways we could try for solving this. We could make instruments from our registry readable. Though I suspect that what these instruments should return isn't well defined so what we return may be different from what the originally used registry might return. We could also override the find method in our registry so that it doesn't find anything. This should make the code in https://github.com/spring-projects/spring-boot/blob/3.2.x/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/MetricsEndpoint.java#L115-L122 proceed to searching from the next registry. Idk if this method is used for anything else. We could also try to enforce the order of registries with

  @Bean
  static BeanPostProcessor postProcessCompositeMeterRegistry() {
    return new BeanPostProcessor() {
      @Override
      public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (bean instanceof CompositeMeterRegistry) {
          CompositeMeterRegistry original = (CompositeMeterRegistry) bean;
          List<MeterRegistry> list = new ArrayList<>(original.getRegistries());
          // sort otel registry last
          list.sort(
              Comparator.comparingInt(
                  value -> value == MicrometerSingletons.meterRegistry() ? 1 : 0));
          Set<MeterRegistry> registries = new LinkedHashSet<>(list);
          return new CompositeMeterRegistry(
              original.config().clock(), Collections.singletonList(original)) {
            @Override
            public Set<MeterRegistry> getRegistries() {
              return registries;
            }
          };
        }
        return bean;
      }
    };
  }

so that our registry would be last.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't thought about that possibility - but it sounds like this could work 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@laurit do you want to give your solution a try?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zeitlinger I wasn't planning to do anything with this since you were already working on it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@laurit added your solution now

@zeitlinger zeitlinger force-pushed the micrometer-bridge-auto-config branch from 0f347b6 to 84abdcc Compare February 6, 2025 14:05
Comment on lines +25 to +33
testing {
suites {
val testPrometheus by registering(JvmTestSuite::class) {
dependencies {
runtimeOnly("io.micrometer:micrometer-registry-prometheus:1.14.3")
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since there are no source for this suite it won't run any tests as far as I can tell

Comment on lines +71 to +76
try {
Class.forName("io.micrometer.prometheusmetrics.PrometheusMeterRegistry");
match.anyMatch(r -> r.getClass().getSimpleName().equals("PrometheusMeterRegistry"));
} catch (ClassNotFoundException e) {
// not testing prometheus
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when running this test the prometheus dependency is never available as far as I can tell. Consider whether you want to test the prometheus here at all. If you want then instead of adding a suite consider adding the prometheus dependency to the main test dependencies.

Comment on lines +67 to +69
assertThat(((CompositeMeterRegistry) meterRegistry).getRegistries())
.anyMatch(r -> r.getClass().getSimpleName().equals("OpenTelemetryMeterRegistry"))
.anyMatch(r -> r.getClass().getSimpleName().equals("SimpleMeterRegistry"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering that the issue is about ordering consider whether it would make sense to test that these registries appear in the right order.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Micrometer-bridge breaks Spring Actuator metrics
3 participants