Logging in Spring Boot with SL4J
Logging is a fundamental part of software development that provides insights into an application's behavior, making debugging and monitoring easier. A well-structured logging system helps developers understand how an application is running, identify issues, and optimize performance. Why Are Logs Important? Logs serve several critical purposes, including: Debugging & Troubleshooting: Logs help track down issues, especially in production environments where debugging directly is not feasible. Monitoring & Observability: Logs allow teams to monitor application health and detect anomalies. Security Auditing: Logs keep records of critical events, such as user authentication attempts and database queries, which are essential for security audits. Performance Optimization: By analyzing logs, developers can detect performance bottlenecks and improve application efficiency. Logging Levels Explained Logs are categorized into different levels, allowing developers to filter and manage the verbosity of log messages. The main logging levels are: TRACE: The most detailed level, used for fine-grained debugging and tracking execution flow. DEBUG: Less verbose than TRACE, but still provides detailed information useful for debugging. INFO: General application flow messages, such as startup, shutdown, or important milestones. WARN: Warnings about potential issues that do not disrupt application functionality. ERROR: Critical problems that might affect the application’s behavior. Example: Using Logs in AWS Service Configuration Consider the following Spring Boot configuration for AWS services, where logging can enhance visibility and traceability. package dev.MSpilari.webSocketBackend.configs; import java.net.URI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; import software.amazon.awssdk.services.dynamodb.model.BillingMode; import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest; import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement; import software.amazon.awssdk.services.dynamodb.model.KeyType; import software.amazon.awssdk.services.dynamodb.model.ListTablesResponse; import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.S3Configuration; import software.amazon.awssdk.services.s3.model.CreateBucketRequest; import software.amazon.awssdk.services.s3.model.ListBucketsResponse; @Configuration public class AwsServicesConfigs { private static final Logger logger = LoggerFactory.getLogger(AwsServicesConfigs.class); @Value("${spring.cloud.aws.credentials.access-key}") private String accessKey; @Value("${spring.cloud.aws.credentials.secret-key}") private String secretKey; @Value("${spring.cloud.aws.region.static}") private String region; @Value("${spring.cloud.aws.s3.endpoint}") private String cloudEndpoint; @Value("${spring.cloud.aws.s3.bucket-name}") private String bucketName; @Value("${spring.cloud.aws.dynamodb.table-name}") private String tableName; @Bean public S3Client s3Client() { return S3Client.builder() .endpointOverride(URI.create(cloudEndpoint)) .region(Region.of(region)) .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretKey))) .serviceConfiguration(S3Configuration.builder() .pathStyleAccessEnabled(true) // Força compatibilidade de caminho .build()) .build(); } @Bean public DynamoDbClient dynamoDbClient() { return DynamoDbClient.builder() .endpointOverride(URI.create(cloudEndpoint)) .region(Region.of(region)) .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretKey))) .build(); } @Bean public DynamoDbEnhancedClient dynamoDbEnhancedClient(DynamoDbClient dynamoDbClient) { return DynamoDbEnhancedClient.builder() .dynamoDbClient(dynamoDbClient) .build(); } @Bean public CommandLineRunner initializeAwsServices(S3Client s3Client, DynamoDbClient dynamoDbClient) {

