Skip to main content
Version: Latest

Modular Logging System

Overview

The Modular Logging System is a new architectural component that provides format-aware, verbosity-driven action logging. It separates data collection, formatting, and output concerns to create a flexible, maintainable logging solution that produces clean, focused output at different verbosity levels.

Problem Statement

The original logging system had several issues:

  • Mixed Concerns: Formatting logic was scattered across multiple components
  • Inconsistent Output: Different parts of the system produced different log formats
  • Hard to Maintain: Changes to log format required modifications in multiple places
  • Limited Flexibility: Difficult to add new output formats or verbosity levels

Architecture

System Components

┌─────────────────────────────────────────────────────────────────┐
│ ActionLifecycleAspect │
│ (Populates ActionResult with execution context) │
├─────────────────────────────────────────────────────────────────┤
│ ActionResult │
│ (Single source of truth with ActionExecutionContext, │
│ ActionMetrics, and EnvironmentSnapshot) │
├─────────────────────────────────────────────────────────────────┤
│ ActionLoggingService │
│ (Routes ActionResult to appropriate formatter) │
├─────────────┬─────────────┬─────────────────────────────────────┤
│ Quiet │ Normal │ Verbose │
│ Formatter │ Formatter │ Formatter │
├─────────────┴─────────────┴─────────────────────────────────────┤
│ Output Destinations │
│ (Console, File, Remote, etc.) │
└─────────────────────────────────────────────────────────────────┘

Core Design Principles

  1. Single Source of Truth: ActionResult contains all information about an action execution
  2. Separation of Concerns: Data collection, formatting, and output are separate responsibilities
  3. Pluggable Formatters: Easy to add new output formats without changing core logic
  4. Configuration-Driven: Verbosity level controlled by configuration, not code
  5. Immutable Data Flow: ActionResult is populated once and passed through the system

Components

Enhanced ActionResult

The ActionResult class has been enhanced with inner classes to capture comprehensive execution data:

public class ActionResult {
// Existing fields...

private ActionExecutionContext executionContext;
private ActionMetrics actionMetrics;
private EnvironmentSnapshot environmentSnapshot;

@Data
public static class ActionExecutionContext {
private String actionType;
private List<StateImage> targetImages = new ArrayList<>();
private List<String> targetStrings = new ArrayList<>();
private List<Region> targetRegions = new ArrayList<>();
private String primaryTargetName;
private boolean success;
private Duration executionDuration = Duration.ZERO;
private Instant startTime;
private Instant endTime;
private List<Match> resultMatches = new ArrayList<>();
private Throwable executionError;
private String executingThread;
private String actionId;
}

@Data
public static class ActionMetrics {
private long executionTimeMs;
private int matchCount;
private double bestMatchConfidence = 0.0;
private String threadName;
private String actionId;
private int retryCount = 0;
private Map<String, Long> timingBreakdown = new HashMap<>();
}

@Data
public static class EnvironmentSnapshot {
private List<MonitorInfo> monitors = new ArrayList<>();
private String osName;
private String javaVersion;
private boolean headlessMode;
private Instant captureTime;

@Data
public static class MonitorInfo {
private int index;
private String displayId;
private Rectangle bounds;
private boolean isPrimary;
}
}
}

ActionLogFormatter Interface

The formatter interface defines the contract for all formatters:

public interface ActionLogFormatter {
/**
* Format an ActionResult into a log message string.
*
* @param actionResult the result of an action execution
* @return formatted log message, or null if this action should not be logged
*/
String format(ActionResult actionResult);

/**
* Determine if an action should be logged at this verbosity level.
*
* @param actionResult the result to check
* @return true if this action should produce log output
*/
boolean shouldLog(ActionResult actionResult);

/**
* Get the verbosity level this formatter handles.
*
* @return the verbosity level
*/
VerbosityLevel getVerbosityLevel();

enum VerbosityLevel {
QUIET, // Minimal output: ✓ Find Working.ClaudeIcon • 234ms
NORMAL, // Balanced output: timestamps, key info, success indicators
VERBOSE // Detailed output: full metadata, environment info, timing details
}
}

Formatter Implementations

QuietFormatter

