diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml index bd2f32275f8e..887fe41914c7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml @@ -92,6 +92,11 @@ micrometer-core true + + io.micrometer + micrometer-jersey2 + true + io.micrometer micrometer-registry-atlas diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey2/server/JerseyServerMetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey2/server/JerseyServerMetricsAutoConfiguration.java new file mode 100644 index 000000000000..431ec310d2f8 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey2/server/JerseyServerMetricsAutoConfiguration.java @@ -0,0 +1,81 @@ +/* + * 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.autoconfigure.metrics.jersey2.server; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.jersey2.server.AnnotationFinder; +import io.micrometer.jersey2.server.DefaultJerseyTagsProvider; +import io.micrometer.jersey2.server.JerseyTagsProvider; +import io.micrometer.jersey2.server.MetricsApplicationEventListener; +import org.glassfish.jersey.server.ResourceConfig; + +import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; +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.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationUtils; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Jersey server instrumentation. + * + * @author Michael Weirauch + * @author Michael Simons + * @since 2.1.0 + */ +@Configuration +@AutoConfigureAfter({ MetricsAutoConfiguration.class, + SimpleMetricsExportAutoConfiguration.class }) +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +@ConditionalOnClass({ ResourceConfig.class, MetricsApplicationEventListener.class }) +@ConditionalOnBean({ MeterRegistry.class, ResourceConfig.class }) +@EnableConfigurationProperties(JerseyServerMetricsProperties.class) +public class JerseyServerMetricsAutoConfiguration { + + @Bean + @ConditionalOnMissingBean(JerseyTagsProvider.class) + public DefaultJerseyTagsProvider jerseyTagsProvider() { + return new DefaultJerseyTagsProvider(); + } + + @Bean + public ResourceConfigCustomizer jerseyServerMetricsResourceConfigCustomizer( + MeterRegistry meterRegistry, JerseyServerMetricsProperties properties, + JerseyTagsProvider tagsProvider) { + return (config) -> config.register(new MetricsApplicationEventListener( + meterRegistry, tagsProvider, properties.getRequestsMetricName(), + properties.isAutoTimeRequests(), new AnnotationFinder() { + @Override + public A findAnnotation( + AnnotatedElement annotatedElement, Class annotationType) { + return AnnotationUtils.findAnnotation(annotatedElement, + annotationType); + } + })); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey2/server/JerseyServerMetricsProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey2/server/JerseyServerMetricsProperties.java new file mode 100644 index 000000000000..8fddb1e8a446 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey2/server/JerseyServerMetricsProperties.java @@ -0,0 +1,50 @@ +/* + * 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.autoconfigure.metrics.jersey2.server; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration for Jersey server instrumentation. + * + * @author Michael Weirauch + * @since 2.1.0 + */ +@ConfigurationProperties(prefix = "management.metrics.jersey2.server") +public class JerseyServerMetricsProperties { + + private String requestsMetricName = "http.server.requests"; + + private boolean autoTimeRequests = true; + + public String getRequestsMetricName() { + return this.requestsMetricName; + } + + public void setRequestsMetricName(String requestsMetricName) { + this.requestsMetricName = requestsMetricName; + } + + public boolean isAutoTimeRequests() { + return this.autoTimeRequests; + } + + public void setAutoTimeRequests(boolean autoTimeRequests) { + this.autoTimeRequests = autoTimeRequests; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey2/server/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey2/server/package-info.java new file mode 100644 index 000000000000..093d22887889 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey2/server/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Auto-configuration for Jersey server actuator metrics. + */ +package org.springframework.boot.actuate.autoconfigure.metrics.jersey2.server; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories index 0109c40f12fb..05ac8400334b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories @@ -54,6 +54,7 @@ org.springframework.boot.actuate.autoconfigure.metrics.export.signalfx.SignalFxM org.springframework.boot.actuate.autoconfigure.metrics.export.statsd.StatsdMetricsExportAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront.WavefrontMetricsExportAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.metrics.jersey2.server.JerseyServerMetricsAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa.HibernateMetricsAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.web.client.HttpClientMetricsAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.web.reactive.WebFluxMetricsAutoConfiguration,\ diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey2/server/JerseyServerMetricsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey2/server/JerseyServerMetricsAutoConfigurationTests.java new file mode 100644 index 000000000000..6d27eae9536c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey2/server/JerseyServerMetricsAutoConfigurationTests.java @@ -0,0 +1,174 @@ +/* + * 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.autoconfigure.metrics.jersey2.server; + +import java.net.URI; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Timer; +import io.micrometer.jersey2.server.DefaultJerseyTagsProvider; +import io.micrometer.jersey2.server.JerseyTagsProvider; +import io.micrometer.jersey2.server.MetricsApplicationEventListener; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.monitoring.RequestEvent; +import org.junit.Test; + +import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration; +import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer; +import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link JerseyServerMetricsAutoConfiguration}. + * + * @author Michael Weirauch + * @author Michael Simons + */ +public class JerseyServerMetricsAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .with(MetricsRun.simple()).withConfiguration( + AutoConfigurations.of(JerseyServerMetricsAutoConfiguration.class)); + + private final WebApplicationContextRunner webContextRunner = new WebApplicationContextRunner( + AnnotationConfigServletWebServerApplicationContext::new) + .withConfiguration( + AutoConfigurations.of(JerseyAutoConfiguration.class, + JerseyServerMetricsAutoConfiguration.class, + ServletWebServerFactoryAutoConfiguration.class, + SimpleMetricsExportAutoConfiguration.class, + MetricsAutoConfiguration.class)) + .withUserConfiguration(ResourceConfiguration.class) + .withPropertyValues("server.port:0"); + + @Test + public void shouldOnlyBeActiveInWebApplicationContext() { + this.contextRunner.run((context) -> assertThat(context) + .doesNotHaveBean(ResourceConfigCustomizer.class)); + } + + @Test + public void shouldProvideAllNecessaryBeans() { + this.webContextRunner.run((context) -> assertThat(context) + .hasSingleBean(DefaultJerseyTagsProvider.class) + .hasSingleBean(ResourceConfigCustomizer.class)); + } + + @Test + public void shouldHonorExistingTagProvider() { + this.webContextRunner + .withUserConfiguration(CustomJerseyTagsProviderConfiguration.class) + .run((context) -> assertThat(context) + .hasSingleBean(CustomJerseyTagsProvider.class)); + } + + @Test + public void httpRequestsAreTimed() { + this.webContextRunner.run((context) -> { + doRequest(context); + + MeterRegistry registry = context.getBean(MeterRegistry.class); + Timer timer = registry.get("http.server.requests").tag("uri", "/users/{id}") + .timer(); + assertThat(timer.count()).isEqualTo(1); + }); + } + + @Test + public void noHttpRequestsTimedWhenJerseyInstrumentationMissingFromClasspath() { + this.webContextRunner + .withClassLoader( + new FilteredClassLoader(MetricsApplicationEventListener.class)) + .run((context) -> { + doRequest(context); + + MeterRegistry registry = context.getBean(MeterRegistry.class); + assertThat(registry.find("http.server.requests").timer()).isNull(); + }); + } + + private static void doRequest(AssertableWebApplicationContext context) { + int port = context + .getSourceApplicationContext( + AnnotationConfigServletWebServerApplicationContext.class) + .getWebServer().getPort(); + RestTemplate restTemplate = new RestTemplate(); + restTemplate.getForEntity(URI.create("http://localhost:" + port + "/users/3"), + String.class); + } + + static class ResourceConfiguration { + + @Bean + ResourceConfig resourceConfig() { + return new ResourceConfig().register(new TestResource()); + } + + @Path("/users") + public class TestResource { + + @GET + @Path("/{id}") + public String getUser(@PathParam("id") String id) { + return id; + } + + } + + } + + static class CustomJerseyTagsProviderConfiguration { + + @Bean + JerseyTagsProvider customJerseyTagsProvider() { + return new CustomJerseyTagsProvider(); + } + + } + + static class CustomJerseyTagsProvider implements JerseyTagsProvider { + + @Override + public Iterable httpRequestTags(RequestEvent event) { + return null; + } + + @Override + public Iterable httpLongRequestTags(RequestEvent event) { + return null; + } + + } + +} diff --git a/spring-boot-project/spring-boot-dependencies/pom.xml b/spring-boot-project/spring-boot-dependencies/pom.xml index ea045f83a6b7..03b63d4b2464 100644 --- a/spring-boot-project/spring-boot-dependencies/pom.xml +++ b/spring-boot-project/spring-boot-dependencies/pom.xml @@ -869,6 +869,11 @@ micrometer-core ${micrometer.version} + + io.micrometer + micrometer-jersey2 + ${micrometer.version} + io.micrometer micrometer-registry-atlas diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index b11ef639d6a9..0e2148446aa0 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -1756,6 +1756,47 @@ To customize the tags, provide a `@Bean` that implements `WebFluxTagsProvider`. +[[production-ready-metrics-jersey-server]] +==== Jersey Server Metrics +Auto-configuration enables the instrumentation of requests handled by the Jersey JAX-RS +implementation. When `management.metrics.jersey2.server.auto-time-requests` is `true`, +this instrumentation occurs for all requests. Alternatively, when set to `false`, you +can enable instrumentation by adding `@Timed` to a request-handling method: + +[source,java,indent=0] +---- + @Component + @Path("/api/people") + @Timed <1> + public class Endpoint { + @GET + @Timed(extraTags = { "region", "us-east-1" }) <2> + @Timed(value = "all.people", longTask = true) <3> + public List listPeople() { ... } + } +---- +<1> On a resource class to enable timings on every request handler in the resource. +<2> On a method to enable for an individual endpoint. This is not necessary if you have it on +the class, but can be used to further customize the timer for this particular endpoint. +<3> On a method with `longTask = true` to enable a long task timer for the method. Long task +timers require a separate metric name, and can be stacked with a short task timer. + +By default, metrics are generated with the name, `http.server.requests`. The name can be +customized by setting the `management.metrics.jersey2.server.requests-metric-name` property. + +By default, Jersey server metrics are tagged with the following information: + +* `method`, the request's method (for example, `GET` or `POST`). +* `uri`, the request's URI template prior to variable substitution, if possible (for +example, `/api/person/{id}`). +* `status`, the response's HTTP status code (for example, `200` or `500`). +* `exception`, the simple class name of any exception that was thrown while handling the +request. + +To customize the tags, provide a `@Bean` that implements `JerseyTagsProvider`. + + + [[production-ready-metrics-http-clients]] ==== HTTP Client Metrics Spring Boot Actuator manages the instrumentation of both `RestTemplate` and `WebClient`.