Skip to content

Distributed Logging (Part 2) | Appenders in Depth | Console, File, Rolling File Appenders

The Bank Ledger Analogy

Imagine an accountant managing transactions for a high volume bank. Every time money moves, the accountant writes a record. Where should those records be placed? For immediate desk checks, the accountant writes the balance on a desktop whiteboard. But at the end of the day, that whiteboard gets erased. To preserve legal history, the accountant copies every entry into a bound paper ledger. As the ledger fills up, the bank archives each completed book into a vault, labeling it by date and year, while opening a fresh book on the desk.

In Java logging, Appenders are those recording destinations. The logger decides what information to capture, but the appender decides where that information gets written: to standard output (the whiteboard), to an active file (the desk ledger), or into time stamped archived archives (the vault). Without properly configured appenders, logging can either vanish when a container stops or fill up production disk drives until the entire server crashes.

This lecture covers Logback architecture, ConsoleAppender, FileAppender, RollingFileAppender, time based versus size based rollover policies, and production XML configuration.


What Is an Appender?

In Logback (the default logging framework in Spring Boot), the architecture separates logging responsibility into two main roles:

  1. Logger: Associated with class names, responsible for capturing events and evaluating log levels (DEBUG, INFO, WARN, ERROR).
  2. Appender: Responsible for delivering the formatted log event to a specific output destination (console, file, socket, database, cloud stream).

A single logger can attach multiple appenders simultaneously. For example, your root logger can write error events to both the console (for developer viewing during local testing) and a rolling log file (for persistent auditing on production servers).

[ Application Code: logger.info("Order created") ]
                       |
               [ Logback Logger ]
                       |
       +---------------+---------------+
       |                               |
[ ConsoleAppender ]           [ RollingFileAppender ]
       |                               |
  Standard Out (stdout)       /var/log/app/app.log

The Three Core Logback Appenders

All standard Logback appenders reside in the ch.qos.logback.core package.

1. ConsoleAppender

Writes log events to System.out or System.err. This is what you see when running Spring Boot locally in your terminal or inside container environments like Docker and Kubernetes, where container engines capture stdout streams.

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

2. FileAppender

Appends log events to a single specified file on the local filesystem:

xml
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>/var/log/myapp/application.log</file>
    <append>true</append>
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level - %msg%n</pattern>
    </encoder>
</appender>

The primary flaw of FileAppender: the file grows infinitely. In a production system processing millions of requests, a single static log file will eventually exhaust all available disk space, causing catastrophic operating system failures.

3. RollingFileAppender

The industry standard for file logging. RollingFileAppender writes to an active file, and when a specified condition is met — such as the passage of time (midnight) or reaching a file size limit (e.g. 50 MB) — it automatically rolls over the active file. It compresses the old log into an archive file (.gz or .zip), creates a fresh active file, and deletes files exceeding a configured age limit.


Rolling Policies in Detail

A RollingFileAppender requires two sub components:

  1. RollingPolicy: Dictates how the rollover is executed (moving, renaming, and compressing files).
  2. TriggeringPolicy: Dictates when the rollover should occur (time elapsed or size reached).

Time Based Rolling: TimeBasedRollingPolicy

The most common policy. It triggers rollover automatically based on the date format pattern in fileNamePattern:

xml
<appender name="ROLLING_TIME" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>/var/log/myapp/app.log</file>

    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <!-- Rollover daily at midnight and gzip the archived file -->
        <fileNamePattern>/var/log/myapp/app-%d{yyyy-MM-dd}.log.gz</fileNamePattern>

        <!-- Keep at most 30 days of history -->
        <maxHistory>30</maxHistory>

        <!-- Delete oldest files if total directory size exceeds 10 GB -->
        <totalSizeCap>10GB</totalSizeCap>
    </rollingPolicy>

    <encoder>
        <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
</appender>

Size and Time Based Rolling: SizeAndTimeBasedRollingPolicy

If high traffic produces five gigabytes of logs in a single morning, daily rollover is insufficient because individual files become too unwieldy to open or search. SizeAndTimeBasedRollingPolicy splits files by both calendar date and individual file size ceiling:

xml
<appender name="ROLLING_SIZE_AND_TIME" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>/var/log/myapp/app.log</file>

    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
        <!-- Must include both date %d and integer index %i -->
        <fileNamePattern>/var/log/myapp/app-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>

        <!-- Rollover whenever the file reaches 50 Megabytes -->
        <maxFileSize>50MB</maxFileSize>

        <!-- Keep at most 14 days of logs -->
        <maxHistory>14</maxHistory>

        <!-- Total archive cap -->
        <totalSizeCap>5GB</totalSizeCap>
    </rollingPolicy>

    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
</appender>

Notice the %i placeholder: on the same day, if app.log reaches 50 MB, it archives to app-2026-09-05.0.log.gz. The next chunk becomes app-2026-09-05.1.log.gz.


Production logback-spring.xml Configuration

In Spring Boot, place your custom logging configuration in src/main/resources/logback-spring.xml:

xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">

    <!-- Define reusable variables -->
    <property name="LOG_PATH" value="/var/log/order-service" />
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />

    <!-- 1. Console Appender for development -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- 2. Production Rolling File Appender -->
    <appender name="FILE_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/application.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/archived/app-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>10GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- Set package-specific logging levels -->
    <logger name="com.example.orderservice" level="DEBUG" />
    <logger name="org.springframework.web" level="INFO" />

    <!-- Root Logger configuration -->
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE_APPENDER" />
    </root>

</configuration>

The attribute scan="true" enables dynamic runtime reloading: if you change a logger level in logback-spring.xml while the server is running, Logback detects the file change every thirty seconds and reloads configuration without restarting the application.


Interview Questions & Pitfalls

Q1: What is the primary difference between FileAppender and RollingFileAppender?

FileAppender appends all log records into a single static file indefinitely, which will eventually consume all available disk space. RollingFileAppender writes to an active file and automatically rolls over into compressed archives when a threshold is met (such as midnight or a specific file size), maintaining retention policies to prevent disk exhaustion.

Q2: What happens if SizeAndTimeBasedRollingPolicy is configured without the %i token in fileNamePattern?

The application will fail to start or log an error during startup. SizeAndTimeBasedRollingPolicy requires %i because multiple rollovers can occur on the same calendar day. The %i index token differentiates multiple rollover files generated on that date (e.g. app-2026-09-05.0.log.gz and app-2026-09-05.1.log.gz).

Q3: What is the purpose of .gz in the fileNamePattern?

Adding .gz or .zip to the end of the fileNamePattern tells Logback to automatically compress the rolled over log file asynchronously using gzip or zip compression. This reduces file size on disk by up to 90%, significantly lowering storage requirements.

Q4: What is the difference between maxHistory and totalSizeCap in Logback?

maxHistory limits how many time units (days, months) of archived files are kept before being purged. totalSizeCap sets an absolute maximum storage boundary for all archived files combined. If the directory exceeds totalSizeCap before maxHistory expires, the oldest files are deleted immediately to protect disk stability.

Q5: Why should you name your file logback-spring.xml instead of logback.xml in Spring Boot?

Standard logback.xml is loaded directly by Logback before Spring Boot has initialized its environment. Naming it logback-spring.xml allows Spring Boot to take control of configuration loading, enabling features like <springProfile> tags, environment variable property interpolation, and profile specific logging configurations.