Skip to content

Commit dd126fa

Browse files
mweirauchmichael-simons
authored andcommitted
Auto-configure Micrometer's Jersey 2 server instrumentation
See spring-projectsgh-12482 Co-authored-by: Michael J. Simons <michael@simons.ac>
1 parent f2e4a0b commit dd126fa

File tree

7 files changed

+372
-0
lines changed

7 files changed

+372
-0
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@
9292
<artifactId>micrometer-core</artifactId>
9393
<optional>true</optional>
9494
</dependency>
95+
<dependency>
96+
<groupId>io.micrometer</groupId>
97+
<artifactId>micrometer-jersey2</artifactId>
98+
<optional>true</optional>
99+
</dependency>
95100
<dependency>
96101
<groupId>io.micrometer</groupId>
97102
<artifactId>micrometer-registry-atlas</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.metrics.jersey2.server;
18+
19+
import java.lang.annotation.Annotation;
20+
import java.lang.reflect.AnnotatedElement;
21+
22+
import io.micrometer.core.instrument.MeterRegistry;
23+
import io.micrometer.jersey2.server.AnnotationFinder;
24+
import io.micrometer.jersey2.server.DefaultJerseyTagsProvider;
25+
import io.micrometer.jersey2.server.JerseyTagsProvider;
26+
import io.micrometer.jersey2.server.MetricsApplicationEventListener;
27+
import org.glassfish.jersey.server.ResourceConfig;
28+
29+
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
30+
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
31+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
32+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
33+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
34+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
35+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
36+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
37+
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
38+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
39+
import org.springframework.context.annotation.Bean;
40+
import org.springframework.context.annotation.Configuration;
41+
import org.springframework.core.annotation.AnnotationUtils;
42+
43+
/**
44+
* {@link EnableAutoConfiguration Auto-configuration} for Jersey server instrumentation.
45+
*
46+
* @author Michael Weirauch
47+
* @author Michael Simons
48+
* @since 2.1.0
49+
*/
50+
@Configuration
51+
@AutoConfigureAfter({ MetricsAutoConfiguration.class,
52+
SimpleMetricsExportAutoConfiguration.class })
53+
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
54+
@ConditionalOnClass({ ResourceConfig.class, MetricsApplicationEventListener.class })
55+
@ConditionalOnBean({ MeterRegistry.class, ResourceConfig.class })
56+
@EnableConfigurationProperties(JerseyServerMetricsProperties.class)
57+
public class JerseyServerMetricsAutoConfiguration {
58+
59+
@Bean
60+
@ConditionalOnMissingBean(JerseyTagsProvider.class)
61+
public DefaultJerseyTagsProvider jerseyTagsProvider() {
62+
return new DefaultJerseyTagsProvider();
63+
}
64+
65+
@Bean
66+
public ResourceConfigCustomizer jerseyServerMetricsResourceConfigCustomizer(
67+
MeterRegistry meterRegistry, JerseyServerMetricsProperties properties,
68+
JerseyTagsProvider tagsProvider) {
69+
return (config) -> config.register(new MetricsApplicationEventListener(
70+
meterRegistry, tagsProvider, properties.getRequestsMetricName(),
71+
properties.isAutoTimeRequests(), new AnnotationFinder() {
72+
@Override
73+
public <A extends Annotation> A findAnnotation(
74+
AnnotatedElement annotatedElement, Class<A> annotationType) {
75+
return AnnotationUtils.findAnnotation(annotatedElement,
76+
annotationType);
77+
}
78+
}));
79+
}
80+
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.metrics.jersey2.server;
18+
19+
import org.springframework.boot.context.properties.ConfigurationProperties;
20+
21+
/**
22+
* Configuration for Jersey server instrumentation.
23+
*
24+
* @author Michael Weirauch
25+
* @since 2.1.0
26+
*/
27+
@ConfigurationProperties(prefix = "management.metrics.jersey2.server")
28+
public class JerseyServerMetricsProperties {
29+
30+
private String requestsMetricName = "http.server.requests";
31+
32+
private boolean autoTimeRequests = true;
33+
34+
public String getRequestsMetricName() {
35+
return this.requestsMetricName;
36+
}
37+
38+
public void setRequestsMetricName(String requestsMetricName) {
39+
this.requestsMetricName = requestsMetricName;
40+
}
41+
42+
public boolean isAutoTimeRequests() {
43+
return this.autoTimeRequests;
44+
}
45+
46+
public void setAutoTimeRequests(boolean autoTimeRequests) {
47+
this.autoTimeRequests = autoTimeRequests;
48+
}
49+
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Auto-configuration for Jersey server actuator metrics.
19+
*/
20+
package org.springframework.boot.actuate.autoconfigure.metrics.jersey2.server;

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetri
6161
org.springframework.boot.actuate.autoconfigure.metrics.export.statsd.StatsdMetricsExportAutoConfiguration,\
6262
org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront.WavefrontMetricsExportAutoConfiguration,\
6363
org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration,\
64+
org.springframework.boot.actuate.autoconfigure.metrics.jersey2.server.JerseyServerMetricsAutoConfiguration,\
6465
org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa.HibernateMetricsAutoConfiguration,\
6566
org.springframework.boot.actuate.autoconfigure.metrics.web.client.HttpClientMetricsAutoConfiguration,\
6667
org.springframework.boot.actuate.autoconfigure.metrics.web.reactive.WebFluxMetricsAutoConfiguration,\
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.metrics.jersey2.server;
18+
19+
import java.net.URI;
20+
21+
import javax.ws.rs.GET;
22+
import javax.ws.rs.Path;
23+
import javax.ws.rs.PathParam;
24+
25+
import io.micrometer.core.instrument.MeterRegistry;
26+
import io.micrometer.core.instrument.Tag;
27+
import io.micrometer.core.instrument.Timer;
28+
import io.micrometer.jersey2.server.DefaultJerseyTagsProvider;
29+
import io.micrometer.jersey2.server.JerseyTagsProvider;
30+
import io.micrometer.jersey2.server.MetricsApplicationEventListener;
31+
import org.glassfish.jersey.server.ResourceConfig;
32+
import org.glassfish.jersey.server.monitoring.RequestEvent;
33+
import org.junit.Test;
34+
35+
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
36+
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
37+
import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
38+
import org.springframework.boot.autoconfigure.AutoConfigurations;
39+
import org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration;
40+
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
41+
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
42+
import org.springframework.boot.test.context.FilteredClassLoader;
43+
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
44+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
45+
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
46+
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
47+
import org.springframework.context.annotation.Bean;
48+
import org.springframework.web.client.RestTemplate;
49+
50+
import static org.assertj.core.api.Assertions.assertThat;
51+
52+
/**
53+
* Tests for {@link JerseyServerMetricsAutoConfiguration}.
54+
*
55+
* @author Michael Weirauch
56+
* @author Michael Simons
57+
*/
58+
public class JerseyServerMetricsAutoConfigurationTests {
59+
60+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
61+
.with(MetricsRun.simple()).withConfiguration(
62+
AutoConfigurations.of(JerseyServerMetricsAutoConfiguration.class));
63+
64+
private final WebApplicationContextRunner webContextRunner = new WebApplicationContextRunner(
65+
AnnotationConfigServletWebServerApplicationContext::new)
66+
.withConfiguration(
67+
AutoConfigurations.of(JerseyAutoConfiguration.class,
68+
JerseyServerMetricsAutoConfiguration.class,
69+
ServletWebServerFactoryAutoConfiguration.class,
70+
SimpleMetricsExportAutoConfiguration.class,
71+
MetricsAutoConfiguration.class))
72+
.withUserConfiguration(ResourceConfiguration.class)
73+
.withPropertyValues("server.port:0");
74+
75+
@Test
76+
public void shouldOnlyBeActiveInWebApplicationContext() {
77+
this.contextRunner.run((context) -> assertThat(context)
78+
.doesNotHaveBean(ResourceConfigCustomizer.class));
79+
}
80+
81+
@Test
82+
public void shouldProvideAllNecessaryBeans() {
83+
this.webContextRunner.run((context) -> assertThat(context)
84+
.hasSingleBean(DefaultJerseyTagsProvider.class)
85+
.hasSingleBean(ResourceConfigCustomizer.class));
86+
}
87+
88+
@Test
89+
public void shouldHonorExistingTagProvider() {
90+
this.webContextRunner
91+
.withUserConfiguration(CustomJerseyTagsProviderConfiguration.class)
92+
.run((context) -> assertThat(context)
93+
.hasSingleBean(CustomJerseyTagsProvider.class));
94+
}
95+
96+
@Test
97+
public void httpRequestsAreTimed() {
98+
this.webContextRunner.run((context) -> {
99+
doRequest(context);
100+
101+
MeterRegistry registry = context.getBean(MeterRegistry.class);
102+
Timer timer = registry.get("http.server.requests").tag("uri", "/users/{id}")
103+
.timer();
104+
assertThat(timer.count()).isEqualTo(1);
105+
});
106+
}
107+
108+
@Test
109+
public void noHttpRequestsTimedWhenJerseyInstrumentationMissingFromClasspath() {
110+
this.webContextRunner
111+
.withClassLoader(
112+
new FilteredClassLoader(MetricsApplicationEventListener.class))
113+
.run((context) -> {
114+
doRequest(context);
115+
116+
MeterRegistry registry = context.getBean(MeterRegistry.class);
117+
assertThat(registry.find("http.server.requests").timer()).isNull();
118+
});
119+
}
120+
121+
private static void doRequest(AssertableWebApplicationContext context) {
122+
int port = context
123+
.getSourceApplicationContext(
124+
AnnotationConfigServletWebServerApplicationContext.class)
125+
.getWebServer().getPort();
126+
RestTemplate restTemplate = new RestTemplate();
127+
restTemplate.getForEntity(URI.create("http://localhost:" + port + "/users/3"),
128+
String.class);
129+
}
130+
131+
static class ResourceConfiguration {
132+
133+
@Bean
134+
ResourceConfig resourceConfig() {
135+
return new ResourceConfig().register(new TestResource());
136+
}
137+
138+
@Path("/users")
139+
public class TestResource {
140+
141+
@GET
142+
@Path("/{id}")
143+
public String getUser(@PathParam("id") String id) {
144+
return id;
145+
}
146+
147+
}
148+
149+
}
150+
151+
static class CustomJerseyTagsProviderConfiguration {
152+
153+
@Bean
154+
JerseyTagsProvider customJerseyTagsProvider() {
155+
return new CustomJerseyTagsProvider();
156+
}
157+
158+
}
159+
160+
static class CustomJerseyTagsProvider implements JerseyTagsProvider {
161+
162+
@Override
163+
public Iterable<Tag> httpRequestTags(RequestEvent event) {
164+
return null;
165+
}
166+
167+
@Override
168+
public Iterable<Tag> httpLongRequestTags(RequestEvent event) {
169+
return null;
170+
}
171+
172+
}
173+
174+
}

spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1832,6 +1832,47 @@ To customize the tags, provide a `@Bean` that implements `WebFluxTagsProvider`.
18321832

18331833

18341834

1835+
[[production-ready-metrics-jersey-server]]
1836+
==== Jersey Server Metrics
1837+
Auto-configuration enables the instrumentation of requests handled by the Jersey JAX-RS
1838+
implementation. When `management.metrics.jersey2.server.auto-time-requests` is `true`,
1839+
this instrumentation occurs for all requests. Alternatively, when set to `false`, you
1840+
can enable instrumentation by adding `@Timed` to a request-handling method:
1841+
1842+
[source,java,indent=0]
1843+
----
1844+
@Component
1845+
@Path("/api/people")
1846+
@Timed <1>
1847+
public class Endpoint {
1848+
@GET
1849+
@Timed(extraTags = { "region", "us-east-1" }) <2>
1850+
@Timed(value = "all.people", longTask = true) <3>
1851+
public List<Person> listPeople() { ... }
1852+
}
1853+
----
1854+
<1> On a resource class to enable timings on every request handler in the resource.
1855+
<2> On a method to enable for an individual endpoint. This is not necessary if you have it on
1856+
the class, but can be used to further customize the timer for this particular endpoint.
1857+
<3> On a method with `longTask = true` to enable a long task timer for the method. Long task
1858+
timers require a separate metric name, and can be stacked with a short task timer.
1859+
1860+
By default, metrics are generated with the name, `http.server.requests`. The name can be
1861+
customized by setting the `management.metrics.jersey2.server.requests-metric-name` property.
1862+
1863+
By default, Jersey server metrics are tagged with the following information:
1864+
1865+
* `method`, the request's method (for example, `GET` or `POST`).
1866+
* `uri`, the request's URI template prior to variable substitution, if possible (for
1867+
example, `/api/person/{id}`).
1868+
* `status`, the response's HTTP status code (for example, `200` or `500`).
1869+
* `exception`, the simple class name of any exception that was thrown while handling the
1870+
request.
1871+
1872+
To customize the tags, provide a `@Bean` that implements `JerseyTagsProvider`.
1873+
1874+
1875+
18351876
[[production-ready-metrics-http-clients]]
18361877
==== HTTP Client Metrics
18371878
Spring Boot Actuator manages the instrumentation of both `RestTemplate` and `WebClient`.

0 commit comments

Comments
 (0)