74 lines
2.4 KiB
Python
74 lines
2.4 KiB
Python
"""UDP JSON event publisher on ``127.0.0.1:7001`` (configurable).
|
|
|
|
Contract: one serialized ``Event`` per UDP datagram. Events larger than
|
|
``max_datagram_bytes`` are dropped and counted in
|
|
``stats.udp_events_oversize`` (no fragmentation).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import json
|
|
import logging
|
|
from typing import Any
|
|
|
|
from ..config import UdpEventsCfg
|
|
from ..core.bus import EventBus
|
|
from ..core.models import Event
|
|
from ..core.stats import Stats
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class UdpEventsPublisher:
|
|
def __init__(self, cfg: UdpEventsCfg, bus: EventBus, stats: Stats) -> None:
|
|
self._cfg = cfg
|
|
self._bus = bus
|
|
self._stats = stats
|
|
self._transport: asyncio.DatagramTransport | None = None
|
|
self._sub: asyncio.Queue[Event] | None = None
|
|
|
|
async def run(self) -> None:
|
|
loop = asyncio.get_running_loop()
|
|
# Ephemeral sender socket; we only *send* to a fixed target.
|
|
transport, _ = await loop.create_datagram_endpoint(
|
|
lambda: asyncio.DatagramProtocol(),
|
|
remote_addr=(self._cfg.host, self._cfg.port),
|
|
)
|
|
self._transport = transport
|
|
self._sub = self._bus.subscribe(maxsize=1024)
|
|
log.info("udp_events publisher -> %s:%d", self._cfg.host, self._cfg.port)
|
|
try:
|
|
while True:
|
|
ev = await self._sub.get()
|
|
self._send(ev)
|
|
except asyncio.CancelledError:
|
|
raise
|
|
finally:
|
|
if self._sub is not None:
|
|
self._bus.unsubscribe(self._sub)
|
|
self._sub = None
|
|
if self._transport is not None:
|
|
self._transport.close()
|
|
self._transport = None
|
|
|
|
def _send(self, ev: Event) -> None:
|
|
try:
|
|
blob = json.dumps(ev.to_dict(), ensure_ascii=False, separators=(",", ":")).encode("utf-8")
|
|
except Exception:
|
|
self._stats.incr("udp_events_serialize_errors")
|
|
return
|
|
if len(blob) > self._cfg.max_datagram_bytes:
|
|
self._stats.incr("udp_events_oversize")
|
|
return
|
|
try:
|
|
assert self._transport is not None
|
|
self._transport.sendto(blob)
|
|
self._stats.incr("udp_events_sent")
|
|
except Exception:
|
|
self._stats.incr("udp_events_send_errors")
|
|
log.debug("udp_events send failed", exc_info=True)
|
|
|
|
|
|
__all__ = ["UdpEventsPublisher"]
|