Today we'll dig a bit deeper into logging. Before jumping into code, ask yourself: how would you know that the application "broke"? Sure, users might complain that "the site is down", but that's not enough to diagnose the problem. This is where logging comes in. Spring provides a powerful and flexible logging mechanism that easily integrates with popular libraries like SLF4J, Logback, and Log4j. Now imagine your life without logs. Your only debugging tool is sprinkling System.out.println() throughout the code? That's not only inefficient, it's like trying to find north with a cactus.
Configuring logging in Spring Boot
By default Spring Boot ships with support for SLF4J (Simple Logging Facade for Java) and Logback. That's the standard combo that covers most use cases.
Adding the dependency (if you don't have it)
If you're not using Spring Boot Starter Logging yet, add this to pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
Spring Boot will automatically pull in Logback, so you won't need extra library tinkering.
Logging in error handlers
Logging errors in handlers is a key part of exception handling. Let's start with a simple example using @ExceptionHandler with some logging added.
Example
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
LOGGER.error("An error occurred: {}", ex.getMessage(), ex);
return new ResponseEntity<>("Invalid request: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
}
}
Here we use a Logger to write an error message along with the stack trace (ex at the end). That gives you the full picture of the failure.
Understanding logging levels
Before you craft an awesome logger, it's important to know what logging levels are:
- TRACE: very detailed information about the app's internals (rarely used).
- DEBUG: information for debugging the application. For example, what values were passed into a method.
- INFO: general information about normal application operation.
- WARN: warnings that aren't critical but should be checked (e.g., a session is about to expire).
- ERROR: errors that require intervention.
Example of using different levels:
LOGGER.trace("Detailed debug information.");
LOGGER.debug("Runtime data for analysis: value X = {}", x);
LOGGER.info("User successfully logged in.");
LOGGER.warn("User's password will expire soon.");
LOGGER.error("Error while processing request.", exception);
In practice, logs below INFO are usually turned off in most production systems.
Configuring Logback via configuration file
Want more control over logs? Spring Boot lets you configure Logback in logback-spring.xml.
Configuration example
<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>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/app.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.example" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</logger>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
This file creates two appenders: one writes logs to the console, the other to the app.log file. Note the pattern: it defines the output format (including timestamp, level, logger name, and message).
Logging with SLF4J in controllers and services
Example
Let's add logs to a controller:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
private static final Logger LOGGER = LoggerFactory.getLogger(DemoController.class);
@GetMapping("/example")
public ResponseEntity<String> exampleMethod(@RequestParam String input) {
LOGGER.info("Got request with param: {}", input);
if (input.isEmpty()) {
LOGGER.warn("Empty request parameter!");
return new ResponseEntity<>("Parameter must not be empty", HttpStatus.BAD_REQUEST);
}
LOGGER.debug("Processing request...");
return new ResponseEntity<>("Successful response", HttpStatus.OK);
}
}
Practical task: logging custom exceptions
Let's create a custom exception and a global handler that logs it.
Step 1. Create the custom exception
public class CustomException extends RuntimeException {
public CustomException(String message) {
super(message);
}
}
Step 2. Add a handler
@RestControllerAdvice
public class GlobalErrorHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalErrorHandler.class);
@ExceptionHandler(CustomException.class)
public ResponseEntity<String> handleCustomException(CustomException ex) {
LOGGER.error("Custom exception: {}", ex.getMessage(), ex);
return new ResponseEntity<>("An error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Step 3. Use the exception in code
@RestController
public class CustomExceptionController {
@GetMapping("/trigger-error")
public ResponseEntity<String> triggerError() {
throw new CustomException("Something went wrong!");
}
}
Result: if a user opens /trigger-error, they'll see a clear error message, and the logs will contain the stack trace with details.
Logging and monitoring
Integrating logs with monitoring systems (for example, ELK: Elasticsearch, Logstash, Kibana) lets you watch logs in real time. Spring Actuator can also expose system events to logs. In other words, your logs become more than plain text — they become a tool for predicting problems.
Common logging mistakes
- Trying to log absolutely everything (a million
DEBUGlogs = end of performance). - Using
System.out.println()instead of a proper logger. - Forgetting about logging levels: everything is either
WARNorINFO. - Logging sensitive data (like passwords).
Use the right levels and avoid overloading logs with useless information.
GO TO FULL VERSION