forked from HAL9000/cleveragents-core
e5f75c5c83
- Remove maximum cap (16) on CA_MAX_PARALLEL_WORKERS in resources.yaml - Can now be set to any positive value (32, 64, etc.) - Only minimum validation remains (must be > 0) - Remove dynamic backpressure/throttling from implementation-orchestrator - Dispatch always runs at full configured speed - Resource monitoring remains for visibility only - No automatic reduction of slots_available based on failures - Convert system-watchdog from auto-degradation to monitoring + suggestions - Renamed DEGRADATION_THRESHOLDS to HEALTH_THRESHOLDS - Removed apply_system_degradation() and check_degradation_recovery() - Changed findings to include suggestions instead of actions - Watchdog now reports issues with fix recommendations - No automatic throttling or pausing of agents The system now operates at maximum configured speed at all times, with the watchdog providing diagnostic insights when issues arise.
365 lines
11 KiB
Markdown
365 lines
11 KiB
Markdown
# Standardized Logging Format
|
|
|
|
This document defines the mandatory logging format and utilities that all agents MUST use to ensure consistent, searchable, and debuggable logs across the system.
|
|
|
|
## Log Format Specification
|
|
|
|
All agents MUST use this exact format for all log entries:
|
|
|
|
```
|
|
[AGENT:{name}][SESSION:{id}][ACTION:{action}][LEVEL:{level}] {message}
|
|
```
|
|
|
|
### Components
|
|
|
|
- **AGENT**: The agent's name (e.g., `implementation-worker`, `pr-self-reviewer`)
|
|
- **SESSION**: Unique session identifier (use first 8 chars if long)
|
|
- **ACTION**: The current operation (e.g., `clone`, `test`, `review`, `merge`)
|
|
- **LEVEL**: Log level - `INFO`, `WARNING`, `ERROR`, `DEBUG`
|
|
- **Message**: Human-readable description of what happened
|
|
|
|
### Examples
|
|
|
|
```
|
|
[AGENT:implementation-worker][SESSION:abc123de][ACTION:clone][LEVEL:INFO] Cloning repository to /tmp/impl-123
|
|
[AGENT:pr-self-reviewer][SESSION:def456gh][ACTION:review][LEVEL:WARNING] Found 3 CONTRIBUTING.md violations
|
|
[AGENT:behave-tester][SESSION:ghi789jk][ACTION:test][LEVEL:ERROR] Test execution failed: 5 scenarios failed
|
|
```
|
|
|
|
## Python Implementation
|
|
|
|
```python
|
|
import json
|
|
import time
|
|
from datetime import datetime
|
|
from typing import Optional, Dict, Any
|
|
|
|
class AgentLogger:
|
|
"""Standardized logger for all CleverAgents agents."""
|
|
|
|
def __init__(self, agent_name: str, session_id: str,
|
|
session_state_issue: Optional[int] = None,
|
|
owner: Optional[str] = None,
|
|
repo: Optional[str] = None):
|
|
"""
|
|
Initialize the logger.
|
|
|
|
Args:
|
|
agent_name: Name of the agent
|
|
session_id: Unique session identifier
|
|
session_state_issue: Forgejo issue number for persistence
|
|
owner: Repository owner (for Forgejo persistence)
|
|
repo: Repository name (for Forgejo persistence)
|
|
"""
|
|
self.agent_name = agent_name
|
|
self.session_id = session_id[:8] if len(session_id) > 8 else session_id
|
|
self.session_state_issue = session_state_issue
|
|
self.owner = owner
|
|
self.repo = repo
|
|
self.current_action = "init"
|
|
|
|
def set_action(self, action: str):
|
|
"""Set the current action context."""
|
|
self.current_action = action
|
|
|
|
def _format_log(self, level: str, message: str, action: Optional[str] = None) -> str:
|
|
"""Format a log entry according to standards."""
|
|
action_str = action or self.current_action
|
|
return f"[AGENT:{self.agent_name}][SESSION:{self.session_id}][ACTION:{action_str}][LEVEL:{level}] {message}"
|
|
|
|
def _persist_critical(self, log_line: str):
|
|
"""Persist critical logs to Forgejo."""
|
|
if self.session_state_issue and self.owner and self.repo:
|
|
try:
|
|
comment = f"[LOG] Critical Event\n```\n{log_line}\n```\n\n---\n**Automated by CleverAgents Bot**"
|
|
forgejo_create_issue_comment(self.owner, self.repo,
|
|
self.session_state_issue, comment)
|
|
except:
|
|
pass # Best effort
|
|
|
|
def info(self, message: str, action: Optional[str] = None):
|
|
"""Log an informational message."""
|
|
log_line = self._format_log("INFO", message, action)
|
|
print(log_line)
|
|
return log_line
|
|
|
|
def warning(self, message: str, action: Optional[str] = None):
|
|
"""Log a warning message."""
|
|
log_line = self._format_log("WARNING", message, action)
|
|
print(log_line)
|
|
return log_line
|
|
|
|
def error(self, message: str, action: Optional[str] = None, persist: bool = True):
|
|
"""Log an error message."""
|
|
log_line = self._format_log("ERROR", message, action)
|
|
print(log_line)
|
|
|
|
if persist:
|
|
self._persist_critical(log_line)
|
|
|
|
return log_line
|
|
|
|
def debug(self, message: str, action: Optional[str] = None):
|
|
"""Log a debug message."""
|
|
log_line = self._format_log("DEBUG", message, action)
|
|
print(log_line)
|
|
return log_line
|
|
|
|
def structured(self, action: str, data: Dict[str, Any], level: str = "INFO"):
|
|
"""Log structured data for automated analysis."""
|
|
entry = {
|
|
"timestamp": datetime.utcnow().isoformat() + "Z",
|
|
"agent": self.agent_name,
|
|
"session": self.session_id,
|
|
"action": action,
|
|
"level": level,
|
|
"data": data
|
|
}
|
|
|
|
# Standard format for visibility
|
|
message = f"Structured data: {action}"
|
|
self._format_log(level, message, action)
|
|
|
|
# JSON format for parsing
|
|
print(f"[STRUCTURED] {json.dumps(entry)}")
|
|
|
|
# Persist if critical
|
|
if level in ["ERROR", "CRITICAL"]:
|
|
self._persist_critical(f"[STRUCTURED] {json.dumps(entry, indent=2)}")
|
|
|
|
return entry
|
|
```
|
|
|
|
## Usage Examples
|
|
|
|
### Basic Usage
|
|
|
|
```python
|
|
# Initialize at agent start
|
|
logger = AgentLogger(
|
|
agent_name="implementation-worker",
|
|
session_id=session_id,
|
|
session_state_issue=SESSION_STATE_ISSUE,
|
|
owner=owner,
|
|
repo=repo
|
|
)
|
|
|
|
# Log with current action context
|
|
logger.set_action("setup")
|
|
logger.info("Validating credentials")
|
|
logger.info("Loading reference materials")
|
|
|
|
# Log with specific action
|
|
logger.info("Repository cloned successfully", action="clone")
|
|
|
|
# Log warnings
|
|
logger.warning("Retry attempt 2 of 3", action="push")
|
|
|
|
# Log errors (automatically persisted)
|
|
logger.error("Failed to merge PR: CI checks failing", action="merge")
|
|
|
|
# Log debug info
|
|
logger.debug(f"Queue depth: {len(queue)}", action="dispatch")
|
|
```
|
|
|
|
### Structured Logging
|
|
|
|
```python
|
|
# Log complex data for analysis
|
|
logger.structured("worker_dispatched", {
|
|
"issue_number": 123,
|
|
"worker_session": worker_session_id,
|
|
"priority": "high",
|
|
"attempt": 1
|
|
})
|
|
|
|
# Log performance metrics
|
|
logger.structured("operation_complete", {
|
|
"operation": "subtask_implementation",
|
|
"duration_seconds": 145.3,
|
|
"lines_changed": 230,
|
|
"files_modified": 5
|
|
}, level="INFO")
|
|
|
|
# Log errors with context
|
|
logger.structured("merge_failed", {
|
|
"pr_number": 456,
|
|
"reason": "insufficient_approvals",
|
|
"current_approvals": 1,
|
|
"required_approvals": 2
|
|
}, level="ERROR")
|
|
```
|
|
|
|
### Integration with Error Handling
|
|
|
|
```python
|
|
from shared.error_handling import safe_operation_with_recovery
|
|
from shared.logging import AgentLogger
|
|
|
|
logger = AgentLogger("implementation-worker", session_id)
|
|
|
|
def clone_with_logging(repo_url, target_dir):
|
|
logger.set_action("clone")
|
|
|
|
def do_clone():
|
|
logger.info(f"Starting clone of {repo_url}")
|
|
result = git_clone(repo_url, target_dir)
|
|
logger.info("Clone completed successfully")
|
|
return result
|
|
|
|
def recovery(error_type, error):
|
|
logger.warning(f"Recovery attempt for {error_type}: {error}")
|
|
# Recovery logic...
|
|
|
|
try:
|
|
return safe_operation_with_recovery(
|
|
"clone repository",
|
|
do_clone,
|
|
recovery
|
|
)
|
|
except OperationFailedError as e:
|
|
logger.error(f"Clone failed after all attempts: {e}")
|
|
raise
|
|
```
|
|
|
|
### Context Manager for Actions
|
|
|
|
```python
|
|
class LoggedAction:
|
|
"""Context manager for action-scoped logging."""
|
|
|
|
def __init__(self, logger: AgentLogger, action: str):
|
|
self.logger = logger
|
|
self.action = action
|
|
self.previous_action = None
|
|
|
|
def __enter__(self):
|
|
self.previous_action = self.logger.current_action
|
|
self.logger.set_action(self.action)
|
|
self.logger.info(f"Starting {self.action}")
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
if exc_type:
|
|
self.logger.error(f"{self.action} failed: {exc_val}")
|
|
else:
|
|
self.logger.info(f"{self.action} completed")
|
|
|
|
# Restore previous action
|
|
self.logger.set_action(self.previous_action)
|
|
|
|
# Don't suppress exceptions
|
|
return False
|
|
|
|
# Usage
|
|
with LoggedAction(logger, "test_execution"):
|
|
run_tests() # Logs automatically scoped to this action
|
|
```
|
|
|
|
## Log Aggregation Patterns
|
|
|
|
### Health Signals
|
|
|
|
```python
|
|
def log_health_signal(logger: AgentLogger, metrics: Dict[str, Any]):
|
|
"""Log standardized health signal."""
|
|
|
|
logger.structured("health_signal", {
|
|
"cycle": metrics.get("cycle", 0),
|
|
"active_workers": metrics.get("active_workers", 0),
|
|
"queue_depth": metrics.get("queue_depth", 0),
|
|
"failure_rate": metrics.get("failure_rate", 0),
|
|
"status": "active"
|
|
}, level="INFO")
|
|
```
|
|
|
|
### Operation Summaries
|
|
|
|
```python
|
|
def log_operation_summary(logger: AgentLogger, operation: str,
|
|
start_time: float, success: bool,
|
|
details: Optional[Dict] = None):
|
|
"""Log operation completion with timing."""
|
|
|
|
duration = time.time() - start_time
|
|
|
|
data = {
|
|
"operation": operation,
|
|
"duration_seconds": round(duration, 2),
|
|
"success": success
|
|
}
|
|
|
|
if details:
|
|
data.update(details)
|
|
|
|
level = "INFO" if success else "ERROR"
|
|
logger.structured(f"{operation}_complete", data, level=level)
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Always initialize logger at agent start** - Don't create multiple instances
|
|
2. **Set action context** - Use `set_action()` for logical operation groups
|
|
3. **Be consistent with action names** - Use standard names across agents
|
|
4. **Log state changes** - Entry/exit of major operations
|
|
5. **Include relevant context** - But avoid logging sensitive data
|
|
6. **Use appropriate levels** - INFO for normal, WARNING for recoverable, ERROR for failures
|
|
7. **Structure complex data** - Use `structured()` for analysis-friendly logs
|
|
|
|
## Standard Action Names
|
|
|
|
Use these standardized action names for consistency:
|
|
|
|
- **init** - Agent initialization
|
|
- **setup** - Environment setup
|
|
- **clone** - Repository cloning
|
|
- **implement** - Code implementation
|
|
- **test** - Test execution
|
|
- **review** - Code review
|
|
- **merge** - PR merging
|
|
- **dispatch** - Worker dispatch
|
|
- **monitor** - Health monitoring
|
|
- **cleanup** - Resource cleanup
|
|
|
|
## Anti-Patterns to Avoid
|
|
|
|
```python
|
|
# ❌ WRONG - Inconsistent format
|
|
print(f"Starting work on issue {issue_number}")
|
|
|
|
# ✓ CORRECT - Use logger
|
|
logger.info(f"Starting work on issue {issue_number}")
|
|
|
|
# ❌ WRONG - No action context
|
|
logger.info("Failed") # Failed what?
|
|
|
|
# ✓ CORRECT - Clear action context
|
|
logger.error("Repository clone failed", action="clone")
|
|
|
|
# ❌ WRONG - Logging credentials
|
|
logger.info(f"Using PAT: {pat}")
|
|
|
|
# ✓ CORRECT - Log presence, not value
|
|
logger.info("Forgejo PAT validated")
|
|
|
|
# ❌ WRONG - Unstructured complex data
|
|
logger.info(f"Stats: workers={w}, queue={q}, failed={f}")
|
|
|
|
# ✓ CORRECT - Structured for parsing
|
|
logger.structured("pool_stats", {
|
|
"workers": w,
|
|
"queue_depth": q,
|
|
"failures": f
|
|
})
|
|
```
|
|
|
|
## Integration Requirements
|
|
|
|
All agents MUST:
|
|
|
|
1. Import and initialize AgentLogger at startup
|
|
2. Use ONLY the logger for output (no raw print statements)
|
|
3. Set appropriate action context
|
|
4. Log all state transitions
|
|
5. Persist ERROR level logs to Forgejo
|
|
6. Use structured logging for metrics and complex data |