diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml
index a679534533..15bbe47126 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/jersey/JerseyServerMetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/JerseyServerMetricsAutoConfiguration.java
new file mode 100644
index 0000000000..4a791db7b0
--- /dev/null
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/JerseyServerMetricsAutoConfiguration.java
@@ -0,0 +1,111 @@
+/*
+ * 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.jersey;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.config.MeterFilter;
+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.MetricsProperties;
+import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Web.Server;
+import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter;
+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;
+import org.springframework.core.annotation.Order;
+
+/**
+ * {@link EnableAutoConfiguration Auto-configuration} for Jersey server instrumentation.
+ *
+ * @author Michael Weirauch
+ * @author Michael Simons
+ * @author Andy Wilkinson
+ * @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(MetricsProperties.class)
+public class JerseyServerMetricsAutoConfiguration {
+
+ private final MetricsProperties properties;
+
+ public JerseyServerMetricsAutoConfiguration(MetricsProperties properties) {
+ this.properties = properties;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(JerseyTagsProvider.class)
+ public DefaultJerseyTagsProvider jerseyTagsProvider() {
+ return new DefaultJerseyTagsProvider();
+ }
+
+ @Bean
+ public ResourceConfigCustomizer jerseyServerMetricsResourceConfigCustomizer(
+ MeterRegistry meterRegistry, JerseyTagsProvider tagsProvider) {
+ Server server = this.properties.getWeb().getServer();
+ return (config) -> {
+ config.register(new MetricsApplicationEventListener(meterRegistry,
+ tagsProvider, server.getRequestsMetricName(),
+ server.isAutoTimeRequests(), new AnnotationUtilsAnnotationFinder()));
+ };
+ }
+
+ @Bean
+ @Order(0)
+ public MeterFilter jerseyMetricsUriTagFilter() {
+ String metricName = this.properties.getWeb().getServer().getRequestsMetricName();
+ MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter(() -> String
+ .format("Reached the maximum number of URI tags for '%s'.", metricName));
+ return MeterFilter.maximumAllowableTags(metricName, "uri",
+ this.properties.getWeb().getServer().getMaxUriTags(), filter);
+ }
+
+ /**
+ * An {@link AnnotationFinder} that uses {@link AnnotationUtils}.
+ */
+ private static class AnnotationUtilsAnnotationFinder implements 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/jersey/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/package-info.java
new file mode 100644
index 0000000000..6a57de0f6a
--- /dev/null
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/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 actuator metrics.
+ */
+package org.springframework.boot.actuate.autoconfigure.metrics.jersey;
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 8539667910..29af9cef1b 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
@@ -61,6 +61,7 @@ org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetri
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.jersey.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/jersey/JerseyServerMetricsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/JerseyServerMetricsAutoConfigurationTests.java
new file mode 100644
index 0000000000..8fdc463200
--- /dev/null
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/jersey/JerseyServerMetricsAutoConfigurationTests.java
@@ -0,0 +1,173 @@
+/*
+ * 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.jersey;
+
+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-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc
index 98386320af..b05de13fcc 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
@@ -1832,6 +1832,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.web.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.web.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`.