StructuredLogger.java
package com.licensis.notaire.observability;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
* Centralized structured logging utility for observability
* Supports JSON logging for ELK/Prometheus integration
* Implements singleton pattern for consistency
*/
public final class StructuredLogger {
private static final StructuredLogger INSTANCE = new StructuredLogger();
private static final Logger logger = LoggerFactory.getLogger(StructuredLogger.class);
private static final ObjectMapper objectMapper = new ObjectMapper();
private StructuredLogger() {
}
public static StructuredLogger getInstance() {
return INSTANCE;
}
/**
* Log at TRACE level with structured context
*/
public void logTrace(String message, Map<String, Object> context) {
if (logger.isTraceEnabled()) {
Map<String, Object> logData = buildLogData("TRACE", message, context);
logger.trace(toJson(logData));
}
}
/**
* Log at DEBUG level with structured context
*/
public void logDebug(String message, Map<String, Object> context) {
if (logger.isDebugEnabled()) {
Map<String, Object> logData = buildLogData("DEBUG", message, context);
logger.debug(toJson(logData));
}
}
/**
* Log at INFO level (events and metrics)
*/
public void logInfo(String message, Map<String, Object> context) {
Map<String, Object> logData = buildLogData("INFO", message, context);
logger.info(toJson(logData));
}
/**
* Log at WARN level (warnings and recoverable errors)
*/
public void logWarn(String message, Map<String, Object> context) {
Map<String, Object> logData = buildLogData("WARN", message, context);
logger.warn(toJson(logData));
}
/**
* Log at ERROR level with exception
*/
public void logError(String message, Throwable throwable, Map<String, Object> context) {
Map<String, Object> logData = buildLogData("ERROR", message, context);
logData.put("exception", throwable.getClass().getSimpleName());
logData.put("exceptionMessage", throwable.getMessage());
logData.put("stackTrace", getStackTrace(throwable));
logger.error(toJson(logData), throwable);
}
/**
* Log business metric event
*/
public void logMetric(String metricName, double value, Map<String, Object> tags) {
Map<String, Object> logData = new HashMap<>();
logData.put("type", "METRIC");
logData.put("metric", metricName);
logData.put("value", value);
logData.putAll(tags);
logger.info(toJson(logData));
}
/**
* Log audit event (security/compliance)
*/
public void logAudit(String action, String actor, String resource, Map<String, Object> details) {
Map<String, Object> logData = buildLogData("AUDIT", action, Map.of(
"actor", actor,
"resource", resource
));
logData.putAll(details);
logger.info(toJson(logData));
}
/**
* Log health probe result
*/
public void logHealthProbe(String probeName, String status, Map<String, Object> details) {
Map<String, Object> logData = buildLogData("PROBE", probeName, Map.of(
"status", status
));
logData.putAll(details);
logger.info(toJson(logData));
}
/**
* Build base log data structure
*/
private Map<String, Object> buildLogData(String level, String message, Map<String, Object> context) {
Map<String, Object> logData = new HashMap<>();
logData.put("timestamp", System.currentTimeMillis());
logData.put("level", level);
logData.put("message", message);
logData.put("thread", Thread.currentThread().getName());
if (context != null) {
logData.putAll(context);
}
return logData;
}
/**
* Convert log data to JSON string
*/
private String toJson(Map<String, Object> logData) {
try {
return objectMapper.writeValueAsString(logData);
} catch (Exception e) {
// Fallback to string representation if JSON serialization fails
return logData.toString();
}
}
/**
* Extract stack trace as array
*/
private String[] getStackTrace(Throwable throwable) {
StackTraceElement[] elements = throwable.getStackTrace();
String[] trace = new String[Math.min(elements.length, 10)]; // Limit to first 10 stack frames
for (int i = 0; i < trace.length; i++) {
trace[i] = elements[i].toString();
}
return trace;
}
}