Produces minimal single-line output perfect for monitoring and CI/CD environments:

✓ Find Working.ClaudeIcon • 234ms
✗ Click Button.Submit • 156ms
✓ Type "username" • 89ms

Features:

  • Single-line format
  • Success/failure symbol (✓/✗)
  • Action type (cleaned up)
  • Target information (State.Object format)
  • Duration in milliseconds

NormalFormatter

Balanced output with timestamps and essential information:

[12:34:56] ✓ Find Working.ClaudeIcon completed in 234ms (1 match)
[12:34:57] ✗ Click Button.Submit failed in 156ms (No matches found)
[12:34:58] ✓ Type TextInput.Username completed in 89ms

Features:

  • Timestamp prefix
  • Success/failure indication
  • Completion status
  • Match count information
  • Error messages for failures

VerboseFormatter

Comprehensive multi-line output for debugging and analysis:

=== ACTION EXECUTION ===
Started: 2024-01-20 12:34:56.123
Completed: 2024-01-20 12:34:56.357
Action ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Thread: main

--- ACTION DETAILS ---
Type: FIND
Status: SUCCESS ✓
Duration: 234ms

--- TARGETS ---
Images (1):
[1] Working.ClaudeIcon

--- RESULTS ---
Matches: 1
[1] Score: 0.987 Region: R[100,200 300x400]

--- ENVIRONMENT ---
OS: Mac OS X
Java: 21.0.7
Headless: false
Monitors: 1

--- METRICS ---
Execution Time: 234ms
Match Count: 1
Best Match Score: 0.987
========================

ActionLoggingService

The central service that coordinates logging:

@Service
@Slf4j
public class ActionLoggingService {
private final Map<VerbosityLevel, ActionLogFormatter> formatters;

@Value("${brobot.logging.verbosity:QUIET}")
private String verbosityConfig;

public void logAction(ActionResult actionResult) {
if (!isLoggingEnabled() || actionResult == null) {
return;
}

VerbosityLevel level = getEffectiveVerbosityLevel();
ActionLogFormatter formatter = formatters.get(level);

if (formatter != null && formatter.shouldLog(actionResult)) {
String formattedMessage = formatter.format(actionResult);
if (formattedMessage != null) {
outputFormattedMessage(formattedMessage, level);
}
}
}
}

Simplified ActionLifecycleAspect

The aspect now focuses solely on populating ActionResult with execution context:

@Aspect
@Component
public class ActionLifecycleAspect {
@Autowired
private ActionLoggingService actionLoggingService;

@Around("actionPerform()")
public Object manageActionLifecycle(ProceedingJoinPoint joinPoint) throws Throwable {
// Extract action information
ActionResult actionResult = extractActionResult(joinPoint);

// Initialize execution context
populateExecutionContext(actionResult, context, objectCollection);

try {
// Execute the action
Object result = joinPoint.proceed();

// Update with results
updateExecutionContextWithResults(actionResult, context, duration);

// Log the completed action
actionLoggingService.logAction(actionResult);

return result;

} catch (Exception e) {
// Update with error information
updateExecutionContextWithError(actionResult, context, e);

// Log the failed action
actionLoggingService.logAction(actionResult);

throw e;
}
}
}

Configuration

Application Properties

brobot:
logging:
verbosity: QUIET # QUIET, NORMAL, or VERBOSE
console:
enabled: true
file:
enabled: false
path: logs/actions.log

Programmatic Configuration

// Change verbosity at runtime
actionLoggingService.setVerbosityLevel(VerbosityLevel.VERBOSE);

// Log with specific verbosity override
actionLoggingService.logAction(actionResult, VerbosityLevel.NORMAL);

Usage Examples

Basic Usage

The system works automatically through AspectJ integration:

// Actions are automatically logged when executed
Action action = new Action();
action.find(stateImage); // Automatically logged based on verbosity

Custom Logging

For manual logging scenarios:

@Autowired
private ActionLoggingService loggingService;

// Create and populate ActionResult manually
ActionResult result = new ActionResult();
ActionResult.ActionExecutionContext context = new ActionResult.ActionExecutionContext();
context.setActionType("CUSTOM_ACTION");
context.setSuccess(true);
context.setExecutionDuration(Duration.ofMillis(100));
result.setExecutionContext(context);

