Spring Boot Logging to File with Pattern Layout, Package-specific Levels, and Rolling Policies



This content originally appeared on DEV Community and was authored by DevCorner2

Logging is one of the most important parts of any production-grade Spring Boot application. Done right, it:

  • Helps debug issues quickly
  • Prevents log flooding
  • Organizes logs for different modules or packages
  • Manages file sizes automatically

In this blog, we’ll configure logging so that:

  • Logs are written to files with a custom pattern layout
  • Different packages have different logging levels
  • Logs are rolled over daily and by file size
  • Log files are named using a date-based format
  • Old logs are automatically cleaned up

1. Default Spring Boot Logging Behavior

Spring Boot uses SLF4J as the logging facade and Logback as the default logging implementation.
By default:

  • Logs go to the console
  • Log level is INFO
  • No rolling file is configured

We need to customize this to:

  • Store logs in a file
  • Control rolling policies
  • Fine-tune package log levels

2. Setting Up Basic Logging in application.properties

You can control log patterns, log levels, and the base log file name from application.properties.

src/main/resources/application.properties:

############################################
# LOGGING CONFIGURATION
############################################

# ---- Log File Location ----
logging.file.name=logs/app.log  # Writes to logs/app.log

# ---- Log Pattern for File & Console ----
# Date Time | Thread | Level | Logger | Message
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
logging.pattern.console=%d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n

# ---- Package-specific log levels ----
logging.level.root=INFO
logging.level.com.example.productapi.service=DEBUG
logging.level.com.example.productapi.repository=WARN
logging.level.org.springframework.web=ERROR

✅ This will:

  • Send logs to both console and logs/app.log
  • Use a readable timestamped pattern
  • Assign different log levels for different packages

⚠ Limitation:
application.properties cannot configure rolling policies (e.g., max file size, date-based naming). For that, we need Logback XML.

3. Advanced Rolling File Configuration with logback-spring.xml

For enterprise-grade log management, we want:

  • Time-based rolling (daily logs)
  • Size-based rolling (split large logs into chunks)
  • Retention policy (keep logs for N days)
  • Date-based file naming

Create src/main/resources/logback-spring.xml:

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

    <!-- === PROPERTIES === -->
    <property name="LOG_HOME" value="logs" />
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>

    <!-- === ROLLING FILE APPENDER === -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/app.log</file>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>

        <!-- Time + Size Based Rolling -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- File name with date and index -->
            <fileNamePattern>${LOG_HOME}/app-%d{yyyy-MM-dd}.%i.log</fileNamePattern>

            <!-- Roll when size exceeds 10MB -->
            <timeBasedFileNamingAndTriggeringPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>

            <!-- Keep logs for 30 days -->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>

    <!-- === CONSOLE APPENDER === -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- === ROOT LOGGER === -->
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
    </root>

    <!-- === PACKAGE-SPECIFIC LOGGERS === -->
    <logger name="com.example.productapi.service" level="DEBUG" />
    <logger name="com.example.productapi.repository" level="WARN" />
    <logger name="org.springframework.web" level="ERROR" />

</configuration>

4. How This Works

  • RollingFileAppender writes logs to a file and handles rolling.
  • TimeBasedRollingPolicy ensures that a new file is created daily.
  • SizeAndTimeBasedFNATP creates new files if a log file exceeds 10MB within the same day.
  • Log file names will look like:
  logs/app-2025-08-12.0.log
  logs/app-2025-08-12.1.log
  logs/app-2025-08-13.0.log
  • maxHistory ensures logs older than 30 days are deleted automatically.

5. Testing the Logging

You can create a simple Spring Boot controller to generate logs for testing:

@RestController
@RequestMapping("/log")
public class LogTestController {

    private static final Logger log = LoggerFactory.getLogger(LogTestController.class);

    @GetMapping
    public String testLogs() {
        log.debug("Debug log message");
        log.info("Info log message");
        log.warn("Warn log message");
        log.error("Error log message");
        return "Logs generated!";
    }
}

Run:

mvn spring-boot:run

Then hit:

http://localhost:8080/log

