-
Notifications
You must be signed in to change notification settings - Fork 269
Description
Since Spring Boot 3.4, there is a way to configure logging directly in spring in a way the output is a json document.
https://spring.io/blog/2024/08/23/structured-logging-in-spring-boot-3-4
When using default sink and writer, the entire logbook json is serialized to a string.
That string (message) is later used in structured logging to populate the message, the final log look like that:
{
"@timestamp": "2025-06-17T13:57:03.956984242Z",
"log": {
"level": "TRACE",
"logger": "org.zalando.logbook.Logbook"
},
"message": "\"duration\": \"267\",\n\"protocol\": \"HTTP\\/1.1\",\n\"status\": \"200\",\"type\": \"response\"",
"ecs": {
"version": "8.11"
},
...
}
That means the content of the message will not be recognized as a json in tools like ElasticSearch not will not be indexed correctly.
The solution my team implemented is not to put the whole json as string into message, but to use MDC to populate individual fields from what logbook collected (a map) and keep the message as human readable string (which I think is how it's meant to be)
class StructuredLogbookSink(private val formatter: JsonHttpLogFormatter, private val writer: HttpLogWriter) : Sink {
override fun write(precorrelation: Precorrelation, request: HttpRequest){
val msg = "Request: ${request.method} ${request.requestUri}"
val requestLogMap = formatter.prepare(precorrelation, request)
withMDC(requestLogMap) {
writer.write(precorrelation, msg)
}
}
override fun write(correlation: Correlation, request: HttpRequest, response: HttpResponse){
val msg = "Response: ${request.method} ${request.requestUri} - Status: ${response.status}"
val responseLogMap = formatter.prepare(correlation, response)
withMDC(responseLogMap) {
writer.write(correlation, msg)
}
}
private fun withMDC(map: Map<String, Any>, block: () -> Unit) {
map.forEach { (key, value) ->
MDC.put("logbook.$key", value.toString())
}
block()
map.keys.forEach { key ->
MDC.remove("logbook.$key")
}
}
}
Using that sink will result in log like that:
{
"@timestamp": "2025-06-17T13:57:03.956984242Z",
"log": {
"level": "TRACE",
"logger": "org.zalando.logbook.Logbook"
},
"message": "Response: GET http:\/\/localhost:8080\/api\/actuator\/health - Status: 200",
"logbook": {
"duration": "267",
"protocol": "HTTP\/1.1",
"status": "200",
"origin": "local",
"type": "response"
},
"ecs": {
"version": "8.11"
}
}
What I don't like about it is that it uses JsonHttpLogFormatter to get the map of log properties even if the json is not used in the end, it's just the method that extracts properties from request/response is there.
Second it's a custom code that we will need to maintain and possible this is something more people will be interested about.
Do you think such a feature could be implemented directly in logbook / spring boot starter?