Skip to content

Commit 1c0be1c

Browse files
committed
Merge pull request #12482 from Michael Weirauch and Michael Simons
* gh-12482: Polish "Auto-configure Micrometer's Jersey 2 server instrumentation" Auto-configure Micrometer's Jersey 2 server instrumentation
2 parents f2e4a0b + 72e2313 commit 1c0be1c

File tree

6 files changed

+351
-0
lines changed

6 files changed

+351
-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,111 @@
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.jersey;
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.core.instrument.config.MeterFilter;
24+
import io.micrometer.jersey2.server.AnnotationFinder;
25+
import io.micrometer.jersey2.server.DefaultJerseyTagsProvider;
26+
import io.micrometer.jersey2.server.JerseyTagsProvider;
27+
import io.micrometer.jersey2.server.MetricsApplicationEventListener;
28+
import org.glassfish.jersey.server.ResourceConfig;
29+
30+
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
31+
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
32+
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Web.Server;
33+
import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter;
34+
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
35+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
36+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
37+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
38+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
39+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
40+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
41+
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
42+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
43+
import org.springframework.context.annotation.Bean;
44+
import org.springframework.context.annotation.Configuration;
45+
import org.springframework.core.annotation.AnnotationUtils;
46+
import org.springframework.core.annotation.Order;
47+
48+
/**
49+
* {@link EnableAutoConfiguration Auto-configuration} for Jersey server instrumentation.
50+
*
51+
* @author Michael Weirauch
52+
* @author Michael Simons
53+
* @author Andy Wilkinson
54+
* @since 2.1.0
55+
*/
56+
@Configuration
57+
@AutoConfigureAfter({ MetricsAutoConfiguration.class,
58+
SimpleMetricsExportAutoConfiguration.class })
59+
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
60+
@ConditionalOnClass({ ResourceConfig.class, MetricsApplicationEventListener.class })
61+
@ConditionalOnBean({ MeterRegistry.class, ResourceConfig.class })
62+
@EnableConfigurationProperties(MetricsProperties.class)
63+
public class JerseyServerMetricsAutoConfiguration {
64+
65+
private final MetricsProperties properties;
66+
67+
public JerseyServerMetricsAutoConfiguration(MetricsProperties properties) {
68+
this.properties = properties;
69+
}
70+
71+
@Bean
72+
@ConditionalOnMissingBean(JerseyTagsProvider.class)
73+
public DefaultJerseyTagsProvider jerseyTagsProvider() {
74+
return new DefaultJerseyTagsProvider();
75+
}
76+
77+
@Bean
78+
public ResourceConfigCustomizer jerseyServerMetricsResourceConfigCustomizer(
79+
MeterRegistry meterRegistry, JerseyTagsProvider tagsProvider) {
80+
Server server = this.properties.getWeb().getServer();
81+
return (config) -> {
82+
config.register(new MetricsApplicationEventListener(meterRegistry,
83+
tagsProvider, server.getRequestsMetricName(),
84+
server.isAutoTimeRequests(), new AnnotationUtilsAnnotationFinder()));
85+
};
86+
}
87+
88+
@Bean
89+
@Order(0)
90+
public MeterFilter jerseyMetricsUriTagFilter() {
91+
String metricName = this.properties.getWeb().getServer().getRequestsMetricName();
92+
MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter(() -> String
93+
.format("Reached the maximum number of URI tags for '%s'.", metricName));
94+
return MeterFilter.maximumAllowableTags(metricName, "uri",
95+
this.properties.getWeb().getServer().getMaxUriTags(), filter);
96+
}
97+
98+
/**
99+
* An {@link AnnotationFinder} that uses {@link AnnotationUtils}.
100+
*/
101+
private static class AnnotationUtilsAnnotationFinder implements AnnotationFinder {
102+
103+
@Override
104+
public <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement,
105+
Class<A> annotationType) {
106+
return AnnotationUtils.findAnnotation(annotatedElement, annotationType);
107+
}
108+
109+
}
110+
111+
}
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 actuator metrics.
19+
*/
20+
package org.springframework.boot.actuate.autoconfigure.metrics.jersey;

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.jersey.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,173 @@
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.jersey;
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+
MeterRegistry registry = context.getBean(MeterRegistry.class);
101+
Timer timer = registry.get("http.server.requests").tag("uri", "/users/{id}")
102+
.timer();
103+
assertThat(timer.count()).isEqualTo(1);
104+
});
105+
}
106+
107+
@Test
108+
public void noHttpRequestsTimedWhenJerseyInstrumentationMissingFromClasspath() {
109+
this.webContextRunner
110+
.withClassLoader(
111+
new FilteredClassLoader(MetricsApplicationEventListener.class))
112+
.run((context) -> {
113+
doRequest(context);
114+
115+
MeterRegistry registry = context.getBean(MeterRegistry.class);
116+
assertThat(registry.find("http.server.requests").timer()).isNull();
117+
});
118+
}
119+
120+
private static void doRequest(AssertableWebApplicationContext context) {
121+
int port = context
122+
.getSourceApplicationContext(
123+
AnnotationConfigServletWebServerApplicationContext.class)
124+
.getWebServer().getPort();
125+
RestTemplate restTemplate = new RestTemplate();
126+
restTemplate.getForEntity(URI.create("http://localhost:" + port + "/users/3"),
127+
String.class);
128+
}
129+
130+
static class ResourceConfiguration {
131+
132+
@Bean
133+
ResourceConfig resourceConfig() {
134+
return new ResourceConfig().register(new TestResource());
135+
}
136+
137+
@Path("/users")
138+
public class TestResource {
139+
140+
@GET
141+
@Path("/{id}")
142+
public String getUser(@PathParam("id") String id) {
143+
return id;
144+
}
145+
146+
}
147+
148+
}
149+
150+
static class CustomJerseyTagsProviderConfiguration {
151+
152+
@Bean
153+
JerseyTagsProvider customJerseyTagsProvider() {
154+
return new CustomJerseyTagsProvider();
155+
}
156+
157+
}
158+
159+
static class CustomJerseyTagsProvider implements JerseyTagsProvider {
160+
161+
@Override
162+
public Iterable<Tag> httpRequestTags(RequestEvent event) {
163+
return null;
164+
}
165+
166+
@Override
167+
public Iterable<Tag> httpLongRequestTags(RequestEvent event) {
168+
return null;
169+
}
170+
171+
}
172+
173+
}

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.web.server.auto-time-requests` is `true`, this
1839+
instrumentation occurs for all requests. Alternatively, when set to `false`, you can
1840+
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.web.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)