Logs
How Spring Boot logs
In the cross-cutting concerns overview, logging was introduced as the concern that answers "What happened?". This document walks through setting it up properly.
In Spring Boot, SLF4J and Logback work together to provide a logging setup.
- SLF4J = Simple Logging Facade for Java. Think of it as a logging API (an abstraction layer). Your application code talks to SLF4J, not directly to a specific logging implementation. This decouples your code from how logs are written.
- Logback = The actual logging implementation (the engine that does the heavy lifting). It's the default logging backend that SLF4J uses in a Spring Boot application.
Even though Spring Boot includes SLF4J and Logback automatically and no setup is required to get basic logging working, you should improve the logging setup. Structured logging with trace IDs, centralized sensitive data masking, and consistent request/response logging make debugging production issues significantly easier.
Files overview
- Java
- Kotlin
- Groovy
Dependencies
- Java
- Kotlin
- Groovy
implementation 'org.springframework.boot:spring-boot-starter-aspectj'
implementation 'org.springframework.boot:spring-boot-starter-opentelemetry'
implementation("io.github.oshai:kotlin-logging-jvm:8.0.01")
implementation("org.springframework.boot:spring-boot-starter-aspectj")
implementation("org.springframework.boot:spring-boot-starter-opentelemetry")
kotlin-logging is a wrapper
around SLF4J that provides a more idiomatic Kotlin way to log messages. It
does not replace SLF4J or Logback; instead, it simplifies logging calls in
Kotlin by using extension functions and lazy evaluation for messages. The
logging hierarchy remains: Kotlin code → kotlin-logging → SLF4J → Logback
(Spring Boot default)
implementation 'org.springframework.boot:spring-boot-starter-aspectj'
implementation 'org.springframework.boot:spring-boot-starter-opentelemetry'
Avoid local OTLP log publishing
When you add spring-boot-starter-opentelemetry to your dependencies, Spring Boot automatically configures OpenTelemetry exporters to publish metrics and traces via OTLP (OpenTelemetry Protocol).
The problem is that on local development, you typically don't have an OTLP collector running. Spring Boot will repeatedly try to connect to the default OTLP endpoint and fail, spamming your console with connection error messages that drown out your actual application logs.
To avoid this noise during local development, disable OTLP metrics export in your dev profile:
- Java
- Kotlin
- Groovy
spring:
application:
name: spring_java
management:
otlp:
metrics:
export:
enabled: false
spring:
application:
name: spring_kotlin
management:
otlp:
metrics:
export:
enabled: false
spring:
application:
name: spring_groovy
management:
otlp:
metrics:
export:
enabled: false
From now on, run the bootRun Gradle task with the SPRING_PROFILES_ACTIVE=dev environment variable to activate the dev profile:
SPRING_PROFILES_ACTIVE=dev ./gradlew bootRun
Log aspect
Using an Aspect for logging is a classic example of AOP in action. Instead of scattering log statements all over your business logic (which clutters the code and mixes concerns), you define the logging behavior once in a separate module. This aspect then automatically intercepts method calls you care about and applies the logging logic without the target code even knowing it's happening.
For a deeper dive, check out the AOP section on Cross-Cutting Concerns.
- Java
- Kotlin
- Groovy
Log filter
Sometimes, a request might not even reach your Spring MVC controllers. By logging at the Servlet level, it grants you visibility into every incoming request and outgoing response, regardless of whether it hits your application's specific endpoints.
This can be incredibly useful for debugging issues like authentication failures, routing problems, or requests that are blocked higher up in the filter chain.
- Java
- Kotlin
- Groovy
Tracing
Ever tried to debug an issue in a busy system by trawling through a massive log file? It's like trying to find a specific needle in a haystack of needles.
This is where tracing comes in. By assigning a unique ID (a 'Trace ID') to each incoming request, you can tag every single log entry generated during that request's lifecycle. Suddenly, you can filter the entire log file to see the journey of just one request across multiple methods, services, or threads.
- Java
- Kotlin
- Groovy
Mask sensitive data in logs with Logback
It's important to mask sensitive details when logging (i.e., passwords, SSN, etc.). Let's mask the logs centrally by configuring masking rules for all log entries produced by Logback.
Create
MaskingPatternLayout:- Java
- Kotlin
- Groovy
Add regex patterns in
maskPatterntags insidelogback.xml:- Java
- Kotlin
- Groovy
Check how logs look like
Run the following curl command (with placeholder values) to see everything working: sensitive data masking, the aspect intercepting controller method invocations, how the logs contain the trace, and the filter printing the HTTP request and response.
curl -s --request GET --url http://localhost:8080/api/films/42 --header 'Accept: application/json' --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' --header 'Cookie: JSESSIONID=A1B2C3D4E5F6G7H8I9J0; auth_token=secret123token456' --header 'Proxy-Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=' --header 'User-Agent: Mozilla/5.0 (Test Client)' --header 'X-API-Key: super-secret-api-key' --header 'X-Auth-Token: super-secret-auth-token-12345' --header 'X-CSRF-Token: csrf_abc123def456ghi789' | jq
Logs should look something like this:
2026-02-18 15:28:11.600 trace_id=b8e1447340832e9b466fde0a1f172b55 span_id=a4fa1234784f7c02 trace_flags=01 INFO http-nio-8080-exec-1 --- d.p.spring_java.config.log.LogFilter >>>> Method: GET; URI: /api/films/42; QueryString: null; Headers: {Host: localhost:8080, Accept: application/json, Authorization: ****, Cookie: ****, Proxy-Authorization: ****, User-Agent: Mozilla/5.0 (Test Client), X-API-Key: ****, X-Auth-Token: ****, X-CSRF-Token: ****
2026-02-18 15:28:11.619 trace_id=b8e1447340832e9b466fde0a1f172b55 span_id=a4fa1234784f7c02 trace_flags=01 INFO http-nio-8080-exec-1 --- d.p.spring_java.config.log.LogAspect [FilmRestController.getFilm(..)] Args: [42]
2026-02-18 15:28:11.620 trace_id=b8e1447340832e9b466fde0a1f172b55 span_id=a4fa1234784f7c02 trace_flags=01 INFO http-nio-8080-exec-1 --- d.p.spring_java.config.log.LogAspect [FilmRestController.getFilm(..)] Response: FilmResponse(id=42, title=ACADEMY DINOSAUR, description=A Epic Drama of a Feminist And a Mad Scientist who must Battle a Teacher in The Canadian Rockies, releaseYear=2006, rating=PG, lengthMinutes=86, language=English)
2026-02-18 15:28:11.664 trace_id=b8e1447340832e9b466fde0a1f172b55 span_id=a4fa1234784f7c02 trace_flags=01 INFO http-nio-8080-exec-1 --- d.p.spring_java.config.log.LogFilter <<<< Response Status: 200