Request Logging
requestLogger(…) takes one lambda and calls it once per completed request:
app.requestLogger((req, completion) -> logger.info("{} {} -> {} ({}ms)",
req.method(), req.path(), completion.statusCode(), completion.took().toMillis()));
Core carries no logging framework, so which logger, at which level, and in which format is the application’s call.
The lambda receives a RequestCompletion after response writing finishes.
statusCode() is the final status reported by the servlet response, including a status changed by a raw writer or by recovery from a write failure.
took() is a Duration covering dispatch, rendering, decoration, and writing.
response() is the response definition after decoration, or the last definition available if decoration failed.
Its status and headers may differ from what a raw writer sent.
failure() is the exception from decoration or writing, or null when neither failed.
failed() reports whether that exception is present.
A failure before commitment can produce a 500; a failure after commitment can leave a 200 with a partial body.
Inspect the failure independently of the status code.
An application exception handled during dispatch is represented by its response status, not by a transmission failure.
Normal SSE closure is not a transmission failure.
Completion does not confirm receipt of the entire body by the remote client.
exception() is what a handler, a filter, or a template threw, whether an exception handler answered it or the framework’s 500 did.
threw() reports whether that exception is present.
It is null when nothing threw, and for an HttpException, which is a status the handler chose rather than a failure.
Read it together with statusCode(): an exception mapped to a 400 is the caller’s mistake, and one answered with a 500 is the application’s.
A logger that records failures on a tracing span or in an error list reads it here, instead of registering a catch-all exception(…) handler that would also have to answer the request:
app.requestLogger((req, completion) -> {
if (completion.threw() && completion.statusCode() >= 500) {
Span.current().recordException(completion.exception());
}
});
req.route() is the route that answered, when one did, so a log line or a metric groups requests by /decks/{deckId} rather than by every id.
It is null for a static file, a 404, a 405, and the automatic OPTIONS answer.
Route Introspection has the rules.
req.body() reads the text a filter or handler already read, since the text is kept for the whole request.
A logger that throws is reported to the servlet log and leaves the response alone.