Article: Comprehensive Logging Strategies for Enterprise Java Applications
Logging is a critical component of any production application, providing visibility into system behavior, facilitating debugging, and enabling monitoring. This article covers comprehensive logging best practices for Java applications.
Core Logging Framework
package com.titliel.logging;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Titliel Advanced Logging Framework
* Implements enterprise logging best practices
*/
public class TitlielLogger {
// Logger configuration
private final Logger logger;
private final String className;
private final LoggingConfig config;
// Performance monitoring
private final Map<String, Long> methodTimings = new ConcurrentHashMap<>();
private static final int MAX_CONTEXT_DATA = 20;
public TitlielLogger(Class<?> clazz) {
this.logger = LoggerFactory.getLogger(clazz);
this.className = clazz.getSimpleName();
this.config = LoggingConfig.getInstance();
}
public static TitlielLogger getLogger(Class<?> clazz) {
return new TitlielLogger(clazz);
}
/**
* Structured logging with context
*/
public void info(String message, Object... args) {
if (logger.isInfoEnabled()) {
LogEntry entry = buildLogEntry(LogLevel.INFO, message, args);
logger.info(entry.toJson());
}
}
public void debug(String message, Object... args) {
if (logger.isDebugEnabled()) {
LogEntry entry = buildLogEntry(LogLevel.DEBUG, message, args);
logger.debug(entry.toJson());
}
}
public void warn(String message, Object... args) {
LogEntry entry = buildLogEntry(LogLevel.WARN, message, args);
logger.warn(entry.toJson());
}
public void error(String message, Object... args) {
LogEntry entry = buildLogEntry(LogLevel.ERROR, message, args);
logger.error(entry.toJson());
}
public void error(String message, Throwable throwable, Object... args) {
LogEntry entry = buildLogEntry(LogLevel.ERROR, message, args)
.withException(throwable);
logger.error(entry.toJson(), throwable);
}
/**
* Business transaction logging
*/
public void businessEvent(String eventType, String transactionId,
Map<String, Object> businessData) {
LogEntry entry = buildLogEntry(LogLevel.INFO, "Business event: {}", eventType)
.withTransactionId(transactionId)
.withBusinessData(businessData);
logger.info(entry.toJson());
}
/**
* Performance monitoring with timing
*/
public void timeMethod(String methodName, Runnable operation) {
long startTime = System.currentTimeMillis();
String timingKey = className + "." + methodName;
try {
debug("Method {} started", methodName);
operation.run();
} finally {
long duration = System.currentTimeMillis() - startTime;
methodTimings.put(timingKey, duration);
if (duration > config.getSlowMethodThreshold()) {
warn("Slow method execution: {} took {}ms", methodName, duration);
}
debug("Method {} completed in {}ms", methodName, duration);
}
}
/**
* Audit logging for security-sensitive operations
*/
public void audit(String action, String userId, String resource,
Map<String, String> details) {
LogEntry entry = buildLogEntry(LogLevel.INFO, "Audit: {}", action)
.withUserId(userId)
.withResource(resource)
.withAuditDetails(details);
logger.info(entry.toJson());
}
private LogEntry buildLogEntry(LogLevel level, String message, Object... args) {
String formattedMessage = String.format(message, args);
return new LogEntry()
.withTimestamp(new Date())
.withLevel(level)
.withLogger(className)
.withMessage(formattedMessage)
.withThread(Thread.currentThread().getName())
.withContext(getContextData());
}
private Map<String, String> getContextData() {
Map<String, String> context = new HashMap<>();
// Add MDC (Mapped Diagnostic Context)
Map<String, String> mdc = MDC.getCopyOfContextMap();
if (mdc != null) {
context.putAll(mdc);
}
// Add application-specific context
context.put("appVersion", config.getAppVersion());
context.put("environment", config.getEnvironment());
// Limit context size for performance
if (context.size() > MAX_CONTEXT_DATA) {
Map<String, String> limited = new HashMap<>();
int count = 0;
for (Map.Entry<String, String> entry : context.entrySet()) {
if (count++ >= MAX_CONTEXT_DATA) break;
limited.put(entry.getKey(), entry.getValue());
}
return limited;
}
return context;
}
/**
* Create a child logger with additional context
*/
public TitlielLogger withContext(String key, String value) {
MDC.put(key, value);
return this;
}
/**
* Clear MDC context
*/
public void clearContext() {
MDC.clear();
}
}
Log Entry Data Structure
package com.titliel.logging;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.*;
/**
* Structured log entry with comprehensive metadata
*/
public class LogEntry {
private Date timestamp;
private LogLevel level;
private String logger;
private String message;
private String thread;
private Map<String, String> context;
private String transactionId;
private String userId;
private String resource;
private Map<String, Object> businessData;
private Map<String, String> auditDetails;
private ExceptionInfo exception;
private static final ObjectMapper objectMapper = new ObjectMapper();
// Fluent builder methods
public LogEntry withTimestamp(Date timestamp) {
this.timestamp = timestamp;
return this;
}
public LogEntry withLevel(LogLevel level) {
this.level = level;
return this;
}
public LogEntry withLogger(String logger) {
this.logger = logger;
return this;
}
public LogEntry withMessage(String message) {
this.message = message;
return this;
}
public LogEntry withThread(String thread) {
this.thread = thread;
return this;
}
public LogEntry withContext(Map<String, String> context) {
this.context = context;
return this;
}
public LogEntry withTransactionId(String transactionId) {
this.transactionId = transactionId;
return this;
}
public LogEntry withUserId(String userId) {
this.userId = userId;
return this;
}
public LogEntry withResource(String resource) {
this.resource = resource;
return this;
}
public LogEntry withBusinessData(Map<String, Object> businessData) {
this.businessData = businessData;
return this;
}
public LogEntry withAuditDetails(Map<String, String> auditDetails) {
this.auditDetails = auditDetails;
return this;
}
public LogEntry withException(Throwable throwable) {
if (throwable != null) {
this.exception = new ExceptionInfo(throwable);
}
return this;
}
/**
* Convert to JSON for structured logging
*/
public String toJson() {
try {
return objectMapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
// Fallback to simple format if JSON serialization fails
return String.format("{\"level\":\"%s\", \"message\":\"%s\", \"error\":\"JSON serialization failed\"}",
level, message);
}
}
/**
* Convert to human-readable format
*/
public String toFormattedString() {
return String.format("[%s] %s %s - %s - Context: %s",
timestamp, level, logger, message, context);
}
// Getters
public Date getTimestamp() { return timestamp; }
public LogLevel getLevel() { return level; }
public String getLogger() { return logger; }
public String getMessage() { return message; }
public String getThread() { return thread; }
public Map<String, String> getContext() { return context; }
public String getTransactionId() { return transactionId; }
public String getUserId() { return userId; }
public String getResource() { return resource; }
public Map<String, Object> getBusinessData() { return businessData; }
public Map<String, String> getAuditDetails() { return auditDetails; }
public ExceptionInfo getException() { return exception; }
}
/**
* Exception information for structured logging
*/
class ExceptionInfo {
private final String type;
private final String message;
private final String stackTrace;
public ExceptionInfo(Throwable throwable) {
this.type = throwable.getClass().getName();
this.message = throwable.getMessage();
this.stackTrace = buildStackTrace(throwable);
}
private String buildStackTrace(Throwable throwable) {
StringBuilder sb = new StringBuilder();
for (StackTraceElement element : throwable.getStackTrace()) {
sb.append(element.toString()).append("\n");
}
return sb.toString();
}
// Getters
public String getType() { return type; }
public String getMessage() { return message; }
public String getStackTrace() { return stackTrace; }
}
/**
* Log levels
*/
enum LogLevel {
TRACE, DEBUG, INFO, WARN, ERROR, FATAL
}
Logging Configuration
package com.titliel.logging;
import java.io.InputStream;
import java.util.Properties;
/**
* Centralized logging configuration
*/
public class LoggingConfig {
private static LoggingConfig instance;
private final Properties properties;
// Configuration keys
private static final String APP_VERSION = "app.version";
private static final String ENVIRONMENT = "app.environment";
private static final String SLOW_METHOD_THRESHOLD = "logging.slow.method.threshold";
private static final String LOG_LEVEL = "logging.level";
private static final String ENABLE_JSON_LOGGING = "logging.json.enabled";
private static final String ENABLE_PERFORMANCE_LOGGING = "logging.performance.enabled";
private LoggingConfig() {
this.properties = loadProperties();
}
public static synchronized LoggingConfig getInstance() {
if (instance == null) {
instance = new LoggingConfig();
}
return instance;
}
private Properties loadProperties() {
Properties props = new Properties();
try (InputStream input = getClass().getClassLoader()
.getResourceAsStream("logging.properties")) {
if (input != null) {
props.load(input);
} else {
// Default configuration
setDefaults(props);
}
} catch (Exception e) {
System.err.println("Failed to load logging configuration: " + e.getMessage());
setDefaults(props);
}
return props;
}
private void setDefaults(Properties props) {
props.setProperty(APP_VERSION, "1.0.0");
props.setProperty(ENVIRONMENT, "development");
props.setProperty(SLOW_METHOD_THRESHOLD, "1000");
props.setProperty(LOG_LEVEL, "INFO");
props.setProperty(ENABLE_JSON_LOGGING, "true");
props.setProperty(ENABLE_PERFORMANCE_LOGGING, "true");
}
// Getters with defaults
public String getAppVersion() {
return properties.getProperty(APP_VERSION, "1.0.0");
}
public String getEnvironment() {
return properties.getProperty(ENVIRONMENT, "development");
}
public long getSlowMethodThreshold() {
return Long.parseLong(properties.getProperty(SLOW_METHOD_THRESHOLD, "1000"));
}
public String getLogLevel() {
return properties.getProperty(LOG_LEVEL, "INFO");
}
public boolean isJsonLoggingEnabled() {
return Boolean.parseBoolean(properties.getProperty(ENABLE_JSON_LOGGING, "true"));
}
public boolean isPerformanceLoggingEnabled() {
return Boolean.parseBoolean(properties.getProperty(ENABLE_PERFORMANCE_LOGGING, "true"));
}
public void setProperty(String key, String value) {
properties.setProperty(key, value);
}
}
Advanced Logging Utilities
package com.titliel.logging;
import org.slf4j.MDC;
import java.util.*;
import java.util.concurrent.Callable;
/**
* Advanced logging utilities and helpers
*/
public class LoggingUtils {
/**
* Execute with MDC context
*/
public static void withContext(Map<String, String> context, Runnable operation) {
Map<String, String> previousContext = MDC.getCopyOfContextMap();
try {
// Set new context
if (context != null) {
MDC.setContextMap(context);
}
operation.run();
} finally {
// Restore previous context
if (previousContext != null) {
MDC.setContextMap(previousContext);
} else {
MDC.clear();
}
}
}
/**
* Execute with MDC context and return value
*/
public static <T> T withContext(Map<String, String> context, Callable<T> operation)
throws Exception {
Map<String, String> previousContext = MDC.getCopyOfContextMap();
try {
// Set new context
if (context != null) {
MDC.setContextMap(context);
}
return operation.call();
} finally {
// Restore previous context
if (previousContext != null) {
MDC.setContextMap(previousContext);
} else {
MDC.clear();
}
}
}
/**
* Create transaction context for distributed tracing
*/
public static Map<String, String> createTransactionContext(String transactionId) {
Map<String, String> context = new HashMap<>();
context.put("transactionId", transactionId);
context.put("spanId", generateSpanId());
context.put("timestamp", String.valueOf(System.currentTimeMillis()));
return context;
}
/**
* Create user context for request tracing
*/
public static Map<String, String> createUserContext(String userId, String sessionId) {
Map<String, String> context = new HashMap<>();
context.put("userId", userId);
context.put("sessionId", sessionId);
context.put("userAgent", getCurrentUserAgent());
context.put("clientIp", getClientIp());
return context;
}
/**
* Mask sensitive data in logs
*/
public static String maskSensitiveData(String input) {
if (input == null) return null;
// Mask credit cards
input = input.replaceAll("\\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14})\\b", "****-****-****-####");
// Mask emails
input = input.replaceAll("\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b",
"***@***.***");
// Mask passwords in key=value pairs
input = input.replaceAll("(password|pwd|pass)=[^&\\s]+", "$1=***");
return input;
}
/**
* Sanitize log message to prevent log injection
*/
public static String sanitizeMessage(String message) {
if (message == null) return null;
// Remove newlines and control characters
return message.replaceAll("[\\r\\n\\t]", " ");
}
/**
* Calculate log rate limiting key
*/
public static String getRateLimitKey(String message, LogLevel level) {
return level + ":" + message.hashCode();
}
private static String generateSpanId() {
return UUID.randomUUID().toString().substring(0, 8);
}
private static String getCurrentUserAgent() {
// In web app, get from HttpServletRequest
return System.getProperty("http.agent", "unknown");
}
private static String getClientIp() {
// In web app, get from HttpServletRequest
return "127.0.0.1";
}
}
/**
* Logging aspect for automatic method tracing
*/
package com.titliel.logging.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
private final TitlielLogger logger = TitlielLogger.getLogger(LoggingAspect.class);
@Around("@annotation(com.titliel.logging.annotation.LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String methodName = signature.getDeclaringType().getSimpleName() + "." + signature.getName();
return logger.timeMethod(methodName, () -> {
try {
return joinPoint.proceed();
} catch (Throwable throwable) {
if (throwable instanceof RuntimeException) {
throw (RuntimeException) throwable;
}
throw new RuntimeException(throwable);
}
});
}
@Around("@annotation(com.titliel.logging.annotation.LogParameters)")
public Object logParameters(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String methodName = signature.getName();
// Log method entry with parameters
if (logger.isDebugEnabled()) {
Object[] args = joinPoint.getArgs();
String[] paramNames = signature.getParameterNames();
Map<String, Object> params = new HashMap<>();
for (int i = 0; i < args.length; i++) {
String paramName = paramNames != null ? paramNames[i] : "arg" + i;
params.put(paramName, maskSensitiveParameters(paramName, args[i]));
}
logger.debug("Method {} invoked with parameters: {}", methodName, params);
}
try {
Object result = joinPoint.proceed();
// Log method exit with result (if not sensitive)
if (logger.isDebugEnabled()) {
logger.debug("Method {} completed successfully", methodName);
}
return result;
} catch (Exception e) {
logger.error("Method {} failed with exception", methodName, e);
throw e;
}
}
private Object maskSensitiveParameters(String paramName, Object value) {
if (value == null) return null;
String paramNameLower = paramName.toLowerCase();
if (paramNameLower.contains("password") || paramNameLower.contains("token") ||
paramNameLower.contains("secret") || paramNameLower.contains("key")) {
return "***";
}
return value;
}
}
Logback Configuration
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
<!-- Property definitions -->
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<property name="JSON_PATTERN" value='{"timestamp":"%d{yyyy-MM-dd HH:mm:ss.SSS}","level":"%level","logger":"%logger","thread":"%thread","message":"%message","context":"%mdc"}'/>
<property name="LOG_PATH" value="${LOG_PATH:-logs}"/>
<property name="APP_NAME" value="my-application"/>
<!-- Console Appender -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- JSON Console Appender for structured logging -->
<appender name="JSON_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp/>
<logLevel/>
<loggerName/>
<message/>
<mdc/>
<threadName/>
<stackTrace/>
</providers>
</encoder>
</appender>
<!-- File Appender -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APP_NAME}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- JSON File Appender -->
<appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APP_NAME}-json.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APP_NAME}-json.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp/>
<logLevel/>
<loggerName/>
<message/>
<mdc/>
<threadName/>
<stackTrace/>
</providers>
</encoder>
</appender>
<!-- Async Appender for Performance -->
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE"/>
<queueSize>10000</queueSize>
<discardingThreshold>0</discardingThreshold>
<includeCallerData>false</includeCallerData>
</appender>
<!-- Error-specific appender -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/errors.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/errors.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>90</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- Audit log appender -->
<appender name="AUDIT_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/audit.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/audit.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>365</maxHistory>
<totalSizeCap>5GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- Logger configurations -->
<logger name="com.titliel" level="DEBUG" additivity="false">
<appender-ref ref="JSON_CONSOLE"/>
<appender-ref ref="ASYNC_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</logger>
<logger name="AUDIT_LOGGER" level="INFO" additivity="false">
<appender-ref ref="AUDIT_FILE"/>
</logger>
<!-- Third-party loggers -->
<logger name="org.springframework" level="INFO"/>
<logger name="org.hibernate" level="WARN"/>
<logger name="com.zaxxer.hikari" level="INFO"/>
<!-- Root logger -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_FILE"/>
</root>
</configuration>
Usage Examples
package com.titliel.service;
import com.titliel.logging.TitlielLogger;
import com.titliel.logging.LoggingUtils;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class UserService {
private final TitlielLogger logger = TitlielLogger.getLogger(UserService.class);
public User createUser(User user) {
// Method timing and tracing
return logger.timeMethod("createUser", () -> {
// Business event logging
Map<String, Object> businessData = new HashMap<>();
businessData.put("userId", user.getId());
businessData.put("email", user.getEmail());
businessData.put("role", user.getRole());
logger.businessEvent("USER_CREATED", user.getId(), businessData);
try {
// Business logic
validateUser(user);
User savedUser = userRepository.save(user);
logger.info("User created successfully: {}", user.getId());
return savedUser;
} catch (Exception e) {
logger.error("Failed to create user: {}", user.getId(), e);
throw e;
}
});
}
public User authenticate(String username, String password) {
// Audit logging for security-sensitive operations
Map<String, String> auditDetails = new HashMap<>();
auditDetails.put("action", "USER_LOGIN");
auditDetails.put("username", username);
auditDetails.put("ip", getClientIp());
logger.audit("LOGIN_ATTEMPT", username, "AUTH_SYSTEM", auditDetails);
try {
User user = userRepository.findByUsername(username);
if (user != null && passwordEncoder.matches(password, user.getPassword())) {
// Successful login
auditDetails.put("result", "SUCCESS");
logger.audit("LOGIN_SUCCESS", username, "AUTH_SYSTEM", auditDetails);
return user;
} else {
// Failed login
auditDetails.put("result", "FAILED");
logger.audit("LOGIN_FAILED", username, "AUTH_SYSTEM", auditDetails);
throw new AuthenticationException("Invalid credentials");
}
} catch (Exception e) {
logger.error("Authentication error for user: {}", username, e);
throw e;
}
}
public void processBatch(List<User> users) {
// Log with context for batch processing
Map<String, String> context = new HashMap<>();
context.put("batchSize", String.valueOf(users.size()));
context.put("batchId", UUID.randomUUID().toString());
LoggingUtils.withContext(context, () -> {
logger.info("Starting batch processing");
for (int i = 0; i < users.size(); i++) {
try {
User user = users.get(i);
logger.withContext("currentIndex", String.valueOf(i))
.info("Processing user: {}", user.getId());
processUser(user);
} catch (Exception e) {
logger.error("Failed to process user at index: {}", i, e);
// Continue with next user
}
}
logger.info("Batch processing completed");
});
}
private void validateUser(User user) {
// Debug logging for validation logic
logger.debug("Validating user: {}", user.getEmail());
if (user.getEmail() == null || !user.getEmail().contains("@")) {
logger.warn("Invalid email format for user: {}", user.getEmail());
throw new ValidationException("Invalid email format");
}
logger.debug("User validation passed: {}", user.getEmail());
}
}
Maven Dependencies
<dependencies> <!-- Logging facade --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.7</version> </dependency> <!-- Logback implementation --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.4.8</version> </dependency> <!-- JSON logging --> <dependency> <groupId>net.logstash.logback</groupId> <artifactId>logstash-logback-encoder</artifactId> <version>7.3</version> </dependency> <!-- Jackson for JSON processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.0</version> </dependency> <!-- AspectJ for method tracing --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>6.0.9</version> </dependency> </dependencies>
Best Practices Summary
1. Use Structured Logging
- Log in JSON format for better parsing
- Include contextual information
- Use consistent log structure
2. Proper Log Levels
- ERROR: System failures, unrecoverable errors
- WARN: Unexpected but recoverable situations
- INFO: Business events, system lifecycle
- DEBUG: Detailed debugging information
- TRACE: Very detailed tracing
3. Performance Considerations
- Use parameterized logging:
log.debug("User {} created", userId) - Implement conditional logging checks
- Use async appenders for high-throughput scenarios
4. Security
- Never log sensitive data (passwords, tokens, PII)
- Implement log sanitization
- Use log masking for sensitive fields
5. Operational Excellence
- Implement log rotation and retention policies
- Use correlation IDs for distributed tracing
- Include sufficient context for debugging
- Monitor log volume and patterns
This Titliel logging framework provides enterprise-grade logging capabilities with security, performance, and operational excellence built-in.