Sitemap
Booking.com Engineering

Software engineering at Booking.com

Unlocking observability: Structured logging in Spring Boot

10 min readMar 26, 2024

--

Source: https://geek-and-poke.com/geekandpoke/2015/10/18/why-logging-is-so-important

But first, what is structured logging?

Companion project

The key benefits of structured logging

Enhanced queryability

Finding one piece of useful information from raw logs emitted by applications can be difficult without enhanced queryability through stuctured logging
Structured logging can help uncover behavioral traits — here the 400 error codes only happen on the POST /party/ route

Standard fields in logs

Contextual information like URI and request method may not be present in the log message itself, but can be ingested by the application as extra data through structured logging

Deeper insights through visualizations

Structured logging as source of data can be aggregated to view overall statistics like the percent of requests that end with a 4xx error

How to add structured logging with SLF4J

Step 1: Generating logs

SLF4J is a logging facade that works with many logging backends, in this article, we demonstrate structured logging with SLF4J and Logback as a backend
public class PartyController {
private final Logger logger = LoggerFactory.getLogger(getClass().getName());

@Operation
@GetMapping("/")
public Map<String, String> index() {
logger.info("Reading index path");
return Map.of("message", "Welcome to Jamboree!");
}
}
MDC.put("REQUEST_URI", request.getRequestURI());

if (params.location() == null) {
logger.error("Missing parameter: location");
return Response.error(HttpStatus.BAD_REQUEST, "Location is mandatory.");
}

// clean up
MDC.remove("REQUEST_URI");

Step 2: Defining the log format

Logback configuration

<appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/jamboree.log</file>

<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/jamboree-%d{yyyy-MM-dd-HH-mm}.log</fileNamePattern>
<maxHistory>60</maxHistory>
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>

<layout class="ch.qos.logback.contrib.json.classic.JsonLayout">
<jsonFormatter class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter">
<prettyPrint>false</prettyPrint>
</jsonFormatter>
<timestampFormat>yyyy-MM-dd HH:mm:ss.SSS</timestampFormat>
<appendLineSeparator>true</appendLineSeparator>
</layout>
</appender>
$ head logs/jamboree-2023-12-04-08-48.log
{"timestamp":"2023-12-04 08:48:00.106","level":"INFO","thread":"http-nio-7123-exec-2","mdc":{"REQUEST_URI":"/party/","TRACE_ID":"e10d91d9-b822-40cb-8794-45a103b6c248","REQUEST_ID":"66481f10-53ee-4c88-b9b2-8547b1416b85","PARTY_ID":"12442","REQUEST_METHOD":"POST"},"logger":"me.mourjo.jamboree.rest.PartyController","message":"Creating a party with PartyRequest[name=Adi Dhakeswari, location=Kolkata, time=null]","context":"default"}
{"timestamp":"2023-12-04 08:48:00.113","level":"ERROR","thread":"http-nio-7123-exec-2","mdc":{"REQUEST_URI":"/party/","TRACE_ID":"e10d91d9-b822-40cb-8794-45a103b6c248","REQUEST_ID":"66481f10-53ee-4c88-b9b2-8547b1416b85","PARTY_ID":"12442","REQUEST_METHOD":"POST"},"logger":"me.mourjo.jamboree.rest.PartyController","message":"Missing parameter: time","context":"default"}

Step 3: Ingestion into logging infrastructure

The logging infrastructure for our demo project uses the ELK stack which runs inside docker containers

Contextual information: What led to this method being called?

The method that logs a piece of information may not have the contextual information that makes structured logging so useful — for example, the DateFormat class is unaware of the request method or trace ID but the log emitted by the application will include this information
Contextual information like TRACE_ID can allow grouping logs that were generated only while processing a single request — it shows a timeline of all the logs as it were emitted as if it was the only thing happening in the application

Logging context in a multi-threaded environment

Uncleared logging context can lead to unexplained behavior — like a different ID in the context to the ID in the log message
try {
MDC.put("PARTY_ID", getPartyId());
doSomeWork(); /// other logic
} finally {
/// note MDC.clear() will remove all keys including the ones not added by this method
MDC.clear();
}

Thread Pool Support

Macro analysis of logs

Data ingested from fields in structured logs can be aggregated to view statistics like the number of views in a given time window

Conclusion

--

--

Mourjo Sen
Mourjo Sen

Written by Mourjo Sen

• Software engineer 👷 by trade • Software artisan 👨‍🎨 at heart • With a passion for writing ✍️

Responses (5)