Logging is a fundamental part of software development that provides insights into an application's behavior, making debugging and monitoring easier. A well-structured logging system helps developers understand how an application is running, identify issues, and optimize performance.
Why Are Logs Important?
Logs serve several critical purposes, including:
- Debugging & Troubleshooting: Logs help track down issues, especially in production environments where debugging directly is not feasible.
- Monitoring & Observability: Logs allow teams to monitor application health and detect anomalies.
- Security Auditing: Logs keep records of critical events, such as user authentication attempts and database queries, which are essential for security audits.
- Performance Optimization: By analyzing logs, developers can detect performance bottlenecks and improve application efficiency.
Logging Levels Explained
Logs are categorized into different levels, allowing developers to filter and manage the verbosity of log messages. The main logging levels are:
- TRACE: The most detailed level, used for fine-grained debugging and tracking execution flow.
- DEBUG: Less verbose than TRACE, but still provides detailed information useful for debugging.
- INFO: General application flow messages, such as startup, shutdown, or important milestones.
- WARN: Warnings about potential issues that do not disrupt application functionality.
- ERROR: Critical problems that might affect the application’s behavior.
Example: Using Logs in AWS Service Configuration
Consider the following Spring Boot configuration for AWS services, where logging can enhance visibility and traceability.
package dev.MSpilari.webSocketBackend.configs;
import java.net.URI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
import software.amazon.awssdk.services.dynamodb.model.BillingMode;
import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
import software.amazon.awssdk.services.dynamodb.model.KeyType;
import software.amazon.awssdk.services.dynamodb.model.ListTablesResponse;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
import software.amazon.awssdk.services.s3.model.ListBucketsResponse;
@Configuration
public class AwsServicesConfigs {
private static final Logger logger = LoggerFactory.getLogger(AwsServicesConfigs.class);
@Value("${spring.cloud.aws.credentials.access-key}")
private String accessKey;
@Value("${spring.cloud.aws.credentials.secret-key}")
private String secretKey;
@Value("${spring.cloud.aws.region.static}")
private String region;
@Value("${spring.cloud.aws.s3.endpoint}")
private String cloudEndpoint;
@Value("${spring.cloud.aws.s3.bucket-name}")
private String bucketName;
@Value("${spring.cloud.aws.dynamodb.table-name}")
private String tableName;
@Bean
public S3Client s3Client() {
return S3Client.builder()
.endpointOverride(URI.create(cloudEndpoint))
.region(Region.of(region))
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretKey)))
.serviceConfiguration(S3Configuration.builder()
.pathStyleAccessEnabled(true) // Força compatibilidade de caminho
.build())
.build();
}
@Bean
public DynamoDbClient dynamoDbClient() {
return DynamoDbClient.builder()
.endpointOverride(URI.create(cloudEndpoint))
.region(Region.of(region))
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretKey)))
.build();
}
@Bean
public DynamoDbEnhancedClient dynamoDbEnhancedClient(DynamoDbClient dynamoDbClient) {
return DynamoDbEnhancedClient.builder()
.dynamoDbClient(dynamoDbClient)
.build();
}
@Bean
public CommandLineRunner initializeAwsServices(S3Client s3Client, DynamoDbClient dynamoDbClient) {
return args -> {
createTableIfNotExists(dynamoDbClient);
createBucketIfNotExists(s3Client);
};
}
private void createBucketIfNotExists(S3Client s3Client) {
logger.info("Trying to create a bucket on S3...");
if (!doesBucketExist(s3Client)) {
logger.info("Creating bucket with name: " + bucketName);
s3Client.createBucket(CreateBucketRequest.builder().bucket(bucketName).build());
} else {
logger.info("Bucket " + bucketName + " already exists !");
}
}
private boolean doesBucketExist(S3Client s3Client) {
ListBucketsResponse listOfBuckets = s3Client.listBuckets();
return listOfBuckets.buckets().stream().anyMatch(bucket -> bucket.name().equals(bucketName));
}
private void createTableIfNotExists(DynamoDbClient dynamoDbClient) {
logger.info("Trying to create a table in DynamoDb...");
if (!doesTableExist(dynamoDbClient)) {
CreateTableRequest createTableRequest = CreateTableRequest.builder()
.tableName(tableName)
.keySchema(KeySchemaElement.builder()
.attributeName("album_id")
.keyType(KeyType.HASH)
.build())
.attributeDefinitions(AttributeDefinition.builder()
.attributeName("album_id")
.attributeType(ScalarAttributeType.S)
.build())
.billingMode(BillingMode.PAY_PER_REQUEST)
.build();
logger.info("Creating a table with name: " + tableName + " in DynamoDb.");
dynamoDbClient.createTable(createTableRequest);
} else {
logger.info("Table with name: " + tableName + " already exists.");
}
}
private boolean doesTableExist(DynamoDbClient dynamoDbClient) {
ListTablesResponse listOfTables = dynamoDbClient.listTables();
return listOfTables.tableNames().stream().anyMatch(name -> name.equalsIgnoreCase(tableName));
}
}
⚠️ Important Note on Resource Initialization
The creation of S3 buckets and DynamoDB tables within the application code, as shown in the AWS configuration example above, is done purely for educational purposes. The goal is to help developers understand how to interact with AWS services programmatically using the AWS SDK in a Spring Boot environment.
However, in a production-grade application, this approach is not recommended. A more robust and scalable solution would involve provisioning these resources using Infrastructure as Code (IaC) tools such as Terraform, AWS CloudFormation, or Pulumi. These tools allow for repeatable, version-controlled, and environment-specific infrastructure setups.
In such setups, your Spring Boot application should assume that the required resources (e.g., buckets and tables) already exist, and focus solely on connecting to and interacting with them via configuration.
This separation of concerns, where infrastructure is handled by IaC and application logic by Spring, improves maintainability, security, and clarity across teams and deployment pipelines.
Best Practices for Logging
-
Use appropriate logging levels: Avoid using
DEBUG
orTRACE
logs in production environments. - Structure log messages: Make logs clear and meaningful, including relevant context.
- Avoid logging sensitive data: Never log passwords, API keys, or personal user information.
- Enable centralized logging: Use tools like AWS CloudWatch, ELK Stack, or Prometheus for better observability.
- Monitor logs regularly: Logging is only useful if logs are actively monitored and analyzed.
Conclusion
Logging is an essential part of any application, helping developers debug, monitor, and optimize performance. Using SLF4J with structured logging practices ensures that logs remain useful and manageable. By implementing logs in AWS service configurations, like the example above, developers can improve observability and system reliability.