Back to Blog
·
java logging monitoring best-practices
Java Logging Best Practices for Production Systems
Effective logging strategies that help you debug issues faster and monitor application health in production.
VK
Varij Kapil Good logging is the difference between hours of debugging and minutes. After years of troubleshooting production issues, here are the logging practices that have saved me countless times.
Choosing the Right Log Level
Use levels consistently across your application:
// ERROR: Something failed and needs attention
// - Exceptions that affect functionality
// - Failed external service calls after retries
log.error("Failed to process payment for order {}", orderId, exception);
// WARN: Something unexpected but handled
// - Retry attempts
// - Deprecated API usage
// - Performance degradation
log.warn("Database connection slow, query took {}ms", duration);
// INFO: Business events and application lifecycle
// - Request processing completed
// - Configuration loaded
// - Scheduled jobs started/completed
log.info("Order {} created for customer {}", orderId, customerId);
// DEBUG: Detailed information for troubleshooting
// - Method entry/exit with parameters
// - Intermediate values
// - External service request/response
log.debug("Calculating discount for items: {}", items);
// TRACE: Very detailed, usually disabled
// - Loop iterations
// - Every step in an algorithm
log.trace("Processing item {} of {}", index, total);
Structured Logging
Make logs machine-parseable for better querying:
// Using SLF4J with Logback and logstash-encoder
import static net.logstash.logback.argument.StructuredArguments.*;
log.info("Order processed",
kv("orderId", order.getId()),
kv("customerId", order.getCustomerId()),
kv("amount", order.getTotalAmount()),
kv("itemCount", order.getItems().size()),
kv("processingTimeMs", duration));
Output:
{
"timestamp": "2024-05-10T14:30:00.000Z",
"level": "INFO",
"message": "Order processed",
"orderId": "ORD-12345",
"customerId": "CUST-789",
"amount": 150.00,
"itemCount": 3,
"processingTimeMs": 45
}
MDC for Request Context
Add context that’s automatically included in all log entries:
@Provider
@Priority(Priorities.USER)
public class LoggingContextFilter implements ContainerRequestFilter, ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext request) {
// Generate or extract correlation ID
String correlationId = request.getHeaderString("X-Correlation-ID");
if (correlationId == null) {
correlationId = UUID.randomUUID().toString();
}
// Add to MDC - automatically included in all logs
MDC.put("correlationId", correlationId);
MDC.put("method", request.getMethod());
MDC.put("path", request.getUriInfo().getPath());
// Add user context if authenticated
SecurityContext security = request.getSecurityContext();
if (security.getUserPrincipal() != null) {
MDC.put("userId", security.getUserPrincipal().getName());
}
}
@Override
public void filter(ContainerRequestContext request,
ContainerResponseContext response) {
// Pass correlation ID to response
response.getHeaders().add("X-Correlation-ID", MDC.get("correlationId"));
// Clean up MDC
MDC.clear();
}
}
Logback configuration:
<pattern>%d{ISO8601} [%X{correlationId}] [%X{userId:-anonymous}] %-5level %logger{36} - %msg%n</pattern>
What to Log
Always Log
// Application startup and configuration
log.info("Application starting with profile: {}", activeProfile);
log.info("Database connection pool: min={}, max={}", minPool, maxPool);
// Security events
log.info("User {} logged in from IP {}", username, ipAddress);
log.warn("Failed login attempt for user {} from IP {}", username, ipAddress);
// Business transactions
log.info("Order {} submitted: {} items, total={}", orderId, itemCount, total);
// External service calls
log.debug("Calling payment service for order {}", orderId);
log.info("Payment service responded in {}ms with status {}", duration, status);
// Errors with full context
log.error("Failed to send email to {}: {}", email, exception.getMessage(), exception);
Never Log
// NEVER log sensitive data
log.info("User password: {}", password); // NEVER
log.info("Credit card: {}", cardNumber); // NEVER
log.info("SSN: {}", socialSecurityNumber); // NEVER
log.info("Auth token: {}", authToken); // NEVER
// Mask sensitive data if needed
log.info("Processing card ending in {}", maskCardNumber(cardNumber));
Exception Logging
Log exceptions properly:
// Bad: loses stack trace
log.error("Error: " + exception.getMessage());
// Bad: logs exception twice
log.error("Error: " + exception.getMessage(), exception);
// Good: message + exception as last argument
log.error("Failed to process order {}: {}", orderId, exception.getMessage(), exception);
// For expected exceptions, consider WARN without stack trace
try {
externalService.call();
} catch (ServiceUnavailableException e) {
log.warn("External service unavailable, will retry: {}", e.getMessage());
// Retry logic...
} catch (Exception e) {
log.error("Unexpected error calling external service", e);
throw e;
}
Performance Considerations
Avoid Expensive Operations in Log Statements
// Bad: toString() called even if DEBUG is disabled
log.debug("Processing items: " + items.toString());
// Good: parameterized logging, evaluated only if needed
log.debug("Processing items: {}", items);
// For expensive operations, check level first
if (log.isDebugEnabled()) {
String expensiveData = calculateExpensiveDebugInfo();
log.debug("Debug info: {}", expensiveData);
}
Async Logging for High-Throughput Systems
<!-- logback.xml -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>10000</queueSize>
<discardingThreshold>0</discardingThreshold>
<includeCallerData>false</includeCallerData>
<appender-ref ref="FILE"/>
</appender>
<root level="INFO">
<appender-ref ref="ASYNC"/>
</root>
Log Aggregation Configuration
Configure for centralized logging (ELK, Splunk, etc.):
<!-- logback-spring.xml -->
<configuration>
<springProfile name="production">
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>correlationId</includeMdcKeyName>
<includeMdcKeyName>userId</includeMdcKeyName>
<customFields>{"service":"order-service","env":"${ENV}"}</customFields>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<springProfile name="development">
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %highlight(%-5level) [%thread] %cyan(%logger{36}) - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
</configuration>
Log Levels Per Environment
# application-dev.yml
logging:
level:
root: INFO
com.mycompany: DEBUG
org.hibernate.SQL: DEBUG
# application-prod.yml
logging:
level:
root: WARN
com.mycompany: INFO
org.hibernate: WARN
Useful Logging Patterns
Method Entry/Exit for Debugging
public Order processOrder(OrderRequest request) {
log.debug("processOrder() called with request: {}", request);
try {
Order result = doProcess(request);
log.debug("processOrder() returning: {}", result.getId());
return result;
} catch (Exception e) {
log.error("processOrder() failed for request: {}", request, e);
throw e;
}
}
Timed Operations
public void syncData() {
long start = System.currentTimeMillis();
log.info("Starting data sync");
try {
int count = performSync();
long duration = System.currentTimeMillis() - start;
log.info("Data sync completed: {} records in {}ms", count, duration);
} catch (Exception e) {
long duration = System.currentTimeMillis() - start;
log.error("Data sync failed after {}ms", duration, e);
throw e;
}
}
Key Takeaways
- Use appropriate log levels - ERROR for failures, INFO for business events
- Structure your logs - JSON format for easier querying
- Add context with MDC - Correlation IDs, user IDs, request paths
- Never log secrets - Passwords, tokens, personal data
- Log exceptions properly - Include stack trace for unexpected errors
- Consider performance - Use parameterized logging, async appenders
- Configure per environment - Verbose in dev, focused in production
Good logging is an investment that pays off during every production incident.