Spring-boot: MetricsWebFilter assigns UNKNOWN outcome to 200 OK responses

Created on 13 Dec 2019  路  10Comments  路  Source: spring-projects/spring-boot

If REST controller doesn't explicitly set status code (so it defaults to 200 OK), MetricsWebFilter will set outcome tag to UNKNOWN instead of SUCCESS.

Example application to reproduce:

package net.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@SpringBootApplication
public class ExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(ExampleApplication.class, args);
    }

}

@RestController
class ExampleController {

    @GetMapping("/example")
    Mono<String> example() {
        return Mono.just("EXAMPLE");
    }

}

```yaml
management:
endpoints:
web:
exposure:
include:
- metrics

```sh
curl http://localhost:8080/example

curl http://localhost:8080/actuator/metrics/http.server.requests?tag=uri:/example
# availableTags{tag="outcome"} only has "UNKNOWN" value
bug

All 10 comments

I can confirm this behaviour is present in spring boot 2.2.2

The behaviour looks to be caused by the following method:
https://github.com/spring-projects/spring-boot/blob/b15e427a3e5d7232e21fcfac43c02763f73b68ad/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTags.java#L145

There is a discrepancy between how the status code is resolved for the status code tag how it is resolved for the outcome tag.

In spring-projects/spring-framework#21901, we can see that the ServerHttpResponse contract is about modifying the response and falling back on the server defaults (HTTP 200 OK) if nothing has been set by the application.
In gh-18267, we also see that we rely on org.springframework.http.server.reactive.AbstractServerHttpResponse#getStatusCodeValue to handle non-standard status codes. But this method does not itself look into the native server response and can return null.

I'm wondering if org.springframework.http.server.reactive.AbstractServerHttpResponse#getStatusCodeValue should return the underlying default server response status. Of if we should, for consistency, return Outcome.SUCCESS in https://github.com/spring-projects/spring-boot/blob/bb3b6dbd7d213e7898fb1f2a88bcea8aae8b6c7f/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTags.java#L137-L141

@rstoyanchev do you think we should rely at all on org.springframework.http.server.reactive.AbstractServerHttpResponse#getStatusCodeValue in the first place - if not, how should we handle non-standard HTTP status codes?

AbstractServerHttpResponse#getStatusCodeValue could be improved to fall back on getStatusCode() but I'm reluctant to change it because it is the only way to know if the status has been set at all. It is also a bit of an internal method to begin with.

I think you could update WebFluxTags to something like this:

private static Integer extractStatusCode(ServerWebExchange exchange) {
    ServerHttpResponse response = exchange.getResponse();
    Integer statusCode = null;      
    if (response instanceof AbstractServerHttpResponse) {
        statusCode = ((AbstractServerHttpResponse) response).getStatusCodeValue();
    }
    if (statusCode == null) {
        HttpStatus httpStatus = response.getStatusCode();
        statusCode = (httpStatus != null ? httpStatus.value() : null);
    }
    return statusCode;
}

Thanks Rossen, we'll switch from Outcome.UNKNOWN to Outcome.SUCCESS by default, since this will be the default HTTP status if no other information is provided. We can use that opportunity to improve status extraction with the code snippet you suggested. Thanks!

@rstoyanchev @bclozel I'm trying to understand why the above snippet is better than what we currently have. Is there any situation where an AbstractServerHttpResponse#getStatusCodeValue would return null but AbstractServerHttpResponse#getStatusCode would not?

@mbhave maybe what you're missing is the fact that getStatusCode() is overridden in sub-classes which fall back on the underlying server default. In short the situation is whenever the status code is not explicitly set.

ah, I see what you mean now. Thanks @rstoyanchev.

Note that with https://github.com/spring-projects/spring-framework/issues/24400 ServerHttpResponse now exposes getRawStatusCode() so you can avoid the downcast to AbstractServerHttpResponse which is now a deprecated method anyway.

I created https://github.com/spring-projects/spring-boot/pull/19987 to apply the upstream change.

Was this page helpful?
0 / 5 - 0 ratings