Skip to content

Commit b9abcba

Browse files
committed
Merge branch '2.4.x'
Closes gh-26596
2 parents 55b507b + ff45e4c commit b9abcba

File tree

7 files changed

+195
-27
lines changed

7 files changed

+195
-27
lines changed

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/MetricsWebFilter.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import io.micrometer.core.annotation.Timed;
2424
import io.micrometer.core.instrument.MeterRegistry;
2525
import io.micrometer.core.instrument.Tag;
26+
import org.apache.commons.logging.Log;
27+
import org.apache.commons.logging.LogFactory;
2628
import org.reactivestreams.Publisher;
2729
import reactor.core.publisher.Mono;
2830

@@ -49,6 +51,8 @@
4951
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
5052
public class MetricsWebFilter implements WebFilter {
5153

54+
private static Log logger = LogFactory.getLog(MetricsWebFilter.class);
55+
5256
private final MeterRegistry registry;
5357

5458
private final WebFluxTagsProvider tagsProvider;
@@ -98,13 +102,19 @@ private void onTerminalSignal(ServerWebExchange exchange, Throwable cause, long
98102
}
99103

100104
private void record(ServerWebExchange exchange, Throwable cause, long start) {
101-
cause = (cause != null) ? cause : exchange.getAttribute(ErrorAttributes.ERROR_ATTRIBUTE);
102-
Object handler = exchange.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
103-
Set<Timed> annotations = getTimedAnnotations(handler);
104-
Iterable<Tag> tags = this.tagsProvider.httpRequestTags(exchange, cause);
105-
long duration = System.nanoTime() - start;
106-
AutoTimer.apply(this.autoTimer, this.metricName, annotations,
107-
(builder) -> builder.tags(tags).register(this.registry).record(duration, TimeUnit.NANOSECONDS));
105+
try {
106+
cause = (cause != null) ? cause : exchange.getAttribute(ErrorAttributes.ERROR_ATTRIBUTE);
107+
Object handler = exchange.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
108+
Set<Timed> annotations = getTimedAnnotations(handler);
109+
Iterable<Tag> tags = this.tagsProvider.httpRequestTags(exchange, cause);
110+
long duration = System.nanoTime() - start;
111+
AutoTimer.apply(this.autoTimer, this.metricName, annotations,
112+
(builder) -> builder.tags(tags).register(this.registry).record(duration, TimeUnit.NANOSECONDS));
113+
}
114+
catch (Exception ex) {
115+
logger.warn("Failed to record timer metrics", ex);
116+
// Allow exchange to continue, unaffected by metrics problem
117+
}
108118
}
109119

110120
private Set<Timed> getTimedAnnotations(Object handler) {

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/LongTaskTimingHandlerInterceptor.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,8 @@
3030
import io.micrometer.core.instrument.LongTaskTimer;
3131
import io.micrometer.core.instrument.MeterRegistry;
3232
import io.micrometer.core.instrument.Tag;
33+
import org.apache.commons.logging.Log;
34+
import org.apache.commons.logging.LogFactory;
3335

3436
import org.springframework.core.annotation.MergedAnnotationCollectors;
3537
import org.springframework.core.annotation.MergedAnnotations;
@@ -46,6 +48,8 @@
4648
*/
4749
public class LongTaskTimingHandlerInterceptor implements HandlerInterceptor {
4850

51+
private static final Log logger = LogFactory.getLog(LongTaskTimingHandlerInterceptor.class);
52+
4953
private final MeterRegistry registry;
5054

5155
private final WebMvcTagsProvider tagsProvider;
@@ -90,12 +94,18 @@ private void startAndAttachTimingContext(HttpServletRequest request, Object hand
9094
private Collection<LongTaskTimer.Sample> getLongTaskTimerSamples(HttpServletRequest request, Object handler,
9195
Set<Timed> annotations) {
9296
List<LongTaskTimer.Sample> samples = new ArrayList<>();
93-
annotations.stream().filter(Timed::longTask).forEach((annotation) -> {
94-
Iterable<Tag> tags = this.tagsProvider.getLongRequestTags(request, handler);
95-
LongTaskTimer.Builder builder = LongTaskTimer.builder(annotation).tags(tags);
96-
LongTaskTimer timer = builder.register(this.registry);
97-
samples.add(timer.start());
98-
});
97+
try {
98+
annotations.stream().filter(Timed::longTask).forEach((annotation) -> {
99+
Iterable<Tag> tags = this.tagsProvider.getLongRequestTags(request, handler);
100+
LongTaskTimer.Builder builder = LongTaskTimer.builder(annotation).tags(tags);
101+
LongTaskTimer timer = builder.register(this.registry);
102+
samples.add(timer.start());
103+
});
104+
}
105+
catch (Exception ex) {
106+
logger.warn("Failed to start long task timers", ex);
107+
// Allow request-response exchange to continue, unaffected by metrics problem
108+
}
99109
return samples;
100110
}
101111

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilter.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import io.micrometer.core.instrument.Timer;
3131
import io.micrometer.core.instrument.Timer.Builder;
3232
import io.micrometer.core.instrument.Timer.Sample;
33+
import org.apache.commons.logging.Log;
34+
import org.apache.commons.logging.LogFactory;
3335

3436
import org.springframework.boot.actuate.metrics.AutoTimer;
3537
import org.springframework.boot.actuate.metrics.annotation.TimedAnnotations;
@@ -52,6 +54,8 @@
5254
*/
5355
public class WebMvcMetricsFilter extends OncePerRequestFilter {
5456

57+
private static final Log logger = LogFactory.getLog(WebMvcMetricsFilter.class);
58+
5559
private final MeterRegistry registry;
5660

5761
private final WebMvcTagsProvider tagsProvider;
@@ -127,11 +131,17 @@ private Throwable fetchException(HttpServletRequest request) {
127131

128132
private void record(TimingContext timingContext, HttpServletRequest request, HttpServletResponse response,
129133
Throwable exception) {
130-
Object handler = getHandler(request);
131-
Set<Timed> annotations = getTimedAnnotations(handler);
132-
Timer.Sample timerSample = timingContext.getTimerSample();
133-
AutoTimer.apply(this.autoTimer, this.metricName, annotations,
134-
(builder) -> timerSample.stop(getTimer(builder, handler, request, response, exception)));
134+
try {
135+
Object handler = getHandler(request);
136+
Set<Timed> annotations = getTimedAnnotations(handler);
137+
Timer.Sample timerSample = timingContext.getTimerSample();
138+
AutoTimer.apply(this.autoTimer, this.metricName, annotations,
139+
(builder) -> timerSample.stop(getTimer(builder, handler, request, response, exception)));
140+
}
141+
catch (Exception ex) {
142+
logger.warn("Failed to record timer metrics", ex);
143+
// Allow request-response exchange to continue, unaffected by metrics problem
144+
}
135145
}
136146

137147
private Object getHandler(HttpServletRequest request) {

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/MetricsWebFilterTests.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818

1919
import java.io.EOFException;
2020
import java.time.Duration;
21+
import java.util.concurrent.atomic.AtomicBoolean;
2122

2223
import io.micrometer.core.annotation.Timed;
2324
import io.micrometer.core.instrument.MockClock;
25+
import io.micrometer.core.instrument.Tag;
2426
import io.micrometer.core.instrument.simple.SimpleConfig;
2527
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
2628
import org.junit.jupiter.api.BeforeEach;
@@ -35,6 +37,7 @@
3537
import org.springframework.util.ReflectionUtils;
3638
import org.springframework.web.method.HandlerMethod;
3739
import org.springframework.web.reactive.HandlerMapping;
40+
import org.springframework.web.server.ServerWebExchange;
3841
import org.springframework.web.util.pattern.PathPatternParser;
3942

4043
import static org.assertj.core.api.Assertions.assertThat;
@@ -51,6 +54,8 @@ class MetricsWebFilterTests {
5154

5255
private static final String REQUEST_METRICS_NAME_PERCENTILE = REQUEST_METRICS_NAME + ".percentile";
5356

57+
private final FaultyWebFluxTagsProvider tagsProvider = new FaultyWebFluxTagsProvider();
58+
5459
private SimpleMeterRegistry registry;
5560

5661
private MetricsWebFilter webFilter;
@@ -59,7 +64,7 @@ class MetricsWebFilterTests {
5964
void setup() {
6065
MockClock clock = new MockClock();
6166
this.registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, clock);
62-
this.webFilter = new MetricsWebFilter(this.registry, new DefaultWebFluxTagsProvider(true), REQUEST_METRICS_NAME,
67+
this.webFilter = new MetricsWebFilter(this.registry, this.tagsProvider, REQUEST_METRICS_NAME,
6368
AutoTimer.ENABLED);
6469
}
6570

@@ -208,6 +213,14 @@ void filterAddsPercentileMeters() {
208213
assertThat(this.registry.get(REQUEST_METRICS_NAME_PERCENTILE).tag("phi", "0.5").gauge().value()).isNotZero();
209214
}
210215

216+
@Test
217+
void whenMetricsRecordingFailsThenExchangeFilteringSucceeds() {
218+
MockServerWebExchange exchange = createExchange("/projects/spring-boot", "/projects/{project}");
219+
this.tagsProvider.failOnce();
220+
this.webFilter.filter(exchange, (serverWebExchange) -> exchange.getResponse().setComplete())
221+
.block(Duration.ofSeconds(30));
222+
}
223+
211224
private MockServerWebExchange createTimedHandlerMethodExchange(String methodName) {
212225
MockServerWebExchange exchange = createExchange("/projects/spring-boot", "/projects/{project}");
213226
exchange.getAttributes().put(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE,
@@ -245,4 +258,26 @@ Mono<String> timedPercentiles() {
245258

246259
}
247260

261+
class FaultyWebFluxTagsProvider extends DefaultWebFluxTagsProvider {
262+
263+
private volatile AtomicBoolean fail = new AtomicBoolean(false);
264+
265+
FaultyWebFluxTagsProvider() {
266+
super(true);
267+
}
268+
269+
@Override
270+
public Iterable<Tag> httpRequestTags(ServerWebExchange exchange, Throwable exception) {
271+
if (this.fail.compareAndSet(true, false)) {
272+
throw new RuntimeException();
273+
}
274+
return super.httpRequestTags(exchange, exception);
275+
}
276+
277+
void failOnce() {
278+
this.fail.set(true);
279+
}
280+
281+
}
282+
248283
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2012-2021 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+
* https://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.metrics.web.servlet;
18+
19+
import java.util.concurrent.atomic.AtomicBoolean;
20+
21+
import javax.servlet.http.HttpServletRequest;
22+
import javax.servlet.http.HttpServletResponse;
23+
24+
import io.micrometer.core.instrument.Tag;
25+
26+
/**
27+
* {@link WebMvcTagsProvider} used for testing that can be configured to fail when getting
28+
* tags or long task tags.
29+
*
30+
* @author Andy Wilkinson
31+
*/
32+
class FaultyWebMvcTagsProvider extends DefaultWebMvcTagsProvider {
33+
34+
private volatile AtomicBoolean fail = new AtomicBoolean(false);
35+
36+
FaultyWebMvcTagsProvider() {
37+
super(true);
38+
}
39+
40+
@Override
41+
public Iterable<Tag> getTags(HttpServletRequest request, HttpServletResponse response, Object handler,
42+
Throwable exception) {
43+
if (this.fail.compareAndSet(true, false)) {
44+
throw new RuntimeException();
45+
}
46+
return super.getTags(request, response, handler, exception);
47+
}
48+
49+
@Override
50+
public Iterable<Tag> getLongRequestTags(HttpServletRequest request, Object handler) {
51+
if (this.fail.compareAndSet(true, false)) {
52+
throw new RuntimeException();
53+
}
54+
return super.getLongRequestTags(request, handler);
55+
}
56+
57+
void failOnce() {
58+
this.fail.set(true);
59+
}
60+
61+
}

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/LongTaskTimingHandlerInterceptorTests.java

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.util.concurrent.Callable;
2020
import java.util.concurrent.CompletableFuture;
2121
import java.util.concurrent.CyclicBarrier;
22+
import java.util.concurrent.TimeUnit;
2223
import java.util.concurrent.atomic.AtomicReference;
2324

2425
import io.micrometer.core.annotation.Timed;
@@ -76,6 +77,9 @@ class LongTaskTimingHandlerInterceptorTests {
7677
@Autowired
7778
private CyclicBarrier callableBarrier;
7879

80+
@Autowired
81+
private FaultyWebMvcTagsProvider tagsProvider;
82+
7983
private MockMvc mvc;
8084

8185
@BeforeEach
@@ -117,6 +121,26 @@ void asyncCallableRequest() throws Exception {
117121
.isEqualTo(0);
118122
}
119123

124+
@Test
125+
void whenMetricsRecordingFailsResponseIsUnaffected() throws Exception {
126+
this.tagsProvider.failOnce();
127+
AtomicReference<MvcResult> result = new AtomicReference<>();
128+
Thread backgroundRequest = new Thread(() -> {
129+
try {
130+
result.set(
131+
this.mvc.perform(get("/api/c1/callable/10")).andExpect(request().asyncStarted()).andReturn());
132+
}
133+
catch (Exception ex) {
134+
fail("Failed to execute async request", ex);
135+
}
136+
});
137+
backgroundRequest.start();
138+
this.callableBarrier.await(10, TimeUnit.SECONDS);
139+
this.callableBarrier.await(10, TimeUnit.SECONDS);
140+
backgroundRequest.join();
141+
this.mvc.perform(asyncDispatch(result.get())).andExpect(status().isOk());
142+
}
143+
120144
@Configuration(proxyBeanMethods = false)
121145
@EnableWebMvc
122146
@Import(Controller1.class)
@@ -138,13 +162,17 @@ CyclicBarrier callableBarrier() {
138162
}
139163

140164
@Bean
141-
WebMvcConfigurer handlerInterceptorConfigurer(MeterRegistry meterRegistry) {
165+
FaultyWebMvcTagsProvider webMvcTagsProvider() {
166+
return new FaultyWebMvcTagsProvider();
167+
}
168+
169+
@Bean
170+
WebMvcConfigurer handlerInterceptorConfigurer(MeterRegistry meterRegistry, WebMvcTagsProvider tagsProvider) {
142171
return new WebMvcConfigurer() {
143172

144173
@Override
145174
public void addInterceptors(InterceptorRegistry registry) {
146-
registry.addInterceptor(
147-
new LongTaskTimingHandlerInterceptor(meterRegistry, new DefaultWebMvcTagsProvider()));
175+
registry.addInterceptor(new LongTaskTimingHandlerInterceptor(meterRegistry, tagsProvider));
148176
}
149177

150178
};

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilterTests.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ class WebMvcMetricsFilterTests {
122122
@Qualifier("completableFutureBarrier")
123123
private CyclicBarrier completableFutureBarrier;
124124

125+
@Autowired
126+
private FaultyWebMvcTagsProvider tagsProvider;
127+
125128
@BeforeEach
126129
void setupMockMvc() {
127130
this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
@@ -193,6 +196,12 @@ void streamingError() throws Exception {
193196
.isEqualTo(1L);
194197
}
195198

199+
@Test
200+
void whenMetricsRecordingFailsResponseIsUnaffected() throws Exception {
201+
this.tagsProvider.failOnce();
202+
this.mvc.perform(get("/api/c1/10")).andExpect(status().isOk());
203+
}
204+
196205
@Test
197206
void anonymousError() {
198207
try {
@@ -365,9 +374,14 @@ CyclicBarrier completableFutureBarrier() {
365374
}
366375

367376
@Bean
368-
WebMvcMetricsFilter webMetricsFilter(MeterRegistry registry, WebApplicationContext ctx) {
369-
return new WebMvcMetricsFilter(registry, new DefaultWebMvcTagsProvider(true), "http.server.requests",
370-
AutoTimer.ENABLED);
377+
WebMvcMetricsFilter webMetricsFilter(MeterRegistry registry, FaultyWebMvcTagsProvider tagsProvider,
378+
WebApplicationContext ctx) {
379+
return new WebMvcMetricsFilter(registry, tagsProvider, "http.server.requests", AutoTimer.ENABLED);
380+
}
381+
382+
@Bean
383+
FaultyWebMvcTagsProvider faultyWebMvcTagsProvider() {
384+
return new FaultyWebMvcTagsProvider();
371385
}
372386

373387
}

0 commit comments

Comments
 (0)