When developing a Spring Boot application, you will eventually need to configure log output. During development you want to see detailed debug information, but in production you only want the necessary logs saved to a file. This article targets Spring Boot 3.x and walks through logging configuration step by step, from the basics to practical production setups.
Spring Boot’s Default Logging Behavior
How to Write Logs from Code (LoggerFactory / @Slf4j)
Before getting into configuration, let’s cover the basics of writing logs from your application code. The standard approach is to obtain a logger from SLF4J’s LoggerFactory.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
public void createOrder(String orderId) {
log.info("注文を受け付けました: orderId={}", orderId);
log.debug("注文処理の詳細情報: {}", orderId);
}
}
The {} in the message is a placeholder that gets filled in with the argument’s value. Unlike string concatenation ("orderId=" + orderId), no string-building cost is incurred at all when that log level is disabled, so always use the placeholder form.
Also, avoid System.out.println in production code. It cannot be controlled by log level, carries no timestamp or thread name, and is not subject to file output or rotation. If you go through a logger, every setting explained in this article applies as-is.
If your project uses Lombok, the @Slf4j annotation lets you omit the logger declaration.
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class OrderService {
public void createOrder(String orderId) {
log.info("注文を受け付けました: orderId={}", orderId);
}
}
Simply adding @Slf4j auto-generates a logger field named log. This style is the mainstream choice in real-world Spring Boot projects.
There are five level-specific methods, in ascending order of severity: trace / debug / info / warn / error. Here is a rough guide to when to use each.
- ERROR: Failures requiring immediate attention (caught exceptions, failed connections to external services)
- WARN: Conditions that won’t break things right away but deserve attention (retries occurring, use of deprecated APIs)
- INFO: Important business events (order accepted, batch job start/end)
- DEBUG: Detailed information for investigation during development (parameter contents, which branches were taken)
- TRACE: Even finer-grained execution traces (rarely used)
When logging an exception, pass the exception object as the last argument and the stack trace will be printed.
try {
externalApi.call();
} catch (ApiException e) {
log.error("外部API呼び出しに失敗しました: orderId={}", orderId, e);
}
Spring Boot outputs logs from startup without any special configuration. This is because Spring Boot adopts SLF4J as the logging facade and Logback as the implementation by default.
If you use a starter such as spring-boot-starter-web, spring-boot-starter-logging is automatically included as a dependency. By default, logs at INFO level and above are written to the console.
You can also switch the implementation to Log4j2. Exclude spring-boot-starter-logging from your dependencies and add spring-boot-starter-log4j2, and as long as you are using the SLF4J API, no changes to application code are needed. That said, there are very few situations where Logback falls short, so unless you have a specific reason such as an existing project already standardized on Log4j2, staying with the default is perfectly fine.
Changing Log Levels in application.properties
Let’s start with the simplest method. With application.properties, you can change log levels without creating an XML file.
# アプリケーション全体のログレベル
logging.level.root=INFO
# 特定パッケージのログレベル
logging.level.com.example.myapp=DEBUG
logging.level.org.springframework.web=WARN
logging.level.root sets the default level for the whole application, and logging.level.<package name> lets you fine-tune specific packages. During development, it’s convenient to set only your own packages to DEBUG while suppressing logs from the Spring framework itself.
Basic Structure of logback-spring.xml
For more advanced configuration, create a Logback configuration file. Place it at src/main/resources/logback-spring.xml.
<configuration>
<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>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
A Logback configuration file consists of three main elements.
- appender defines where and in what format logs are written
- logger configures specific packages
- root holds the global default settings
Naming the file logback-spring.xml rather than logback.xml enables Spring Boot-specific features. For example, per-environment configuration via the springProfile tag, and referencing properties defined in application.properties via the springProperty tag, do not work in logback.xml. This is because Logback reads logback.xml directly, before Spring Boot’s initialization. In Spring Boot projects, it’s safest to standardize on logback-spring.xml.
Customizing the Console Appender
The console output format can be freely changed via pattern. Here are some commonly used patterns.
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{40}) - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
%d{...}is the timestamp format%threadis the thread name%-5levelis the log level (the-means left-aligned, padded to 5 characters)%logger{40}is the logger name (up to 40 characters)%msgis the log message%nis a newline%highlightand%cyanadd color (easier to read in development)
Colored output is handy in development, but ANSI escape sequences are unwanted in production or in log files, so it’s a good idea to separate the configuration by environment.
File Output and Log Rotation
In production, you’ll want logs saved to files. Since log files growing without bound is a problem, use RollingFileAppender with rotation configured from the very start in real projects.
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/application-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
maxFileSizeis the maximum size of a single file (rolls over to a new file once it exceeds 10MB)maxHistoryis the number of days to retain (keeps 30 days)totalSizeCapis the upper limit on the combined size of all log files (up to 1GB)
Adding .gz to the file name causes old log files to be gzip-compressed automatically.
One important caveat: Logback does not create the logs directory automatically. Specifying a directory that doesn’t exist causes an error at startup, so either create it beforehand with mkdir logs, or specify an absolute path to a directory that is guaranteed to exist.
Switching Log Configuration per Environment
Structured Logging (JSON Output) in Spring Boot 3.4 and Later
Starting with Spring Boot 3.4, structured logging (JSON format) is supported natively without writing a Logback configuration file. Just add a single line to application.properties.
# コンソール出力をElastic Common Schema形式のJSONにする
logging.structured.format.console=ecs
# ファイル出力を構造化する場合
logging.structured.format.file=ecs
Three formats are supported: ecs (Elastic Common Schema), logstash, and gelf (Graylog, 3.5 and later). The output looks like the following JSON.
{"@timestamp":"2026-08-29T10:15:30.123Z","log.level":"INFO","message":"注文を受け付けました: orderId=A123","process.thread.name":"http-nio-8080-exec-1","log.logger":"com.example.myapp.OrderService"}
Log platforms such as Elasticsearch, CloudWatch Logs, and Loki can ingest JSON with its structure preserved, which makes searching and aggregating by fields like orderId far easier. When running Spring Boot in containers such as Kubernetes, “emit JSON to standard output and let the collection platform handle it” is the first choice over file output plus rotation. For the overall picture of container operations, see the article on deploying Spring Boot apps to Kubernetes.
Note that this feature is only available in Spring Boot 3.4 and later. If you need JSON logs on an earlier version, use the traditional approach of wiring the logstash-logback-encoder library into logback-spring.xml. Details are collected in the Structured Logging section of the official Spring Boot documentation.
Using Spring Boot’s Profile feature, you can apply different logging configurations for development and production. See also the Profiles article for details on per-environment configuration.
<configuration>
<springProfile name="dev">
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %highlight(%-5level) %cyan(%logger{40}) - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
</root>
</springProfile>
<springProfile name="prod">
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/application-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="FILE" />
</root>
</springProfile>
</configuration>
Specifying spring.profiles.active=prod in application.properties applies the production configuration. This makes it easy to show detailed DEBUG logs on the console in development while saving INFO and above to files in production.
Note that in container environments (Docker, Kubernetes, and so on), it’s also common to write logs to standard output rather than files and stream them to an external log collection system. Choose according to your environment.
A Practical, Complete Configuration Example
Automatically Attaching a Request ID to Logs with MDC
In a production environment where many requests are processed concurrently, being able to trace “which request does this log line belong to” is critical. This is where MDC (Mapped Diagnostic Context) comes in. If you store a value in thread-local storage, it is automatically attached to every log line emitted from that thread.
Here is an example of a servlet filter that issues an ID for each request.
@Component
public class RequestIdFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String requestId = UUID.randomUUID().toString().substring(0, 8);
MDC.put("requestId", requestId);
try {
filterChain.doFilter(request, response);
} finally {
MDC.remove("requestId");
}
}
}
Adding %X{key name} to the log pattern outputs the MDC value.
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] [%X{requestId}] %-5level %logger{36} - %msg%n</pattern>
Don’t forget the MDC.remove() in the finally block. Application servers reuse threads, so if you don’t remove the value, the previous request’s value leaks into a different request.
An implementation that combines MDC with exception handling to enable support-ticket tracing via a trace ID is explained in detail in the article on implementing a production-ready GlobalExceptionHandler.
Finally, here is a complete configuration example you can use in real projects. It supports both development and production environments and includes per-package log level tuning.
<configuration>
<property name="LOG_PATH" value="logs" />
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" />
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/application.log</file>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/application-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
</appender>
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
</root>
<logger name="com.example.myapp" level="DEBUG" />
</springProfile>
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="FILE" />
</root>
<logger name="org.springframework" level="WARN" />
<logger name="com.example.myapp" level="INFO" />
</springProfile>
</configuration>
Defining variables with the <property> tag reduces duplication in the configuration and improves maintainability.
If you want additional information such as request IDs or user IDs automatically included in logs, the MDC (Mapped Diagnostic Context) mechanism is convenient. Details are covered in a separate article, but it’s worth remembering as a feature that stores values in thread-local storage and attaches them automatically at log output time.
Troubleshooting Logging Configuration
Here is a summary of common logging configuration problems and how to address them.
- No logs are output → Check
logback-spring.xmlfor syntax errors. Verify thatLogback configuration errordoesn’t appear on the console at startup, and that the file is placed atsrc/main/resources/logback-spring.xml - Cannot write to file → Verify that the log directory exists and that you have write permission
- Log level isn’t applied → Check the configuration precedence. In general, the order of priority is
logback-spring.xml>application.properties> defaults. A misspelled logger name (package name) is another frequent cause
Operating Logs in Production
Let’s cover a few key points for operating logs in real-world settings.
The basic rule is INFO and above in production, and DEBUG in development. DEBUG and TRACE generate too much log volume and negatively affect disk space and performance.
Treat rotation configuration as mandatory. Log files that grow without bound will fill the disk and can cause the application to stop.
Personal information and confidential data (passwords, tokens, credit card numbers, and so on) must never be written to logs. Be careful not to accidentally output them while debugging.
Summary
This article walked through Spring Boot logging configuration step by step, from the basics to practical production setups.
Starting with simple log level changes in application.properties and progressing to full-fledged configuration with logback-spring.xml, it covers the knowledge you need in real projects. Including per-environment configuration switching and log rotation, you should now be able to implement a configuration that’s ready to run in production.
Combined with logging configuration, Spring Boot Actuator lets you monitor your application as well. Give it a try with real-world operations in mind.
Related Articles
Here are articles on production operations that are worth reading alongside logging configuration.
- Implementing a Production-Ready GlobalExceptionHandler in Spring Boot - Exception logging design and trace ID attachment with MDC
- How to Deploy a Spring Boot App to Kubernetes - Operational setup including log collection in container environments
- How to Achieve Graceful Shutdown and Zero-Downtime Deployment in Spring Boot - Pitfalls in logging and request handling during deployment