Files
hermes-agent/agent/monitoring/events.py
Teknium 7eb8ccc315 refactor(agent/runtime): monitoring — shared OTLP plumbing in otlp_exporter, event base class, EmitterStreamer, dedupe health export
- otlp_exporter hosts SDK loading (symbol table), header/endpoint/resource helpers
  shared with gateway_health_export; export module imports them instead of
  keeping copies (_otlp_config/_resolve_headers/_install_id/_safe_resource_attributes
  unified). _KEEP_BY_KIND attribute allowlist reused for diagnostic log attrs.
- events: _MonitoringEvent base supplies to_dict; field order (wire order) unchanged.
- redaction: redact_bounded() replaces 3 inline redact+truncate try/excepts.
- EmitterStreamer base owns the shared unsubscribe/flush/shutdown for
  OTLPStreamer and GatewayDiagnosticLogStreamer.
- gateway_health: metric construction helpers, _contains_any shared with
  cron_health, dead _allowed_logger/redact_gateway_message removed (0 refs).
- Comment/docstring compaction keeping every stated invariant.
2026-09-02 13:29:47 -07:00

89 lines
2.5 KiB
Python

"""Typed gateway monitoring events.
Content-free service-health and redacted diagnostic events for the gateway
daemon — the only shapes the monitoring plane emits: no prompts, messages,
tool args/results, session history, or usage analytics. Field order is wire
order (``asdict``); never reorder.
"""
from __future__ import annotations
import time
from dataclasses import dataclass, field, asdict
from typing import Any, ClassVar, Dict, Optional
class _MonitoringEvent:
__slots__ = ()
EVENT: ClassVar[str]
def to_dict(self) -> Dict[str, Any]:
return {"event": self.EVENT, **asdict(self)}
@dataclass(slots=True)
class GatewayHealthEvent(_MonitoringEvent):
"""Content-free gateway health snapshot or lifecycle event."""
EVENT: ClassVar[str] = "gateway_health"
name: str
gateway_state: Optional[str] = None
old_state: Optional[str] = None
new_state: Optional[str] = None
exit_reason: Optional[str] = None
restart_requested: Optional[bool] = None
active_agents: int = 0
gateway_busy: bool = False
gateway_drainable: bool = False
platform_count: int = 0
fatal_platform_count: int = 0
profile: Optional[str] = None
install_id: Optional[str] = None
version: Optional[str] = None
supervision_mode: Optional[str] = None
pid: Optional[int] = None
ts_ns: int = field(default_factory=time.time_ns)
@dataclass(slots=True)
class GatewayDiagnosticEvent(_MonitoringEvent):
"""Redacted gateway diagnostic event for operator-owned observability."""
EVENT: ClassVar[str] = "gateway_diagnostic"
name: str
subsystem: str
error_class: str = "unknown"
error_code: Optional[str] = None
platform: Optional[str] = None
old_state: Optional[str] = None
new_state: Optional[str] = None
profile: Optional[str] = None
version: Optional[str] = None
severity: str = "warning"
ts_ns: int = field(default_factory=time.time_ns)
source_logger: Optional[str] = None
@dataclass(slots=True)
class CronExecutionEvent(_MonitoringEvent):
"""Content-free durable cron execution lifecycle projection."""
EVENT: ClassVar[str] = "cron_execution"
status: str
job_key: str
source: str = "unknown"
duration_ms: Optional[int] = None
delivery_outcome: Optional[str] = None
error_class: Optional[str] = None
ts_ns: int = field(default_factory=time.time_ns)
__all__ = [
"GatewayHealthEvent",
"GatewayDiagnosticEvent",
"CronExecutionEvent",
]