Record server metrics for cancelled connections
Prior to this commit, the Actuator instrumentation for WebFlux servers would not record metrics in two cases: * the client disconnects before the response has been sent * a server timeout is triggered before the response is sent This commit improves the existing instrumentation to record metrics in these cases. Since the causes of timeouts/disconnections can vary a lot, the chosen "outcome" tag for metrics is "UNKNOWN". Closes gh-23606
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
* Copyright 2012-2021 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.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.boot.actuate.metrics.web.reactive.server;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.time.Duration;
|
||||
|
||||
import io.micrometer.core.instrument.MockClock;
|
||||
@@ -24,6 +25,7 @@ import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.boot.actuate.metrics.AutoTimer;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
@@ -116,6 +118,32 @@ class MetricsWebFilterTests {
|
||||
assertThat(this.registry.get(REQUEST_METRICS_NAME).tag("status", "200").timer().count()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancelledConnectionsShouldProduceMetrics() {
|
||||
MockServerWebExchange exchange = createExchange("/projects/spring-boot", "/projects/{project}");
|
||||
Mono<Void> processing = this.webFilter.filter(exchange,
|
||||
(serverWebExchange) -> exchange.getResponse().setComplete());
|
||||
StepVerifier.create(processing).thenCancel().verify(Duration.ofSeconds(5));
|
||||
assertMetricsContainsTag("uri", "/projects/{project}");
|
||||
assertMetricsContainsTag("status", "200");
|
||||
assertMetricsContainsTag("outcome", "UNKNOWN");
|
||||
}
|
||||
|
||||
@Test
|
||||
void disconnectedExceptionShouldProduceMetrics() {
|
||||
MockServerWebExchange exchange = createExchange("/projects/spring-boot", "/projects/{project}");
|
||||
Mono<Void> processing = this.webFilter
|
||||
.filter(exchange, (serverWebExchange) -> Mono.error(new EOFException("Disconnected")))
|
||||
.onErrorResume((t) -> {
|
||||
exchange.getResponse().setRawStatusCode(500);
|
||||
return exchange.getResponse().setComplete();
|
||||
});
|
||||
StepVerifier.create(processing).expectComplete().verify(Duration.ofSeconds(5));
|
||||
assertMetricsContainsTag("uri", "/projects/{project}");
|
||||
assertMetricsContainsTag("status", "500");
|
||||
assertMetricsContainsTag("outcome", "UNKNOWN");
|
||||
}
|
||||
|
||||
private MockServerWebExchange createExchange(String path, String pathPattern) {
|
||||
PathPatternParser parser = new PathPatternParser();
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(path).build());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
* Copyright 2012-2021 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.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.boot.actuate.metrics.web.reactive.server;
|
||||
|
||||
import java.io.EOFException;
|
||||
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -140,7 +142,7 @@ class WebFluxTagsTests {
|
||||
@Test
|
||||
void outcomeTagIsSuccessWhenResponseStatusIsNull() {
|
||||
this.exchange.getResponse().setStatusCode(null);
|
||||
Tag tag = WebFluxTags.outcome(this.exchange);
|
||||
Tag tag = WebFluxTags.outcome(this.exchange, null);
|
||||
assertThat(tag.getValue()).isEqualTo("SUCCESS");
|
||||
}
|
||||
|
||||
@@ -153,56 +155,68 @@ class WebFluxTagsTests {
|
||||
given(response.getRawStatusCode()).willReturn(null);
|
||||
given(exchange.getRequest()).willReturn(request);
|
||||
given(exchange.getResponse()).willReturn(response);
|
||||
Tag tag = WebFluxTags.outcome(exchange);
|
||||
Tag tag = WebFluxTags.outcome(exchange, null);
|
||||
assertThat(tag.getValue()).isEqualTo("SUCCESS");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outcomeTagIsInformationalWhenResponseIs1xx() {
|
||||
this.exchange.getResponse().setStatusCode(HttpStatus.CONTINUE);
|
||||
Tag tag = WebFluxTags.outcome(this.exchange);
|
||||
Tag tag = WebFluxTags.outcome(this.exchange, null);
|
||||
assertThat(tag.getValue()).isEqualTo("INFORMATIONAL");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outcomeTagIsSuccessWhenResponseIs2xx() {
|
||||
this.exchange.getResponse().setStatusCode(HttpStatus.OK);
|
||||
Tag tag = WebFluxTags.outcome(this.exchange);
|
||||
Tag tag = WebFluxTags.outcome(this.exchange, null);
|
||||
assertThat(tag.getValue()).isEqualTo("SUCCESS");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outcomeTagIsRedirectionWhenResponseIs3xx() {
|
||||
this.exchange.getResponse().setStatusCode(HttpStatus.MOVED_PERMANENTLY);
|
||||
Tag tag = WebFluxTags.outcome(this.exchange);
|
||||
Tag tag = WebFluxTags.outcome(this.exchange, null);
|
||||
assertThat(tag.getValue()).isEqualTo("REDIRECTION");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outcomeTagIsClientErrorWhenResponseIs4xx() {
|
||||
this.exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
|
||||
Tag tag = WebFluxTags.outcome(this.exchange);
|
||||
Tag tag = WebFluxTags.outcome(this.exchange, null);
|
||||
assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outcomeTagIsServerErrorWhenResponseIs5xx() {
|
||||
this.exchange.getResponse().setStatusCode(HttpStatus.BAD_GATEWAY);
|
||||
Tag tag = WebFluxTags.outcome(this.exchange);
|
||||
Tag tag = WebFluxTags.outcome(this.exchange, null);
|
||||
assertThat(tag.getValue()).isEqualTo("SERVER_ERROR");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outcomeTagIsClientErrorWhenResponseIsNonStandardInClientSeries() {
|
||||
this.exchange.getResponse().setRawStatusCode(490);
|
||||
Tag tag = WebFluxTags.outcome(this.exchange);
|
||||
Tag tag = WebFluxTags.outcome(this.exchange, null);
|
||||
assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outcomeTagIsUnknownWhenResponseStatusIsInUnknownSeries() {
|
||||
this.exchange.getResponse().setRawStatusCode(701);
|
||||
Tag tag = WebFluxTags.outcome(this.exchange);
|
||||
Tag tag = WebFluxTags.outcome(this.exchange, null);
|
||||
assertThat(tag.getValue()).isEqualTo("UNKNOWN");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outcomeTagIsClientErrorWhenExceptionIsDisconnectedClient() {
|
||||
Tag tag = WebFluxTags.outcome(this.exchange, new EOFException("broken pipe"));
|
||||
assertThat(tag.getValue()).isEqualTo("UNKNOWN");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outcomeTagIsClientErrorWhenExceptionIsCancelledExchange() {
|
||||
Tag tag = WebFluxTags.outcome(this.exchange, new CancelledServerWebExchangeException());
|
||||
assertThat(tag.getValue()).isEqualTo("UNKNOWN");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user