Replaces LICENSE (GPLv3 -> AGPLv3) and prepends `SPDX-License-Identifier: AGPL-3.0-or-later` to every source file across decnet/, decnet_web/, tests/, scripts/, and tools/. Rationale: closes the GPLv3 ASP loophole so any party operating a modified DECNET as a network service must offer their modified source. Personal copyright (Samuel Paschuan) + inbound=outbound contributions make a future unilateral relicense infeasible. - LICENSE: full AGPL-3.0 text (gnu.org/licenses/agpl-3.0.txt) - COPYRIGHT: project copyright notice - tools/add_spdx_headers.py: idempotent header injector (shebang- and PEP 263-aware) Touches 1565 source files (.py, .ts, .tsx, .js, .jsx, .css, .sh). No behavior change; comments only.
73 lines
2.3 KiB
Python
73 lines
2.3 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
"""
|
|
RFC 5424 syslog formatting and log-file writing for the fleet sniffer.
|
|
|
|
Reuses the same wire format as templates/sniffer/decnet_logging.py so the
|
|
existing collector parser and ingester can consume events without changes.
|
|
"""
|
|
|
|
import json
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from decnet.collector.worker import parse_rfc5424
|
|
from decnet.telemetry import traced as _traced
|
|
|
|
# ─── Constants (must match templates/sniffer/decnet_logging.py) ──────────────
|
|
|
|
_FACILITY_LOCAL0 = 16
|
|
_SD_ID = "relay@55555"
|
|
_NILVALUE = "-"
|
|
|
|
SEVERITY_INFO = 6
|
|
SEVERITY_WARNING = 4
|
|
|
|
_MAX_HOSTNAME = 255
|
|
_MAX_APPNAME = 48
|
|
_MAX_MSGID = 32
|
|
|
|
|
|
# ─── Formatter ───────────────────────────────────────────────────────────────
|
|
|
|
def _sd_escape(value: str) -> str:
|
|
return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]")
|
|
|
|
|
|
def _sd_element(fields: dict[str, Any]) -> str:
|
|
if not fields:
|
|
return _NILVALUE
|
|
params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items())
|
|
return f"[{_SD_ID} {params}]"
|
|
|
|
|
|
def syslog_line(
|
|
service: str,
|
|
hostname: str,
|
|
event_type: str,
|
|
severity: int = SEVERITY_INFO,
|
|
msg: str | None = None,
|
|
**fields: Any,
|
|
) -> str:
|
|
pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>"
|
|
ts = datetime.now(timezone.utc).isoformat()
|
|
host = (hostname or _NILVALUE)[:_MAX_HOSTNAME]
|
|
appname = (service or _NILVALUE)[:_MAX_APPNAME]
|
|
msgid = (event_type or _NILVALUE)[:_MAX_MSGID]
|
|
sd = _sd_element(fields)
|
|
message = f" {msg}" if msg else ""
|
|
return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}"
|
|
|
|
|
|
@_traced("sniffer.write_event")
|
|
def write_event(line: str, log_path: Path, json_path: Path) -> None:
|
|
"""Append a syslog line to the raw log and its parsed JSON to the json log."""
|
|
with open(log_path, "a", encoding="utf-8") as lf:
|
|
lf.write(line + "\n")
|
|
lf.flush()
|
|
parsed = parse_rfc5424(line)
|
|
if parsed:
|
|
with open(json_path, "a", encoding="utf-8") as jf:
|
|
jf.write(json.dumps(parsed) + "\n")
|
|
jf.flush()
|