diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/jetty/JettyMetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/jetty/JettyMetricsAutoConfiguration.java index bd74a82dba..c4f50203c7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/jetty/JettyMetricsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/jetty/JettyMetricsAutoConfiguration.java @@ -17,16 +17,21 @@ package org.springframework.boot.actuate.autoconfigure.metrics.web.jetty; import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.binder.jetty.JettyConnectionMetrics; import io.micrometer.core.instrument.binder.jetty.JettyServerThreadPoolMetrics; +import io.micrometer.core.instrument.binder.jetty.JettySslHandshakeMetrics; import org.eclipse.jetty.server.Server; import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; +import org.springframework.boot.actuate.metrics.web.jetty.JettyConnectionMetricsBinder; import org.springframework.boot.actuate.metrics.web.jetty.JettyServerThreadPoolMetricsBinder; +import org.springframework.boot.actuate.metrics.web.jetty.JettySslHandshakeMetricsBinder; import org.springframework.boot.autoconfigure.AutoConfigureAfter; 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.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -35,6 +40,7 @@ import org.springframework.context.annotation.Configuration; * {@link EnableAutoConfiguration Auto-configuration} for Jetty metrics. * * @author Andy Wilkinson + * @author Chris Bono * @since 2.1.0 */ @Configuration(proxyBeanMethods = false) @@ -50,4 +56,19 @@ public class JettyMetricsAutoConfiguration { return new JettyServerThreadPoolMetricsBinder(meterRegistry); } + @Bean + @ConditionalOnBean(MeterRegistry.class) + @ConditionalOnMissingBean({ JettyConnectionMetrics.class, JettyConnectionMetricsBinder.class }) + public JettyConnectionMetricsBinder jettyConnectionMetricsBinder(MeterRegistry meterRegistry) { + return new JettyConnectionMetricsBinder(meterRegistry); + } + + @Bean + @ConditionalOnBean(MeterRegistry.class) + @ConditionalOnMissingBean({ JettySslHandshakeMetrics.class, JettySslHandshakeMetricsBinder.class }) + @ConditionalOnProperty(name = "server.ssl.enabled", havingValue = "true") + public JettySslHandshakeMetricsBinder jettySslHandshakeMetricsBinder(MeterRegistry meterRegistry) { + return new JettySslHandshakeMetricsBinder(meterRegistry); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/jetty/JettyMetricsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/jetty/JettyMetricsAutoConfigurationTests.java index e216e23e2e..f0cd52d604 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/jetty/JettyMetricsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/jetty/JettyMetricsAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2021 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. @@ -16,12 +16,18 @@ package org.springframework.boot.actuate.autoconfigure.metrics.web.jetty; +import java.util.Collections; + import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.jupiter.api.Test; import org.springframework.boot.SpringApplication; +import org.springframework.boot.actuate.metrics.web.jetty.JettyConnectionMetricsBinder; import org.springframework.boot.actuate.metrics.web.jetty.JettyServerThreadPoolMetricsBinder; +import org.springframework.boot.actuate.metrics.web.jetty.JettySslHandshakeMetricsBinder; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; @@ -43,6 +49,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link JettyMetricsAutoConfiguration}. * * @author Andy Wilkinson + * @author Chris Bono */ class JettyMetricsAutoConfigurationTests { @@ -83,6 +90,132 @@ class JettyMetricsAutoConfigurationTests { .hasBean("customJettyServerThreadPoolMetricsBinder")); } + @Test + void autoConfiguresConnectionMetricsWithEmbeddedServletJetty() { + new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new) + .withConfiguration(AutoConfigurations.of(JettyMetricsAutoConfiguration.class, + ServletWebServerFactoryAutoConfiguration.class)) + .withUserConfiguration(ServletWebServerConfiguration.class, MeterRegistryConfiguration.class) + .run((context) -> { + context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null, + context.getSourceApplicationContext())); + assertThat(context).hasSingleBean(JettyConnectionMetricsBinder.class); + SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class); + assertThat(registry.find("jetty.connections.messages.in").meter()).isNotNull(); + }); + } + + @Test + void autoConfiguresConnectionMetricsWithEmbeddedReactiveJetty() { + new ReactiveWebApplicationContextRunner(AnnotationConfigReactiveWebServerApplicationContext::new) + .withConfiguration(AutoConfigurations.of(JettyMetricsAutoConfiguration.class, + ReactiveWebServerFactoryAutoConfiguration.class)) + .withUserConfiguration(ReactiveWebServerConfiguration.class, MeterRegistryConfiguration.class) + .run((context) -> { + context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null, + context.getSourceApplicationContext())); + SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class); + assertThat(registry.find("jetty.connections.messages.in").meter()).isNotNull(); + }); + } + + @Test + void allowsCustomJettyConnectionMetricsBinderToBeUsed() { + new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new) + .withConfiguration(AutoConfigurations.of(JettyMetricsAutoConfiguration.class, + ServletWebServerFactoryAutoConfiguration.class)) + .withUserConfiguration(ServletWebServerConfiguration.class, CustomJettyConnectionMetricsBinder.class, + MeterRegistryConfiguration.class) + .run((context) -> { + context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null, + context.getSourceApplicationContext())); + assertThat(context).hasSingleBean(JettyConnectionMetricsBinder.class) + .hasBean("customJettyConnectionMetricsBinder"); + SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class); + assertThat(registry.find("jetty.connections.messages.in").tag("custom-tag-name", "custom-tag-value") + .meter()).isNotNull(); + }); + } + + @Test + void autoConfiguresSslHandshakeMetricsWithEmbeddedServletJetty() { + new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new) + .withConfiguration(AutoConfigurations.of(JettyMetricsAutoConfiguration.class, + ServletWebServerFactoryAutoConfiguration.class)) + .withUserConfiguration(ServletWebServerConfiguration.class, MeterRegistryConfiguration.class) + .withPropertyValues("server.ssl.enabled: true", "server.ssl.key-store: src/test/resources/test.jks", + "server.ssl.key-store-password: secret", "server.ssl.key-password: password") + .run((context) -> { + context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null, + context.getSourceApplicationContext())); + assertThat(context).hasSingleBean(JettySslHandshakeMetricsBinder.class); + SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class); + assertThat(registry.find("jetty.ssl.handshakes").meter()).isNotNull(); + }); + } + + @Test + void autoConfiguresSslHandshakeMetricsWithEmbeddedReactiveJetty() { + new ReactiveWebApplicationContextRunner(AnnotationConfigReactiveWebServerApplicationContext::new) + .withConfiguration(AutoConfigurations.of(JettyMetricsAutoConfiguration.class, + ReactiveWebServerFactoryAutoConfiguration.class)) + .withUserConfiguration(ReactiveWebServerConfiguration.class, MeterRegistryConfiguration.class) + .withPropertyValues("server.ssl.enabled: true", "server.ssl.key-store: src/test/resources/test.jks", + "server.ssl.key-store-password: secret", "server.ssl.key-password: password") + .run((context) -> { + context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null, + context.getSourceApplicationContext())); + SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class); + assertThat(registry.find("jetty.ssl.handshakes").meter()).isNotNull(); + }); + } + + @Test + void allowsCustomJettySslHandshakeMetricsBinderToBeUsed() { + new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new) + .withConfiguration(AutoConfigurations.of(JettyMetricsAutoConfiguration.class, + ServletWebServerFactoryAutoConfiguration.class)) + .withUserConfiguration(ServletWebServerConfiguration.class, CustomJettySslHandshakeMetricsBinder.class, + MeterRegistryConfiguration.class) + .withPropertyValues("server.ssl.enabled: true", "server.ssl.key-store: src/test/resources/test.jks", + "server.ssl.key-store-password: secret", "server.ssl.key-password: password") + .run((context) -> { + context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null, + context.getSourceApplicationContext())); + assertThat(context).hasSingleBean(JettySslHandshakeMetricsBinder.class) + .hasBean("customJettySslHandshakeMetricsBinder"); + SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class); + assertThat(registry.find("jetty.ssl.handshakes").tag("custom-tag-name", "custom-tag-value").meter()) + .isNotNull(); + }); + + new WebApplicationContextRunner().withConfiguration(AutoConfigurations.of(JettyMetricsAutoConfiguration.class)) + .withUserConfiguration(CustomJettySslHandshakeMetricsBinder.class, MeterRegistryConfiguration.class) + .withPropertyValues("server.ssl.enabled: true", "server.ssl.key-store: src/test/resources/test.jks", + "server.ssl.key-store-password: secret", "server.ssl.key-password: password") + .run((context) -> assertThat(context).hasSingleBean(JettySslHandshakeMetricsBinder.class) + .hasBean("customJettySslHandshakeMetricsBinder")); + } + + @Test + void doesNotautoConfiguresSslHandshakeMetricsWhenSslEnabledPropertyNotSpecified() { + new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new) + .withConfiguration(AutoConfigurations.of(JettyMetricsAutoConfiguration.class, + ServletWebServerFactoryAutoConfiguration.class)) + .withUserConfiguration(ServletWebServerConfiguration.class, MeterRegistryConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(JettySslHandshakeMetricsBinder.class)); + } + + @Test + void doesNotautoConfiguresSslHandshakeMetricsWhenSslEnabledPropertySetToFalse() { + new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new) + .withConfiguration(AutoConfigurations.of(JettyMetricsAutoConfiguration.class, + ServletWebServerFactoryAutoConfiguration.class)) + .withUserConfiguration(ServletWebServerConfiguration.class, MeterRegistryConfiguration.class) + .withPropertyValues("server.ssl.enabled: false") + .run((context) -> assertThat(context).doesNotHaveBean(JettySslHandshakeMetricsBinder.class)); + } + @Configuration(proxyBeanMethods = false) static class MeterRegistryConfiguration { @@ -128,4 +261,24 @@ class JettyMetricsAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class CustomJettyConnectionMetricsBinder { + + @Bean + JettyConnectionMetricsBinder customJettyConnectionMetricsBinder(MeterRegistry meterRegistry) { + return new JettyConnectionMetricsBinder(meterRegistry, Tags.of("custom-tag-name", "custom-tag-value")); + } + + } + + @Configuration(proxyBeanMethods = false) + static class CustomJettySslHandshakeMetricsBinder { + + @Bean + JettySslHandshakeMetricsBinder customJettySslHandshakeMetricsBinder(MeterRegistry meterRegistry) { + return new JettySslHandshakeMetricsBinder(meterRegistry, Tags.of("custom-tag-name", "custom-tag-value")); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/jetty/JettyConnectionMetricsBinder.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/jetty/JettyConnectionMetricsBinder.java new file mode 100644 index 0000000000..eca35a17bd --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/jetty/JettyConnectionMetricsBinder.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-2021 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 + * + * https://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.jetty; + +import java.util.Collections; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.binder.jetty.JettyConnectionMetrics; +import org.eclipse.jetty.server.Server; + +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.boot.web.context.WebServerApplicationContext; +import org.springframework.boot.web.embedded.jetty.JettyWebServer; +import org.springframework.boot.web.server.WebServer; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; + +/** + * Binds {@link JettyConnectionMetrics} in response to the + * {@link ApplicationStartedEvent}. + * + * @author Chris Bono + * @since 2.6.0 + */ +public class JettyConnectionMetricsBinder implements ApplicationListener { + + private final MeterRegistry meterRegistry; + + private final Iterable tags; + + public JettyConnectionMetricsBinder(MeterRegistry meterRegistry) { + this(meterRegistry, Collections.emptyList()); + } + + public JettyConnectionMetricsBinder(MeterRegistry meterRegistry, Iterable tags) { + this.meterRegistry = meterRegistry; + this.tags = tags; + } + + @Override + public void onApplicationEvent(ApplicationStartedEvent event) { + ApplicationContext applicationContext = event.getApplicationContext(); + Server server = findServer(applicationContext); + if (server != null) { + JettyConnectionMetrics.addToAllConnectors(server, this.meterRegistry, this.tags); + } + } + + private Server findServer(ApplicationContext applicationContext) { + if (applicationContext instanceof WebServerApplicationContext) { + WebServer webServer = ((WebServerApplicationContext) applicationContext).getWebServer(); + if (webServer instanceof JettyWebServer) { + return ((JettyWebServer) webServer).getServer(); + } + } + return null; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/jetty/JettySslHandshakeMetricsBinder.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/jetty/JettySslHandshakeMetricsBinder.java new file mode 100644 index 0000000000..cd2f93bf5d --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/jetty/JettySslHandshakeMetricsBinder.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-2021 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 + * + * https://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.jetty; + +import java.util.Collections; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.binder.jetty.JettySslHandshakeMetrics; +import org.eclipse.jetty.server.Server; + +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.boot.web.context.WebServerApplicationContext; +import org.springframework.boot.web.embedded.jetty.JettyWebServer; +import org.springframework.boot.web.server.WebServer; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; + +/** + * Binds {@link JettySslHandshakeMetrics} in response to the + * {@link ApplicationStartedEvent}. + * + * @author Chris Bono + * @since 2.6.0 + */ +public class JettySslHandshakeMetricsBinder implements ApplicationListener { + + private final MeterRegistry meterRegistry; + + private final Iterable tags; + + public JettySslHandshakeMetricsBinder(MeterRegistry meterRegistry) { + this(meterRegistry, Collections.emptyList()); + } + + public JettySslHandshakeMetricsBinder(MeterRegistry meterRegistry, Iterable tags) { + this.meterRegistry = meterRegistry; + this.tags = tags; + } + + @Override + public void onApplicationEvent(ApplicationStartedEvent event) { + ApplicationContext applicationContext = event.getApplicationContext(); + Server server = findServer(applicationContext); + if (server != null) { + JettySslHandshakeMetrics.addToAllConnectors(server, this.meterRegistry, this.tags); + } + } + + private Server findServer(ApplicationContext applicationContext) { + if (applicationContext instanceof WebServerApplicationContext) { + WebServer webServer = ((WebServerApplicationContext) applicationContext).getWebServer(); + if (webServer instanceof JettyWebServer) { + return ((JettyWebServer) webServer).getServer(); + } + } + return null; + } + +}