diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/tomcat/TomcatMetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/tomcat/TomcatMetricsAutoConfiguration.java index 03d3a67516..7258d98c45 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/tomcat/TomcatMetricsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/tomcat/TomcatMetricsAutoConfiguration.java @@ -16,20 +16,16 @@ package org.springframework.boot.actuate.autoconfigure.metrics.web.tomcat; -import java.util.Collections; - +import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.binder.tomcat.TomcatMetrics; -import org.apache.catalina.Context; import org.apache.catalina.Manager; +import org.springframework.boot.actuate.metrics.web.tomcat.TomcatMetricsBinder; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; -import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory; -import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; -import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -44,30 +40,11 @@ import org.springframework.context.annotation.Configuration; @ConditionalOnClass({ TomcatMetrics.class, Manager.class }) public class TomcatMetricsAutoConfiguration { - private volatile Context context; - @Bean - @ConditionalOnMissingBean - public TomcatMetrics tomcatMetrics() { - return new TomcatMetrics( - (this.context != null) ? this.context.getManager() : null, - Collections.emptyList()); - } - - @Bean - @ConditionalOnWebApplication(type = Type.SERVLET) - public WebServerFactoryCustomizer contextCapturingServletTomcatCustomizer() { - return (tomcatFactory) -> tomcatFactory.addContextCustomizers(this::setContext); - } - - @Bean - @ConditionalOnWebApplication(type = Type.REACTIVE) - public WebServerFactoryCustomizer contextCapturingReactiveTomcatCustomizer() { - return (tomcatFactory) -> tomcatFactory.addContextCustomizers(this::setContext); - } - - private void setContext(Context context) { - this.context = context; + @ConditionalOnBean(MeterRegistry.class) + @ConditionalOnMissingBean({ TomcatMetrics.class, TomcatMetricsBinder.class }) + public TomcatMetricsBinder tomcatMetricsBinder(MeterRegistry meterRegistry) { + return new TomcatMetricsBinder(meterRegistry); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/tomcat/TomcatMetricsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/tomcat/TomcatMetricsAutoConfigurationTests.java index 98012c7000..9ce083fa60 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/tomcat/TomcatMetricsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/tomcat/TomcatMetricsAutoConfigurationTests.java @@ -18,22 +18,23 @@ package org.springframework.boot.actuate.autoconfigure.metrics.web.tomcat; import java.util.Collections; -import io.micrometer.core.instrument.binder.MeterBinder; +import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.binder.tomcat.TomcatMetrics; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.Test; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.actuate.metrics.web.tomcat.TomcatMetricsBinder; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; +import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext; -import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.server.reactive.HttpHandler; @@ -50,37 +51,20 @@ public class TomcatMetricsAutoConfigurationTests { @Test public void autoConfiguresTomcatMetricsWithEmbeddedServletTomcat() { - new WebApplicationContextRunner( - AnnotationConfigServletWebServerApplicationContext::new) - .withConfiguration(AutoConfigurations.of( - TomcatMetricsAutoConfiguration.class, - ServletWebServerFactoryAutoConfiguration.class)) - .withUserConfiguration(ServletWebServerConfiguration.class) - .run((context) -> { - assertThat(context).hasSingleBean(TomcatMetrics.class); - SimpleMeterRegistry registry = new SimpleMeterRegistry(); - context.getBean(TomcatMetrics.class).bindTo(registry); - assertThat( - registry.find("tomcat.sessions.active.max").meter()) - .isNotNull(); - assertThat(registry.find("tomcat.threads.current").meter()) - .isNotNull(); - }); - } - - @Test - public void sessionMetricsAreAvailableWhenEarlyMeterBinderInitializationOccurs() { new WebApplicationContextRunner( AnnotationConfigServletWebServerApplicationContext::new) .withConfiguration(AutoConfigurations.of( TomcatMetricsAutoConfiguration.class, ServletWebServerFactoryAutoConfiguration.class)) .withUserConfiguration(ServletWebServerConfiguration.class, - EarlyMeterBinderInitializationConfiguration.class) + MeterRegistryConfiguration.class) .run((context) -> { - assertThat(context).hasSingleBean(TomcatMetrics.class); - SimpleMeterRegistry registry = new SimpleMeterRegistry(); - context.getBean(TomcatMetrics.class).bindTo(registry); + context.publishEvent( + new ApplicationStartedEvent(new SpringApplication(), + null, context.getSourceApplicationContext())); + assertThat(context).hasSingleBean(TomcatMetricsBinder.class); + SimpleMeterRegistry registry = context + .getBean(SimpleMeterRegistry.class); assertThat( registry.find("tomcat.sessions.active.max").meter()) .isNotNull(); @@ -96,11 +80,14 @@ public class TomcatMetricsAutoConfigurationTests { .withConfiguration(AutoConfigurations.of( TomcatMetricsAutoConfiguration.class, ReactiveWebServerFactoryAutoConfiguration.class)) - .withUserConfiguration(ReactiveWebServerConfiguration.class) + .withUserConfiguration(ReactiveWebServerConfiguration.class, + MeterRegistryConfiguration.class) .run((context) -> { - assertThat(context).hasSingleBean(TomcatMetrics.class); - SimpleMeterRegistry registry = new SimpleMeterRegistry(); - context.getBean(TomcatMetrics.class).bindTo(registry); + context.publishEvent( + new ApplicationStartedEvent(new SpringApplication(), + null, context.getSourceApplicationContext())); + SimpleMeterRegistry registry = context + .getBean(SimpleMeterRegistry.class); assertThat( registry.find("tomcat.sessions.active.max").meter()) .isNotNull(); @@ -114,7 +101,22 @@ public class TomcatMetricsAutoConfigurationTests { new WebApplicationContextRunner() .withConfiguration( AutoConfigurations.of(TomcatMetricsAutoConfiguration.class)) - .run((context) -> assertThat(context).hasSingleBean(TomcatMetrics.class)); + .withUserConfiguration(MeterRegistryConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(TomcatMetricsBinder.class); + }); + } + + @Test + public void allowsCustomTomcatMetricsBinderToBeUsed() { + new WebApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of(TomcatMetricsAutoConfiguration.class)) + .withUserConfiguration(MeterRegistryConfiguration.class, + CustomTomcatMetricsBinder.class) + .run((context) -> assertThat(context) + .hasSingleBean(TomcatMetricsBinder.class) + .hasBean("customTomcatMetricsBinder")); } @Test @@ -122,11 +124,23 @@ public class TomcatMetricsAutoConfigurationTests { new WebApplicationContextRunner() .withConfiguration( AutoConfigurations.of(TomcatMetricsAutoConfiguration.class)) - .withUserConfiguration(CustomTomcatMetrics.class) - .run((context) -> assertThat(context).hasSingleBean(TomcatMetrics.class) + .withUserConfiguration(MeterRegistryConfiguration.class, + CustomTomcatMetrics.class) + .run((context) -> assertThat(context) + .doesNotHaveBean(TomcatMetricsBinder.class) .hasBean("customTomcatMetrics")); } + @Configuration + static class MeterRegistryConfiguration { + + @Bean + public SimpleMeterRegistry meterRegistry() { + return new SimpleMeterRegistry(); + } + + } + @Configuration static class ServletWebServerConfiguration { @@ -163,11 +177,12 @@ public class TomcatMetricsAutoConfigurationTests { } @Configuration - static class EarlyMeterBinderInitializationConfiguration { + static class CustomTomcatMetricsBinder { @Bean - public ServletContextInitializer earlyInitializer(ApplicationContext context) { - return (servletContext) -> context.getBeansOfType(MeterBinder.class); + public TomcatMetricsBinder customTomcatMetricsBinder( + MeterRegistry meterRegistry) { + return new TomcatMetricsBinder(meterRegistry); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/tomcat/TomcatMetricsBinder.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/tomcat/TomcatMetricsBinder.java new file mode 100644 index 0000000000..ef9178d80b --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/tomcat/TomcatMetricsBinder.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.metrics.web.tomcat; + +import java.util.Collections; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.binder.tomcat.TomcatMetrics; +import org.apache.catalina.Container; +import org.apache.catalina.Context; +import org.apache.catalina.Manager; + +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.boot.web.context.WebServerApplicationContext; +import org.springframework.boot.web.embedded.tomcat.TomcatWebServer; +import org.springframework.boot.web.server.WebServer; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; + +/** + * Binds {@link TomcatMetrics} in response to the {@link ApplicationStartedEvent}. + * + * @author Andy Wilkinson + * @since 2.1.0 + */ +public class TomcatMetricsBinder implements ApplicationListener { + + private final MeterRegistry meterRegistry; + + private final Iterable tags; + + public TomcatMetricsBinder(MeterRegistry meterRegistry) { + this(meterRegistry, Collections.emptyList()); + } + + public TomcatMetricsBinder(MeterRegistry meterRegistry, Iterable tags) { + this.meterRegistry = meterRegistry; + this.tags = tags; + } + + @Override + public void onApplicationEvent(ApplicationStartedEvent event) { + ApplicationContext applicationContext = event.getApplicationContext(); + Manager manager = findManager(applicationContext); + new TomcatMetrics(manager, this.tags).bindTo(this.meterRegistry); + } + + private Manager findManager(ApplicationContext applicationContext) { + if (applicationContext instanceof WebServerApplicationContext) { + WebServer webServer = ((WebServerApplicationContext) applicationContext) + .getWebServer(); + if (webServer instanceof TomcatWebServer) { + Context context = findContext((TomcatWebServer) webServer); + return context.getManager(); + } + } + return null; + } + + private Context findContext(TomcatWebServer tomcatWebServer) { + for (Container container : tomcatWebServer.getTomcat().getHost().findChildren()) { + if (container instanceof Context) { + return (Context) container; + } + } + return null; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/tomcat/package-info.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/tomcat/package-info.java new file mode 100644 index 0000000000..90fc4d4190 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/tomcat/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Actuator support for Tomcat metrics. + */ +package org.springframework.boot.actuate.metrics.web.tomcat;