Open raman-babich opened 2 years ago
Thanks for the samples.
You have configured the management server to run on a separate port. This results in a child application context being created for the management server and the servlet and reactive stacks treat context hierarchies differently. The key difference is that WebFlux does not consider beans in ancestor contexts. This means that MetricsWebFilter
is not found and therefore isn't included in the management server's HttpHandler
. As a result, no metrics are recorded for requests to any management endpoints.
This is very similar to https://github.com/spring-projects/spring-boot/issues/27504. Flagging for discussion at a team meeting so that we can consider our options.
My description above is back to front. It's the servlet filter that's not picked up in the child context so metrics don't appear when using MVC but do appear with WebFlux
See https://github.com/spring-projects/spring-boot/issues/27504#issuecomment-1240381510 for an update on this issue. The fact that WebFilter
from the app context are applied to the management context makes me wonder if we shouldn't reconsider the parent/child relationship between those context.
@bclozel is going to review the current status of things now that the ordering issue has been fixed in Framework.
I've reviewed the behavior after recent changes and it's still the same.
In the case of WebFlux, the Framework web infrastructure itself looks for beans by type in the current application context. This lookup operation involves context parent lookup, which explains why WebFilter
beans defined in the main application context are also registered in the management context.
For Spring MVC, the situation is different since Filter
and FilterRegistrationBean
are detected and applied through initiazers (see ServletContextInitializerBeans
). In this case, the ListableBeanFactory
lookup only considers the current bean factory and not the hierarchy. We could change this behavior by using methods in BeanFactoryUtils
, but this is a rather important change and echoes my previous comment.
At this point, the inconsistency is accidental:
FilterRegistrationBean
support in Spring Boot only considers the child context components on purpose so farIs there a workaround in Spring Boot 3? The previous hack that I used:
@Component
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
public class ManagementContextFactoryBeanPostProcessor
implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof ManagementContextFactory managementContextFactory) {
return (ManagementContextFactory) (parent, configurationClasses) -> {
var context = managementContextFactory.createManagementContext(parent, configurationClasses);
if (context instanceof GenericWebApplicationContext genericWebApplicationContext) {
genericWebApplicationContext.registerBean(ForwardedHeaderFilterRegistrationBean.class);
}
return context;
};
}
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
public static class ForwardedHeaderFilterRegistrationBean
extends FilterRegistrationBean<ForwardedHeaderFilter> {
public ForwardedHeaderFilterRegistrationBean() {
setFilter(new ForwardedHeaderFilter());
setOrder(Ordered.HIGHEST_PRECEDENCE);
}
}
}
doesn't seem to work anymore since ManagementContextFactory
is no longer an interface but a final class and the method signature for createManagementContext
has also changed.
A workaround that I used on Spring Boot 3 (tested on 3.1.0):
@Bean
@Primary
public static ManagementContextFactory myServletWebChildContextFactory() {
return new ManagementContextFactory(WebApplicationType.SERVLET, ServletWebServerFactory.class,
ServletWebServerFactoryAutoConfiguration.class, MyForwardedHeaderFilterAutoConfiguration.class);
}
static class MyForwardedHeaderFilterAutoConfiguration {
@Bean
public FilterRegistrationBean<ForwardedHeaderFilter> myForwardedHeaderFilter() {
ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
FilterRegistrationBean<ForwardedHeaderFilter> registration = new FilterRegistrationBean<>(filter);
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR);
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registration;
}
}
It should be enough to add the code above anywhere to the root context.
The idea is to have custom autoconfiguration class that defines the FilterRegistrationBean we need (the one that is actually autocreated in the parent context, but not registered in the management context). We can then create a ManagementContextFactory bean that we annotate with @Primary (to let spring use this one instead) and in the factory method we add our auto-config class ass the last parameter - this way our FilterRegistrationBean bean gets processed within the management context and filter should be registered successfuly.
Spring boot webmvc service and spring boot webflux service have inconsistent behavior for http server requests metrics(prometheus). Webflux service exposes http_server_requests_seconds that includes metrics for management endpoints and service endpoints but for webmvc there are only metrics for service endpoints. I am attaching the minimal example(for spring boot v2.6.9) to be more clear.
spring-http-server-metrics.zip