You’ll see logs in:

  • Console
  • logs/app.log And rolled files will appear once size/date conditions are met.

6. Best Practices for Logging in Production

  • Don’t log sensitive data (passwords, tokens)
  • Use DEBUG level sparingly in production
  • Rotate logs to prevent disk overuse
  • Use centralized log aggregation tools like ELK or Grafana Loki
  • Always keep log format consistent for easier parsing

✅ Final Output:

  • Logs to both console and file
  • Custom pattern layout
  • Package-specific log levels
  • Rolling policy based on date and size
  • Automatic cleanup after 30 days

Alright — let’s extend the logging setup so that each package logs into its own file, with its own rolling policy, file naming format, and retention.

This is a very enterprise-friendly approach because:

  • Service-related logs go into one file
  • Repository (DB) logs go into another
  • Framework (Spring) logs are isolated
  • Easier to debug and monitor each layer independently

1. Updated logback-spring.xml for Multiple Package-specific Files

Place this in src/main/resources/logback-spring.xml:

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

    <!-- === GLOBAL PROPERTIES === -->
    <property name="LOG_HOME" value="logs" />
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
    <property name="MAX_FILE_SIZE" value="10MB" />
    <property name="MAX_HISTORY_DAYS" value="30" />

    <!-- === COMMON ROLLING POLICY TEMPLATE === -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- === Root Application Log (catch-all) === -->
    <appender name="ROOT_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/app.log</file>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/app-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>${MAX_HISTORY_DAYS}</maxHistory>
        </rollingPolicy>
    </appender>

    <!-- === Service Layer Logs === -->
    <appender name="SERVICE_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/service.log</file>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/service-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>${MAX_HISTORY_DAYS}</maxHistory>
        </rollingPolicy>
    </appender>

    <!-- === Repository Layer Logs === -->
    <appender name="REPOSITORY_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/repository.log</file>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/repository-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>${MAX_HISTORY_DAYS}</maxHistory>
        </rollingPolicy>
    </appender>

    <!-- === Spring Framework Logs === -->
    <appender name="SPRING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/spring.log</file>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/spring-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>${MAX_HISTORY_DAYS}</maxHistory>
        </rollingPolicy>
    </appender>

    <!-- === LOGGERS WITH APPENDER MAPPING === -->
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="ROOT_FILE" />
    </root>

    <logger name="com.example.productapi.service" level="DEBUG" additivity="false">
        <appender-ref ref="SERVICE_FILE" />
        <appender-ref ref="CONSOLE" />
    </logger>

    <logger name="com.example.productapi.repository" level="WARN" additivity="false">
        <appender-ref ref="REPOSITORY_FILE" />
        <appender-ref ref="CONSOLE" />
    </logger>

    <logger name="org.springframework" level="ERROR" additivity="false">
        <appender-ref ref="SPRING_FILE" />
        <appender-ref ref="CONSOLE" />
    </logger>

</configuration>

2. How This Works

  • Multiple file appenders are defined — one for each log category (service, repository, spring, root).
  • additivity="false" ensures logs from a package don’t go to multiple files unintentionally.
  • Each appender:

    • Rolls logs daily
    • Splits files when size exceeds 10MB
    • Deletes logs older than 30 days
  • File names look like:

  logs/service-2025-08-12.0.log
  logs/service-2025-08-12.1.log
  logs/repository-2025-08-13.0.log
  logs/spring-2025-08-13.0.log

3. Benefits of This Approach

  • Separation of Concerns — each package logs into its own file
  • Easier Debugging — when investigating an issue, you can check only relevant logs
  • Controlled Size & Retention — prevents huge unmanageable log files
  • Production Friendly — works seamlessly with log aggregation tools like ELK, Loki, Splunk

4. Testing the Setup

You can trigger logs in each package and verify that:

  • Service layer logs go into service-YYYY-MM-DD.log
  • Repository layer logs go into repository-YYYY-MM-DD.log
  • Spring framework logs go into spring-YYYY-MM-DD.log
  • Miscellaneous logs go into app-YYYY-MM-DD.log


This content originally appeared on DEV Community and was authored by DevCorner2