2026-05-29 12:26:34 +02:00
|
|
|
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."""
|
|
|
|
|
|
2026-05-29 12:56:13 +02:00
|
|
|
def __init__(
|
|
|
|
|
self, events_dir: Path, node_name: str, location_tag: str = ""
|
|
|
|
|
) -> None:
|
2026-05-29 12:26:34 +02:00
|
|
|
self._events_dir = events_dir
|
|
|
|
|
self._node_name = node_name
|
2026-05-29 12:56:13 +02:00
|
|
|
self._location_tag = location_tag
|
2026-05-29 12:26:34 +02:00
|
|
|
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)
|
2026-05-29 12:56:13 +02:00
|
|
|
merged: dict[str, Any] = {}
|
|
|
|
|
if self._location_tag:
|
|
|
|
|
merged["location_tag"] = self._location_tag
|
|
|
|
|
merged.update(payload or {})
|
2026-05-29 12:26:34 +02:00
|
|
|
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,
|
2026-05-29 12:56:13 +02:00
|
|
|
payload=merged,
|
2026-05-29 12:26:34 +02:00
|
|
|
)
|
|
|
|
|
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
|