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