fix(trigger): DPLAN-0112 Option C — strip dispatch from error_logged, route through Medic v2
error_logged.py handler stripped to monitor-only (log event, no email/dispatch). Centralized watcher (watchers/log_watcher.py) now calls registry_report() + fires error_detected for ERROR-level lines, routing through full Medic v2 pipeline (count threshold, circuit breaker, fingerprint backoff). Falls back to error_logged (monitor-only) if registry unavailable. 370 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
64c74a5a2f
commit
0c290a95d1
@@ -270,70 +270,13 @@ def handle_error_logged(
|
||||
# Resolve source_module from either parameter name
|
||||
effective_module = source_module or module_name or "unknown"
|
||||
|
||||
# --- Medic gating (FPLAN-0371 Phase 1) ---
|
||||
|
||||
# Gate 1: Global medic toggle
|
||||
if not _is_medic_enabled():
|
||||
_log_suppression("Medic OFF", branch, effective_module, message)
|
||||
return
|
||||
|
||||
# Gate 2: Per-branch mute
|
||||
if _is_branch_muted(branch):
|
||||
_log_suppression("Branch muted", branch, effective_module, message)
|
||||
return
|
||||
|
||||
# Gate 3: Convert branch name to email format
|
||||
recipient = f"@{branch.lower()}"
|
||||
|
||||
# devpulse is protected from auto-triggering
|
||||
if recipient == '@devpulse':
|
||||
return
|
||||
|
||||
# Gate 4: Validate target branch exists in registry
|
||||
registered_emails = _get_registered_emails()
|
||||
if recipient not in registered_emails:
|
||||
_log_suppression("Unknown branch skipped", branch, effective_module, message)
|
||||
return
|
||||
|
||||
# Gate 5: Rate limiting (3 dispatches per 10 minutes per branch)
|
||||
if _is_rate_limited(recipient):
|
||||
_log_suppression("Rate limited", branch, effective_module, message)
|
||||
return
|
||||
|
||||
# --- Dispatch ---
|
||||
|
||||
try:
|
||||
from aipass.ai_mail.apps.modules.email import deliver_email_to_branch
|
||||
except ImportError:
|
||||
return
|
||||
|
||||
effective_timestamp = timestamp or datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
effective_log_file = log_file or "unknown"
|
||||
|
||||
email_subject = f"[ERROR] {effective_module} - investigation needed"
|
||||
|
||||
notification_message = _build_notification_message(
|
||||
error_hash=error_hash,
|
||||
source_module=effective_module,
|
||||
message=message,
|
||||
timestamp=effective_timestamp,
|
||||
log_file=effective_log_file
|
||||
)
|
||||
|
||||
email_data = {
|
||||
"from": "@trigger",
|
||||
"from_name": "TRIGGER",
|
||||
"to": recipient,
|
||||
"subject": email_subject,
|
||||
"message": f"⚡ DISPATCH TASK - READ THIS FIRST ⚡\n\n{notification_message}",
|
||||
"timestamp": effective_timestamp,
|
||||
}
|
||||
deliver_email_to_branch(recipient, email_data)
|
||||
|
||||
# Record dispatch for rate limiting
|
||||
_record_dispatch(recipient)
|
||||
|
||||
json_handler.log_operation("error_logged_event", {"success": True})
|
||||
# Monitor-only: log the event for tracking, no dispatch.
|
||||
# All dispatch now goes through error_detected.py (Medic v2).
|
||||
json_handler.log_operation("error_logged_event", {
|
||||
"branch": branch,
|
||||
"module": effective_module,
|
||||
"error_hash": error_hash,
|
||||
})
|
||||
|
||||
except Exception as exc:
|
||||
_log_warning(f"handle_error_logged failed: {exc}")
|
||||
|
||||
@@ -13,9 +13,9 @@ Watches system_logs/ for log file changes.
|
||||
Detects ERROR/WARNING/INFO entries and fires appropriate events.
|
||||
|
||||
Events fired:
|
||||
- error_logged: When ERROR level log detected
|
||||
- error_detected: When ERROR level log detected (Medic v2 pipeline via registry_report)
|
||||
- error_logged: Monitoring-only event (no dispatch)
|
||||
- warning_logged: When WARNING level log detected
|
||||
- log_entry: All log entries (for monitoring systems)
|
||||
|
||||
Architecture:
|
||||
- Trigger OWNS all file watching (filesystem events)
|
||||
@@ -257,7 +257,30 @@ class LogFileWatcher(WatchdogFileSystemEventHandler if WATCHDOG_AVAILABLE else o
|
||||
}
|
||||
|
||||
if level == 'error':
|
||||
trigger.fire('error_logged', **event_data)
|
||||
# Route through Medic v2: registry_report() for dedup/count, then error_detected
|
||||
try:
|
||||
from aipass.trigger.apps.handlers.error_registry import report as registry_report
|
||||
result = registry_report(
|
||||
error_type='ERROR',
|
||||
message=message,
|
||||
component=branch,
|
||||
log_path=log_file,
|
||||
severity='medium'
|
||||
)
|
||||
error_count = result.get('count', 1)
|
||||
trigger.fire('error_detected',
|
||||
branch=branch, module=module_name, message=message,
|
||||
log_path=log_file, error_hash=result.get('id', error_hash),
|
||||
timestamp=timestamp,
|
||||
fingerprint=result.get('fingerprint', ''),
|
||||
registry_id=result.get('id', ''),
|
||||
first_seen=result.get('first_seen', ''),
|
||||
last_seen=result.get('last_seen', ''),
|
||||
count=error_count,
|
||||
)
|
||||
except Exception:
|
||||
# Registry unavailable — fire error_logged as monitoring-only fallback
|
||||
trigger.fire('error_logged', **event_data)
|
||||
json_handler.log_operation("system_log_event", {"level": level, "module": module_name})
|
||||
elif level == 'warning':
|
||||
trigger.fire('warning_logged', **event_data)
|
||||
|
||||
Reference in New Issue
Block a user