This commit is contained in:
Jonas Bengtsson
2026-10-08 07:57:21 +02:00
parent 406fdc61ac
commit 1478db73a9
14 changed files with 972 additions and 50 deletions
+67 -9
View File
@@ -554,10 +554,70 @@ def _cmd_probe(
return _cmd_read(config_path, server, host, (), source, as_json=False)
def _build_reporters(config: Config):
"""Assemble the reporter fan-out from the config (spec §9).
Console is always on in the foreground ``run``; JSONL, the text anomaly
log and the Windows Event Log follow their config sections; the webhook
only when ``report.http`` is configured. Returns ``(fan_out, close)``
where ``close`` must be called exactly once on shutdown.
"""
from opcda_diagnozer.report.base import FanOut
from opcda_diagnozer.report.jsonl import JsonlReporter, config_digest
from opcda_diagnozer.report.textlog import TextLogReporter
sinks = []
def console_reporter(event: DiagEvent) -> None:
# Console = the human view (spec §9): clear text, status NAME (0xNN)
line = f"{event.time_utc.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]} UTC {event.severity.value:<8} {event.kind.value}"
extras = " ".join(f"{k}={v}" for k, v in event.detail.items())
print(f"{line} {extras}" if extras else line, flush=True)
sinks.append(console_reporter)
sinks.append(
JsonlReporter(
config.report.jsonl.dir,
max_mb=config.report.jsonl.max_mb,
backups=config.report.jsonl.backups,
digest=config_digest(config),
)
)
sinks.append(
TextLogReporter(
config.report.textlog.dir,
max_mb=config.report.textlog.max_mb,
backups=config.report.textlog.backups,
)
)
if sys.platform == "win32":
from opcda_diagnozer.report.eventlog import EventLogReporter
sinks.append(
EventLogReporter(
source=config.report.eventlog.source,
min_severity=config.report.eventlog.min_severity,
)
)
if config.report.http is not None:
from opcda_diagnozer.report.http import HttpReporter
sinks.append(
HttpReporter(
url=config.report.http.url,
bearer_token=config.report.http.bearer_token,
timeout_s=config.report.http.timeout_s,
)
)
fan_out = FanOut(tuple(sinks), error=lambda msg: print(f"run: {msg}", file=sys.stderr, flush=True))
return fan_out
def _cmd_run(config_path: str, server: str | None, host: str | None) -> int:
"""Foreground monitoring loop: poll → detect → print (Ctrl+C stops)."""
"""Foreground monitoring loop: poll → detect → report (Ctrl+C stops)."""
import signal
from opcda_diagnozer.events import DiagEvent
from opcda_diagnozer.monitor.poller import Poller
from opcda_diagnozer.opc.automation import AutomationOpcClient
@@ -587,13 +647,8 @@ def _cmd_run(config_path: str, server: str | None, host: str | None) -> int:
connect_timeout_s=config.opc.connect_timeout_s,
)
def reporter(event) -> None:
# Console = the human view (spec §9): clear text, status NAME (0xNN)
line = f"{event.time_utc.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]} UTC {event.severity.value:<8} {event.kind.value}"
extras = " ".join(f"{k}={v}" for k, v in event.detail.items())
print(f"{line} {extras}" if extras else line, flush=True)
poller = Poller(client_factory, config, reporter)
reporters = _build_reporters(config)
poller = Poller(client_factory, config, reporters.emit)
stop_requested = threading.Event()
def _on_ctrl_c(signum, frame) -> None: # noqa: ANN001, ARG001
@@ -604,7 +659,10 @@ def _cmd_run(config_path: str, server: str | None, host: str | None) -> int:
print("\nrun: stop requested - finishing current poll ...", file=sys.stderr, flush=True)
signal.signal(signal.SIGINT, _on_ctrl_c)
poller.run_forever()
try:
poller.run_forever()
finally:
reporters.close()
return 0