
Logger in Java – Logging Example and Best Practices
Java logging is a fundamental aspect of application development that serves as your eyes and ears into what your application is doing at runtime. Whether you’re debugging tricky issues, monitoring performance, or maintaining compliance requirements, proper logging can make the difference between a quick fix and hours of head-scratching. This guide walks you through Java’s logging ecosystem, from basic implementations to advanced configurations, helping you avoid common pitfalls while implementing industry best practices that will make your applications more maintainable and your debugging sessions more productive.
Understanding Java Logging Architecture
Java’s logging landscape has evolved significantly over the years. The core java.util.logging (JUL) package provides basic functionality, but most enterprise applications rely on more robust frameworks like SLF4J, Logback, or Log4j2. These frameworks follow a similar pattern: loggers capture events, appenders determine where logs go, and formatters control how they appear.
The typical logging flow works like this: your application code calls a logger method, the logger checks if that level is enabled, formats the message, and passes it to configured appenders. Appenders then write to destinations like files, databases, or network endpoints.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
public User createUser(String username, String email) {
logger.info("Creating user with username: {}", username);
try {
User user = new User(username, email);
userRepository.save(user);
logger.info("Successfully created user with ID: {}", user.getId());
return user;
} catch (ValidationException e) {
logger.warn("User creation failed for username {}: {}", username, e.getMessage());
throw e;
} catch (Exception e) {
logger.error("Unexpected error creating user {}: ", username, e);
throw new ServiceException("User creation failed", e);
}
}
}
Step-by-Step Implementation Guide
Setting up logging properly requires choosing the right framework and configuring it for your environment. Here’s a practical approach using SLF4J with Logback, which offers excellent performance and flexibility.
First, add the necessary dependencies to your project:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>
Create a logback-spring.xml configuration file in your resources directory:
<configuration>
<property name="LOG_PATH" value="./logs"/>
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/application.log</file>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/application.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
<logger name="com.yourcompany.security" level="DEBUG"/>
<logger name="org.springframework" level="WARN"/>
</configuration>
For applications running on VPS environments, consider adding structured logging for better log aggregation:
<appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/application.json</file>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp/>
<logLevel/>
<loggerName/>
<message/>
<stackTrace/>
<mdc/>
</providers>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/application.%d{yyyy-MM-dd}.json.gz</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
</appender>
Real-World Examples and Use Cases
Different scenarios require different logging approaches. Here are some practical examples that you’ll encounter in production environments.
For web applications, implement request tracking using MDC (Mapped Diagnostic Context):
@Component
public class RequestLoggingFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(RequestLoggingFilter.class);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestId = UUID.randomUUID().toString().substring(0, 8);
MDC.put("requestId", requestId);
MDC.put("userId", getCurrentUserId(httpRequest));
MDC.put("userAgent", httpRequest.getHeader("User-Agent"));
long startTime = System.currentTimeMillis();
logger.info("Request started: {} {}", httpRequest.getMethod(), httpRequest.getRequestURI());
try {
chain.doFilter(request, response);
} finally {
long duration = System.currentTimeMillis() - startTime;
HttpServletResponse httpResponse = (HttpServletResponse) response;
logger.info("Request completed: status={}, duration={}ms",
httpResponse.getStatus(), duration);
MDC.clear();
}
}
}
For database operations, implement detailed logging with performance metrics:
@Repository
public class UserRepository {
private static final Logger logger = LoggerFactory.getLogger(UserRepository.class);
private static final Logger slowQueryLogger = LoggerFactory.getLogger("SLOW_QUERY");
public List<User> findUsersByCity(String city) {
logger.debug("Searching users in city: {}", city);
long startTime = System.nanoTime();
List<User> users = jdbcTemplate.query(
"SELECT * FROM users WHERE city = ? AND active = 1",
userRowMapper, city);
long durationMs = (System.nanoTime() - startTime) / 1_000_000;
if (durationMs > 1000) {
slowQueryLogger.warn("Slow query detected: findUsersByCity took {}ms for city: {}",
durationMs, city);
}
logger.info("Found {} users in city: {} ({}ms)", users.size(), city, durationMs);
return users;
}
}
For microservices running on dedicated servers, implement correlation ID tracking across service boundaries:
@RestController
public class OrderController {
private static final Logger logger = LoggerFactory.getLogger(OrderController.class);
@Autowired
private PaymentService paymentService;
@PostMapping("/orders")
public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
String correlationId = MDC.get("correlationId");
if (correlationId == null) {
correlationId = generateCorrelationId();
MDC.put("correlationId", correlationId);
}
logger.info("Processing order creation: items={}, total={}",
request.getItems().size(), request.getTotal());
try {
Order order = orderService.createOrder(request);
PaymentResult payment = paymentService.processPayment(order.getTotal(), correlationId);
logger.info("Order created successfully: orderId={}, paymentStatus={}",
order.getId(), payment.getStatus());
return ResponseEntity.ok(order);
} catch (PaymentException e) {
logger.warn("Payment failed for order: amount={}, reason={}",
request.getTotal(), e.getMessage());
throw new OrderProcessingException("Payment processing failed", e);
}
}
}
Framework Comparison and Performance
Choosing the right logging framework significantly impacts both performance and functionality. Here’s a detailed comparison of popular Java logging frameworks:
Framework | Performance | Configuration | Features | Memory Usage | Best Use Case |
---|---|---|---|---|---|
java.util.logging | Moderate | Properties/XML | Basic | Low | Simple applications |
Logback | High | XML/Groovy | Rich | Moderate | Most applications |
Log4j2 | Highest | XML/JSON/YAML | Most comprehensive | Higher | High-performance systems |
SLF4J + Simple | High | Properties | Minimal | Very Low | Microservices |
Performance benchmarks show significant differences in throughput:
Framework | Synchronous (ops/sec) | Asynchronous (ops/sec) | Memory per Log Entry | Startup Time |
---|---|---|---|---|
Logback | 580,000 | 2,800,000 | 180 bytes | 45ms |
Log4j2 | 720,000 | 4,100,000 | 165 bytes | 120ms |
JUL | 210,000 | N/A | 220 bytes | 15ms |
For high-throughput applications, enable asynchronous logging:
<configuration>
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE"/>
<queueSize>1000</queueSize>
<discardingThreshold>0</discardingThreshold>
<includeCallerData>false</includeCallerData>
<neverBlock>true</neverBlock>
</appender>
<root level="INFO">
<appender-ref ref="ASYNC_FILE"/>
</root>
</configuration>
Best Practices and Common Pitfalls
Effective logging requires following established patterns while avoiding common mistakes that can impact performance or obscure important information.
Use appropriate log levels consistently:
- ERROR: System failures, exceptions that affect functionality
- WARN: Unexpected situations that don’t break functionality
- INFO: Important business events, system lifecycle events
- DEBUG: Detailed diagnostic information
- TRACE: Very detailed diagnostic information
Implement structured logging for better searchability:
public class PaymentProcessor {
private static final Logger logger = LoggerFactory.getLogger(PaymentProcessor.class);
public PaymentResult processPayment(PaymentRequest request) {
// Good: Structured with context
logger.info("Payment processing started: userId={}, amount={}, currency={}, method={}",
request.getUserId(), request.getAmount(), request.getCurrency(), request.getMethod());
// Bad: Unstructured message
// logger.info("Processing payment for user " + request.getUserId() + " amount " + request.getAmount());
try {
PaymentResult result = externalPaymentGateway.charge(request);
logger.info("Payment completed: transactionId={}, status={}, processingTime={}ms",
result.getTransactionId(), result.getStatus(), result.getProcessingTime());
return result;
} catch (PaymentGatewayException e) {
logger.error("Payment gateway error: userId={}, amount={}, gatewayError={}, transactionId={}",
request.getUserId(), request.getAmount(), e.getErrorCode(), e.getTransactionId(), e);
throw new PaymentProcessingException("Payment failed", e);
}
}
}
Avoid these common anti-patterns:
// DON'T: String concatenation in log messages
logger.info("User " + user.getName() + " performed action " + action);
// DO: Use parameterized messages
logger.info("User {} performed action {}", user.getName(), action);
// DON'T: Logging sensitive information
logger.info("User login: username={}, password={}", username, password);
// DO: Log safely with masking
logger.info("User login: username={}, password={}", username, maskPassword(password));
// DON'T: Expensive operations in log statements
logger.debug("Current state: " + expensiveToStringMethod());
// DO: Guard with level checks for expensive operations
if (logger.isDebugEnabled()) {
logger.debug("Current state: {}", expensiveToStringMethod());
}
Implement proper exception logging:
public class FileProcessor {
private static final Logger logger = LoggerFactory.getLogger(FileProcessor.class);
public void processFile(String filename) {
try {
File file = new File(filename);
processFileContent(file);
logger.info("File processed successfully: filename={}, size={}", filename, file.length());
} catch (IOException e) {
// Good: Include context and full exception
logger.error("Failed to process file: filename={}, error={}", filename, e.getMessage(), e);
throw new FileProcessingException("File processing failed: " + filename, e);
} catch (SecurityException e) {
// Good: Different handling for different exception types
logger.warn("Access denied for file: filename={}, user={}", filename, getCurrentUser());
throw new AccessDeniedException("Insufficient permissions for file: " + filename);
}
}
}
Configure environment-specific logging levels:
<configuration>
<springProfile name="development">
<logger name="com.yourcompany" level="DEBUG"/>
<logger name="org.springframework.web" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<springProfile name="production">
<logger name="com.yourcompany" level="INFO"/>
<logger name="org.springframework" level="WARN"/>
<root level="WARN">
<appender-ref ref="ASYNC_FILE"/>
</root>
</springProfile>
</configuration>
For production deployments, implement log monitoring and alerting. Tools like ELK Stack (Elasticsearch, Logstash, Kibana) or Fluentd can aggregate logs from multiple instances. Set up alerts for ERROR level logs and monitor log volume patterns to detect anomalies.
Security considerations include avoiding logging sensitive data, implementing log file access controls, and considering log retention policies for compliance requirements. Always sanitize user inputs before logging and be cautious with debugging logs in production environments.
Performance optimization involves using asynchronous appenders for high-throughput scenarios, implementing appropriate buffering strategies, and regularly rotating log files to prevent disk space issues. Monitor your logging overhead – it should typically consume less than 1% of application CPU time.
The official documentation for major logging frameworks provides comprehensive configuration options: Logback documentation and Apache Log4j2 documentation offer detailed guides for advanced configurations and performance tuning.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.