Auto-configure Jetty connection and SSL metrics

See gh-26418
This commit is contained in:
bono007 2021-05-09 16:47:20 -05:00 committed by Andy Wilkinson
parent c36476ed16
commit e6c43a32c8
4 changed files with 323 additions and 1 deletions

View File

@ -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);
}
}

View File

@ -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"));
}
}
}

View File

@ -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<ApplicationStartedEvent> {
private final MeterRegistry meterRegistry;
private final Iterable<Tag> tags;
public JettyConnectionMetricsBinder(MeterRegistry meterRegistry) {
this(meterRegistry, Collections.emptyList());
}
public JettyConnectionMetricsBinder(MeterRegistry meterRegistry, Iterable<Tag> 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;
}
}

View File

@ -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<ApplicationStartedEvent> {
private final MeterRegistry meterRegistry;
private final Iterable<Tag> tags;
public JettySslHandshakeMetricsBinder(MeterRegistry meterRegistry) {
this(meterRegistry, Collections.emptyList());
}
public JettySslHandshakeMetricsBinder(MeterRegistry meterRegistry, Iterable<Tag> 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;
}
}