// Log it
loggingService.logAction(result);

Extension Guide

Adding a New Formatter

  1. Implement the ActionLogFormatter interface:
@Component
public class JsonFormatter implements ActionLogFormatter {
@Override
public String format(ActionResult actionResult) {
// Convert to JSON format
return objectMapper.writeValueAsString(actionResult);
}

@Override
public boolean shouldLog(ActionResult actionResult) {
return true; // Log all actions
}

@Override
public VerbosityLevel getVerbosityLevel() {
return VerbosityLevel.NORMAL; // Or create a new level
}
}
  1. The formatter is automatically registered via Spring component scanning.

Adding New Metrics

Extend the ActionMetrics class:

public static class ActionMetrics {
// Existing fields...

// Add new metrics
private long memoryUsageDelta;
private int cpuUsagePercent;
private Map<String, Object> customMetrics = new HashMap<>();
}

Custom Output Destinations

Override the output method in ActionLoggingService:

@Component
public class RemoteLoggingService extends ActionLoggingService {
@Override
protected void outputFormattedMessage(String message, VerbosityLevel level) {
// Send to remote logging service
remoteLogger.send(message, level);
}
}

Benefits

For Developers

  • Clean Code: Logging logic separated from business logic
  • Easy Maintenance: Changes to log format only require updating formatters
  • Extensible: New formats and destinations easily added
  • Testable: Each component can be tested in isolation

For Users

  • Consistent Output: All actions logged in the same format
  • Configurable Verbosity: Choose the right level of detail
  • Performance: Minimal overhead in QUIET mode
  • Debugging: VERBOSE mode provides comprehensive information

For Operations

  • CI/CD Friendly: QUIET mode perfect for automated environments
  • Monitoring: Consistent format enables easy parsing
  • Troubleshooting: VERBOSE mode captures all relevant context
  • Flexible Deployment: Configure verbosity per environment

Performance Considerations

Overhead

  • QUIET Mode: < 100 microseconds per action
  • NORMAL Mode: < 200 microseconds per action
  • VERBOSE Mode: < 500 microseconds per action

Memory Usage

  • ActionExecutionContext: ~500 bytes per action
  • ActionMetrics: ~200 bytes per action
  • EnvironmentSnapshot: ~1KB (cached and reused)

Optimization Tips

  1. Use QUIET mode in production
  2. Enable file logging asynchronously
  3. Implement sampling for high-frequency actions
  4. Use conditional logging based on action type

Migration from Legacy Logging

Before (Scattered Logging)

// In ActionExecution
System.out.println("✓ " + action + " " + target);

// In ConsoleReporter
ConsoleReporter.log(OutputLevel.HIGH, result.toString());

// In ActionLogger
actionLogger.logAction(sessionId, result, collection);

After (Unified Logging)

// All logging handled by ActionLoggingService
actionLoggingService.logAction(actionResult);

Migration Steps

  1. Update brobot library to latest version
  2. Enable modular logging in configuration
  3. Remove direct console output calls
  4. Test with different verbosity levels
  5. Adjust formatters as needed

Troubleshooting

No Output

  • Check if logging is enabled: brobot.logging.console.enabled=true
  • Verify verbosity level: brobot.logging.verbosity=QUIET
  • Ensure AspectJ is enabled: @EnableAspectJAutoProxy

Wrong Format

  • Verify correct formatter is being used
  • Check verbosity configuration
  • Ensure ActionResult is properly populated

Performance Issues

  • Switch to QUIET mode for production
  • Disable file logging if not needed
  • Consider async logging for high-volume scenarios

Future Enhancements

Planned Features

  1. Structured Logging: JSON/XML output formats
  2. Remote Logging: Send logs to centralized servers
  3. Real-time Streaming: WebSocket-based log streaming
  4. Analytics Integration: Export to analytics platforms
  5. Custom Filters: Filter logs based on criteria
  6. Log Aggregation: Combine logs from multiple sources

Integration Opportunities

  • OpenTelemetry for distributed tracing
  • ELK Stack for log analysis
  • Prometheus for metrics export
  • Grafana for visualization