Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ricpalomino/spring-boot/llms.txt

Use this file to discover all available pages before exploring further.

Every HTTP request that reaches the Products API is automatically tracked from arrival to completion by LoggingInterceptor. The interceptor generates a unique trace ID for each request, writes that ID into the SLF4J Mapped Diagnostic Context (MDC), and logs the method, URI, response status, and wall-clock duration. Logback then stamps that trace ID onto every log line emitted during the request’s lifetime, so you can search your log files for a single ID and see the full picture of what happened.

How request tracing works

LoggingInterceptor implements Spring MVC’s HandlerInterceptor and is registered globally through LoggingConfig:
public class LoggingInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String traceId = UUID.randomUUID().toString();
        MDC.put("traceId", traceId);

        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);

        logger.info("=== REQUEST START === | Method: {} | URI: {} | RemoteAddr: {}",
            request.getMethod(), request.getRequestURI(), request.getRemoteAddr());

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        long startTime = (long) request.getAttribute("startTime");
        long duration = System.currentTimeMillis() - startTime;

        logger.info("=== REQUEST END === | Status: {} | Duration: {}ms", response.getStatus(), duration);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        if (ex != null) {
            logger.error("=== REQUEST ERROR === | Exception: {}", ex.getMessage(), ex);
        }
        MDC.clear();
    }
}
The three lifecycle hooks each serve a distinct purpose:
HookFiresWhat it logs
preHandleBefore the controller methodHTTP method, request URI, client IP, and the newly generated trace ID
postHandleAfter the controller method returnsHTTP response status and total request duration in milliseconds
afterCompletionAfter the response is committedException message and stack trace if an unhandled exception occurred
MDC.clear() in afterCompletion removes the trace ID from the thread’s context so it does not leak into the next request handled by the same thread pool worker.

Log output format

The file log pattern from application.properties is:
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %X{traceId} - %msg%n
A typical request produces two lines in logs/application.log:
2026-05-19 14:23:01 [http-nio-8080-exec-3] INFO  c.z.s.i.LoggingInterceptor - a3f1b2c4-... - === REQUEST START === | Method: GET | URI: /api/v1/products | RemoteAddr: 127.0.0.1
2026-05-19 14:23:01 [http-nio-8080-exec-3] INFO  c.z.s.i.LoggingInterceptor - a3f1b2c4-... - === REQUEST END === | Status: 200 | Duration: 12ms
The %X{traceId} token is replaced by the UUID written to the MDC in preHandle. If a log line is emitted outside of a request (e.g., during startup), that token renders as an empty string. The console pattern is more compact and omits the thread name and trace ID for readability during development:
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n

Logback configuration

logback-spring.xml defines three appenders:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <springProperty name="LOG_FILE" source="logging.file.name" defaultValue="logs/application.log"/>

    <!-- Console Appender -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%-5level] [%X{traceId}] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- File Appender -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_FILE}</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] [%-5level] [%X{traceId}] %logger{36} - %msg%n</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/application-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>10</maxHistory>
            <totalSizeCap>100MB</totalSizeCap>
        </rollingPolicy>
    </appender>

    <!-- Error File Appender -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/error.log</file>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        ...
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
        <appender-ref ref="ERROR_FILE"/>
    </root>
</configuration>

CONSOLE

Writes all levels to standard output. Includes the trace ID bracket so you can correlate local development output with a specific request.

FILE

Writes all levels to logs/application.log. Rotates by size and date. Archived files follow the pattern logs/application-2026-05-19.0.log.

ERROR_FILE

Writes only ERROR-level lines to logs/error.log. Useful for monitoring: tail this file or ship it to an alerting system without processing the full application log.

File rotation policy

SettingValueEffect
maxFileSize10MBTriggers rotation when the active file reaches 10 MB
maxHistory10Keeps at most 10 rotated archive files
totalSizeCap100MBDeletes the oldest archives if the total exceeds 100 MB
Rotated files are written to the same logs/ directory with the naming pattern logs/application-{date}.{index}.log. On a busy service that writes 10 MB per rotation period you will retain up to 10 rotated files before the oldest are automatically pruned.
Override the logging level for any package directly in application.properties — no restart required when using Spring Boot DevTools during development:
# Reduce Spring web dispatcher noise
logging.level.org.springframework.web=WARN

# Set application code to INFO for a quieter local session
logging.level.com.zegel.springboot=INFO
Each logging.level.<package> entry is independent, so you can tune verbosity across libraries and your own code without affecting other loggers.

Build docs developers (and LLMs) love