55 lines
1.7 KiB
Python
55 lines
1.7 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import json
|
||
|
|
import re
|
||
|
|
import time
|
||
|
|
from datetime import datetime, timezone
|
||
|
|
from pathlib import Path
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from .models import EventRecord
|
||
|
|
|
||
|
|
|
||
|
|
class EventEmitter:
|
||
|
|
"""Writes atomic JSON event files to the events directory."""
|
||
|
|
|
||
|
|
def __init__(self, events_dir: Path, node_name: str) -> None:
|
||
|
|
self._events_dir = events_dir
|
||
|
|
self._node_name = node_name
|
||
|
|
self._seq = 0
|
||
|
|
events_dir.mkdir(parents=True, exist_ok=True)
|
||
|
|
|
||
|
|
def _make_id(self, event_type: str, service: str) -> str:
|
||
|
|
# Sequence suffix guarantees uniqueness even when multiple events of the
|
||
|
|
# same type are emitted within the same millisecond.
|
||
|
|
self._seq += 1
|
||
|
|
ts = int(time.time())
|
||
|
|
svc_slug = re.sub(r"[^a-z0-9]", "-", (service or "ha").lower())[:32].strip("-")
|
||
|
|
return f"evt-{self._node_name}-{ts}-{event_type}-{svc_slug}-{self._seq}"
|
||
|
|
|
||
|
|
def emit(
|
||
|
|
self,
|
||
|
|
event_type: str,
|
||
|
|
severity: str,
|
||
|
|
service: str,
|
||
|
|
message: str,
|
||
|
|
payload: dict[str, Any] | None = None,
|
||
|
|
) -> str:
|
||
|
|
event_id = self._make_id(event_type, service)
|
||
|
|
record = EventRecord(
|
||
|
|
id=event_id,
|
||
|
|
timestamp=int(time.time()),
|
||
|
|
date=datetime.now(timezone.utc).isoformat(),
|
||
|
|
type=event_type,
|
||
|
|
severity=severity,
|
||
|
|
node=self._node_name,
|
||
|
|
service=service,
|
||
|
|
message=message,
|
||
|
|
payload=payload or {},
|
||
|
|
)
|
||
|
|
path = self._events_dir / f"{event_id}.json"
|
||
|
|
tmp = path.with_suffix(".tmp")
|
||
|
|
tmp.write_text(json.dumps(record.model_dump(), indent=2))
|
||
|
|
tmp.rename(path)
|
||
|
|
return event_id
|