4

For any 4xx or 5xx response given out by my micronaut server, I'd like to log the response status code and endpoint it targeted. It looks like a filter would be a good place for this, but I can't seem to figure out how to plug into the onError handling

for instance, this filter

@Filter("/**")
class RequestLoggerFilter: OncePerRequestHttpServerFilter() {
  companion object {
    private val log = LogManager.getLogger(RequestLoggerFilter::class.java)
  }

  override fun doFilterOnce(request: HttpRequest<*>, chain: ServerFilterChain): Publisher<MutableHttpResponse<*>>? {
    return Publishers.then(chain.proceed(request), ResponseLogger(request))
  }

  class ResponseLogger(private val request: HttpRequest<*>): Consumer<MutableHttpResponse<*>> {
    override fun accept(response: MutableHttpResponse<*>) {
      log.info("Status: ${response.status.code} Endpoint: ${request.path}")
    }
  }
}

only logs on a successful response and not on 4xx or 5xx responses. How would i get this to hook into the onError handling?

Knut Knutson
  • 163
  • 2
  • 8
  • Have you looked at the `@Error` annotation (https://docs.micronaut.io/2.0.1/guide/index.html#errorHandling)? – Jeff Scott Brown Aug 03 '20 at 20:02
  • I did check out the error annotation, and maybe that's the route to go. But if I create a generic error handler that catches Exception then it seems like I lose some nice default error handling that's given for free (like io.micronaut.core.convert.exceptions.ConversionErrorException being turned into a bad request). Is there a way to have a general error handler perform some logging and then pass off behavior to what would normally occur? : I guess what I'd really like is for the regular error handling to occur and then log the final result - 400 to /path – Knut Knutson Aug 03 '20 at 20:14

3 Answers3

1

You could do the following. Create your own ApplicationException ( extends RuntimeException), there you could handle your application errors and in particular how they result into http error codes. You exception could hold the status code as well.

Example:

class BadRequestException extends ApplicationException {
 
    public HttpStatus getStatus() {
        return HttpStatus.BAD_REQUEST;
    }
}

You could have multiple of this ExceptionHandler for different purposes.

@Slf4j
@Produces
@Singleton
@Requires(classes = {ApplicationException.class, ExceptionHandler.class})
public class ApplicationExceptionHandler implements ExceptionHandler<ApplicationException, HttpResponse> {

    @Override
    public HttpResponse handle(final HttpRequest request, final ApplicationException exception) {
        log.error("Application exception message={}, cause={}", exception.getMessage(), exception.getCause());
        final String message = exception.getMessage();
        final String code = exception.getClass().getSimpleName();
        final ErrorCode error = new ErrorCode(message, code);

        log.info("Status: ${exception.getStatus())} Endpoint: ${request.path}")

        return HttpResponse.status(exception.getStatus()).body(error);
    }
}
Traycho Ivanov
  • 2,887
  • 14
  • 24
1

If you are trying to handle Micronaut native exceptions like 400 (Bad Request) produced by ConstraintExceptionHandler you will need to Replace the beans to do that.

I've posted example here how to handle ConstraintExceptionHandler.

If you want to only handle responses itself you could use this mapping each response code (example on @Controller so not sure if it works elsewhere even with global flag:

@Error(status = HttpStatus.NOT_FOUND, global = true)  
public HttpResponse notFound(HttpRequest request) {
  <...>
}

Example from Micronaut documentation.

saganas
  • 555
  • 4
  • 12
0

Below code I used for adding custom cors headers in the error responses, in doOnError you can log errors

@Filter("/**")
public class ResponseCORSAdder implements HttpServerFilter {
    @Override
    public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
            return this.trace(request) 
            .switchMap(aBoolean -> chain.proceed(request))
            .doOnError(error -> {
                if (error instanceof MutableHttpResponse<?>) {
                    MutableHttpResponse<?> res = (MutableHttpResponse<?>) error;
                    addCorsHeaders(res);
                }
            })  
            .doOnNext(res -> addCorsHeaders(res));
    }

        private MutableHttpResponse<?> addCorsHeaders(MutableHttpResponse<?> res) {
            return res
            .header("Access-Control-Allow-Origin", "*")
            .header("Access-Control-Allow-Methods", "OPTIONS,POST,GET")
            .header("Access-Control-Allow-Credentials", "true");
        }

        private Flowable<Boolean> trace(HttpRequest<?> request) {
            return Flowable.fromCallable(() -> { 
                    // trace logic here, potentially performing I/O 
                    return true;
            }).subscribeOn(Schedulers.io()); 
        }
}
Harsh Rohila
  • 412
  • 3
  • 10