Logging Architecture
Technical architecture documentation for the Brobot Unified Logging System.
System Overview
The Unified Logging System consolidates three previously separate logging mechanisms into a cohesive architecture with clear separation of concerns and extensible design.
┌─────────────────────────────────────────────────────────────────┐
│ BrobotLogger │
│ (Unified Facade - Single Entry Point for All Logging) │
├─────────────────────────────────────────────────────────────────┤
│ LoggingContext │
│ (Thread-local: Session, State, Action, Performance) │
├─────────────────────────────────────────────────────────────────┤
│ MessageRouter │
│ (Routes to appropriate handlers based on type/config) │
├─────────────┬─────────────┬─────────────┬──────────────────────┤
│ Console │ Structured │ SLF4J │ Metrics │
│ Handler │ Handler │ Handler │ Handler │
├─────────────┴─────────────┴─────────────┴──────────────────────┤
│ Sinks/Appenders │
│ (Console, File, Database, Cloud, Message Queue, etc.) │
└─────────────────────────────────────────────────────────────────┘
Core Components
BrobotLogger
The main facade that provides the unified API:
@Component
public class BrobotLogger {
// Simple logging methods
void action(String action, StateObject target);
void transition(State from, State to);
void observation(String observation);
void error(String message, Throwable cause);
// Advanced features
LogBuilder log();
AutoCloseable session(String sessionId);
AutoCloseable operation(String operationName);
Timer startTimer(String timerName);
}
Responsibilities:
- Provide simple, intuitive API for common logging needs
- Create LogEvent instances with proper context
- Delegate routing to MessageRouter
- Manage configuration settings
LoggingContext
Thread-local storage for contextual information:
@Component
public class LoggingContext {
private static final ThreadLocal<Context> contextHolder;
static class Context {
String sessionId;
State currentState;
String currentAction;
Deque<String> operationStack;
Map<String, Object> metadata;
}
}
Features:
- Thread isolation for concurrent executions
- Hierarchical operation tracking
- Context snapshots for async operations
- Automatic cleanup to prevent memory leaks
LogEvent
Immutable data model for log entries:
public class LogEvent {
enum Type { ACTION, TRANSITION, OBSERVATION, PERFORMANCE, ERROR }
enum Level { DEBUG, INFO, WARNING, ERROR }
// Core fields
private final Type type;
private final Level level;
private final String message;
private final long timestamp;
// Contextual data
private final String sessionId;
private final String stateId;
private final Map<String, Object> metadata;
}
Design Principles:
- Immutable for thread safety
- Builder pattern for flexible construction
- Rich metadata support
- Self-contained (no external dependencies)
MessageRouter
Routes LogEvent instances to appropriate handlers:
@Component
public class MessageRouter {
void route(LogEvent event) {
routeToSlf4j(event);
routeToConsole(event);
if (structuredLoggingEnabled) {
routeToActionLogger(event);
}
}
}
Routing Rules:
- All events → SLF4J (with appropriate levels)
- Events → ConsoleReporter (based on output level)
- Events → ActionLogger (if structured logging enabled)
- Future: Custom handlers via SPI
LogBuilder
Fluent API for complex logging scenarios:
public class LogBuilder {
LogBuilder level(Level level);
LogBuilder action(String action);
LogBuilder target(StateObject object);
LogBuilder result(ActionResult result);
LogBuilder screenshot(String path);
LogBuilder metadata(String key, Object value);
LogBuilder performance(String metric, long value);
void log();
}
Design Patterns
1. Facade Pattern
BrobotLogger acts as a simplified interface to the complex logging subsystem.
2. Builder Pattern
LogEvent and LogBuilder use builders for flexible object construction.
3. Strategy Pattern
MessageRouter implements different routing strategies based on configuration.
4. Thread-Local Pattern
LoggingContext uses ThreadLocal for thread-safe context management.
5. Template Method Pattern
Handlers follow a template for processing LogEvents.
Integration Points
SLF4J Integration
private void routeToSlf4j(LogEvent event) {
String message = formatSlf4jMessage(event);
switch (event.getLevel()) {
case DEBUG: slf4jLogger.debug(message); break;
case INFO: slf4jLogger.info(message); break;
case WARNING: slf4jLogger.warn(message); break;
case ERROR: slf4jLogger.error(message, event.getError()); break;
}
}
ConsoleReporter Integration
private void routeToConsole(LogEvent event) {
OutputLevel level = determineConsoleLevel(event);
if (ConsoleReporter.minReportingLevel(level)) {
String output = event.toFormattedString();
String[] colors = determineColors(event);
ConsoleReporter.println(output, colors);
}
}
ActionLogger Integration
private void routeToActionLogger(LogEvent event) {
switch (event.getType()) {
case ACTION:
actionLogger.logAction(sessionId, toActionResult(event),
toObjectCollection(event));
break;
case TRANSITION:
actionLogger.logStateTransition(sessionId,
extractStates(event));
break;
// ... other types
}
}
Extension Points
Custom Log Sinks
Implement the LogSink interface for custom persistence:
@FunctionalInterface
public interface LogSink {
void save(LogData logData);
}
// Example implementation
public class ElasticsearchLogSink implements LogSink {
@Override
public void save(LogData logData) {
// Send to Elasticsearch
}
}
Custom Handlers
Add new handlers to MessageRouter:
public interface LogHandler {
void handle(LogEvent event);
boolean canHandle(LogEvent event);
}
@Component
public class MetricsHandler implements LogHandler {
@Override
public void handle(LogEvent event) {
if (event.getType() == PERFORMANCE) {
// Export to Prometheus/Grafana
}
}
}
Event Processors
Pre-process events before routing:
public interface EventProcessor {
LogEvent process(LogEvent event);
}
@Component
public class SanitizationProcessor implements EventProcessor {
@Override
public LogEvent process(LogEvent event) {
// Remove sensitive data from metadata
return sanitize(event);
}
}
Performance Characteristics
Memory Usage
- ThreadLocal Context: ~1KB per thread
- LogEvent: ~500 bytes average (depends on metadata)
- Buffer Size: Configurable, default 1000 events
Processing Time
- Simple Log: < 100 microseconds
- With Routing: < 500 microseconds
- With Persistence: Depends on sink (async recommended)
Optimization Strategies
-
Lazy Evaluation
logger.log()
.message(() -> expensiveComputation())
.log(); -
Async Processing
@Async
public void handleStructuredLog(LogEvent event) {
// Process in background thread
} -
Batch Operations
public class BatchingLogSink implements LogSink {
private final BlockingQueue<LogData> queue;
// Batch write every N events or T seconds
}
Configuration Architecture
Hierarchical Configuration
brobot:
logging:
defaults: # Global defaults
level: INFO
format: json
console: # Console-specific
inherit: defaults
level: HIGH
structured: # Structured logging
inherit: defaults
sinks:
- file
- elasticsearch
Dynamic Reconfiguration
@Component
public class LoggingConfigManager {
@EventListener(ConfigChangeEvent.class)
public void onConfigChange(ConfigChangeEvent event) {
// Update logging configuration without restart
reconfigureLoggers(event.getNewConfig());
}
}
Security Considerations
Sensitive Data Handling
@Component
public class SensitiveDataFilter {
private final Set<String> sensitiveKeys = Set.of(
"password", "token", "apiKey", "ssn"
);
public LogEvent filter(LogEvent event) {
Map<String, Object> filtered = new HashMap<>();
event.getMetadata().forEach((k, v) -> {
if (sensitiveKeys.contains(k.toLowerCase())) {
filtered.put(k, "***REDACTED***");
} else {
filtered.put(k, v);
}
});
return event.withMetadata(filtered);
}
}
Access Control
@Component
public class LogAccessController {
@PreAuthorize("hasRole('ADMIN')")
public List<LogEvent> getSecurityLogs() {
// Only admins can access security logs
}
}
Testing Strategy
Unit Tests
- Test each component in isolation
- Mock dependencies
- Verify routing logic
Integration Tests
- Test full logging pipeline
- Verify handler interactions
- Check configuration loading
Performance Tests
- Measure logging overhead
- Test under high concurrency
- Validate memory usage
Migration Architecture
Compatibility Layer
@Component
public class LegacyLoggerAdapter {
private final BrobotLogger logger;
// Adapts old ActionLogger calls
public void logAction(String sessionId, ActionResult result) {
try (var session = logger.session(sessionId)) {
logger.action("LEGACY", null, result);
}
}
}
Gradual Migration
- Phase 1: Deploy unified logger alongside existing
- Phase 2: Route existing loggers through unified system
- Phase 3: Migrate code to use unified API
- Phase 4: Remove legacy logging code
Future Architecture
Planned Enhancements
-
Distributed Tracing
- OpenTelemetry integration
- Trace ID propagation
- Span management
-
Real-time Streaming
- WebSocket log streaming
- Server-sent events
- Log tailing API
-
Machine Learning
- Anomaly detection
- Pattern recognition
- Predictive alerts
-
Cloud Native
- Kubernetes operator
- Service mesh integration
- Multi-cluster support