Logging in Spring Boot 3: Best Practices for Configuration and Implementation
Logging in a Spring Boot 3 application is essential for monitoring system behavior, diagnosing issues, and analyzing performance. Well-structured logging helps maintain a clear view of the application flow, identify anomalies, and optimize debugging. However, improper use of logging can negatively impact application performance and security. In this article, we will explore best practices for configuring and writing logs effectively, ensuring scalability, readability, and compliance with data protection regulations.
Installing Lombok for Logging
Adding Lombok as a Dependency
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
If you use Gradle, add this dependency to your build.gradle:
dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}
Enabling Annotation Processing in Your IDE
For IntelliJ IDEA:- Go to File β Settings β Build, Execution, Deployment β Compiler β Annotation Processors.
- Select Enable annotation processing.
- Rebuild the project.
- Install the Lombok plugin if it's not already installed.
- Navigate to Window β Preferences β Java β Compiler β Annotation Processing.
- Enable Annotation processing.
- Rebuild the project.
Manually Installing Lombok
- Download Lombok from the Project Lombok website.
java -jar lombok.jar
- This will open a popup that allows you to configure Lombok for your IDE.
- Follow the instructions and restart your IDE.
Using SLF4J and Logback
Basic Configuration
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.example" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="FILE"/>
</root>
</configuration>
Declaring the Logger in Code
With Lombok
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class OrderService {
public void processOrder(Long orderId) {
log.info("Starting order processing: {}", orderId);
try {
// Simulating order processing
log.debug("Detailed processing for order {}", orderId);
} catch (Exception e) {
log.error("Error processing order {}", orderId, e);
}
log.info("Order {} processed successfully", orderId);
}
}
Without Lombok
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OrderService {
private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
}
When to Use INFO, DEBUG, WARN, and ERROR
Enabling Behavior
When a log level is enabled, all more severe levels are automatically enabled as well.Example:
- If you enable
INFO
, messages at theINFO
,WARN
,ERROR
(andFATAL
, if present) levels will be logged. - If you enable
DEBUG
, messages at theDEBUG
,INFO
,WARN
,ERROR
levels will be logged. - If you enable
ERROR
, only messages at theERROR
level will be logged.
Logging at the Start and End of a Method
Writing logs at the beginning and end of a method is useful for tracking execution flow, but excessive logging can slow down the system.Good Practice
public void processPayment(Long transactionId) {
log.info("[START] Processing payment for transaction: {}", transactionId);
try {
log.debug("Checking fund availability for transaction: {}", transactionId);
} catch (Exception e) {
log.error("Payment error for transaction: {}", transactionId, e);
}
log.info("[END] Payment completed for transaction: {}", transactionId);
}
Log Management in Production Environments
<configuration>
<property name="LOG_LEVEL" value="INFO"/>
<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="${LOG_LEVEL}">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
Rotating Log Files
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/app-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxHistory>30</maxHistory>
<maxFileSize>10MB</maxFileSize>
</rollingPolicy>
This setup ensures that log files are rotated based on both date and size.
Structured Logging (JSON Format)
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>
{
"timestamp": "%date{yyyy-MM-dd'T'HH:mm:ss.SSSZ}",
"level": "%level",
"logger": "%logger",
"message": "%message",
"thread": "%thread"
}
</pattern>
</encoder>
Filtering and Logging Sensitive Events
<logger name="com.example" level="INFO">
<appender-ref ref="FILE"/>
</logger>
<logger name="org.springframework.web" level="DEBUG">
<appender-ref ref="CONSOLE"/>
</logger>
Log Monitoring with Prometheus and Grafana
Sending Logs to Prometheus and Grafana
Configuring Prometheus
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
If you're using Gradle, add:
dependencies {
implementation 'io.micrometer:micrometer-registry-prometheus'
}
Expose Prometheus Endpoint in Spring Boot
Spring Boot automatically exposes a /actuator/prometheus endpoint for collecting metrics. Ensure these configurations are present in application.properties or application.yml:
management.endpoints.web.exposure.include=health,info,prometheus
management.endpoint.prometheus.enabled=true
The Prometheus endpoint will be available at:
π http://localhost:8080/actuator/prometheus
Configuring Prometheus
scrape_configs:
- job_name: 'spring-boot-app'
static_configs:
- targets: ['localhost:8080']
Configuring Grafana with Prometheus
Add Prometheus as a Data Source in Grafana
- Go to Configuration β Data Sources.
- Add a new Prometheus data source.
- Enter your Prometheus server URL (e.g., http://localhost:9090).
- Save the configuration.
- Navigate to Create β Dashboard and add a Panel.
- Select Prometheus as the data source.
- Use a PromQL query to visualize collected metrics (e.g., http_requests_total or jvm_memory_used_bytes).
Sending Logs to Grafana with Loki
To send logs to Grafana and view them in real-time, configure Grafana Loki, which is designed to collect and index logs in a Prometheus-like fashion.<dependency>
<groupId>com.github.loki</groupId>
<artifactId>logback-loki-appender</artifactId>
<version>1.3.0</version>
</dependency>
Configure Logback to Send Logs to Loki
<appender name="LOKI" class="com.github.loki.LogbackLokiAppender">
<url>http://localhost:3100/loki/api/v1/push</url>
<encoder>
<pattern>
{"timestamp":"%date{ISO8601}","level":"%level","message":"%message"}
</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="LOKI"/>
</root>
Configuring Grafana Loki
Install Grafana Loki following the official documentation.Configure Loki to collect logs (e.g., by setting up an input file pointing to logs generated by your application).
In Grafana, navigate to Configuration β Data Sources, add Loki, and enter its server URL (e.g., http://localhost:3100).
Create a new Dashboard and add a Panel to visualize logs.
Best Practices
Do Not Log Sensitive InformationAvoid logging sensitive data like:
Passwords, Access Tokens, Personal User Data, Bank Information
Bad Example:
log.info("User authenticated with password: {}", password);
Solution: Mask or exclude sensitive information in logs
Use Lazy Logging for Performance Optimization
Avoid:
log.debug("Request received for user: " + user.getName());
Use:
log.debug("Request received for user: {}", user.getName());
This ensures logging is evaluated only if the DEBUG level is enabled.
Conclusion
References
I am passionate about IT technologies. If youβre interested in learning more or staying updated with my latest articles, feel free to connect with me on:
Feel free to reach out through any of these platforms if you have any questions!
Comments
Post a Comment