mirror of
https://github.com/kc1awv/rrcd.git
synced 2026-05-17 13:24:45 -07:00
Begin adding in logging configuration support
This commit is contained in:
22
README.md
22
README.md
@@ -46,6 +46,28 @@ Optional:
|
|||||||
|
|
||||||
You need a working Reticulum configuration (see Reticulum docs).
|
You need a working Reticulum configuration (see Reticulum docs).
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
By default, `rrcd` logs to stderr (good for systemd/journald). You can configure
|
||||||
|
logging in `~/.rrcd/rrcd.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[logging]
|
||||||
|
level = "INFO" # set to DEBUG for connection/packet tracing
|
||||||
|
rns_level = "WARNING" # python-logging level for the "RNS" logger (if used)
|
||||||
|
console = true
|
||||||
|
file = "" # e.g. "~/.rrcd/rrcd.log" (empty disables)
|
||||||
|
format = "%(asctime)s %(levelname)s %(name)s[%(threadName)s]: %(message)s"
|
||||||
|
datefmt = ""
|
||||||
|
```
|
||||||
|
|
||||||
|
CLI overrides are also available:
|
||||||
|
|
||||||
|
- `rrcd --log-level DEBUG`
|
||||||
|
- `rrcd --log-file ~/.rrcd/rrcd.log`
|
||||||
|
|
||||||
|
If you use `/reload`, logging settings are applied immediately.
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
`rrcd` implements the core RRC protocol as described in the RRC docs.
|
`rrcd` implements the core RRC protocol as described in the RRC docs.
|
||||||
|
|||||||
61
rrcd/cli.py
61
rrcd/cli.py
@@ -1,7 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from dataclasses import asdict, replace
|
from dataclasses import asdict, replace
|
||||||
@@ -9,6 +8,7 @@ from dataclasses import asdict, replace
|
|||||||
import RNS
|
import RNS
|
||||||
|
|
||||||
from .config import HubRuntimeConfig
|
from .config import HubRuntimeConfig
|
||||||
|
from .logging_config import configure_logging
|
||||||
from .paths import (
|
from .paths import (
|
||||||
default_config_path,
|
default_config_path,
|
||||||
default_identity_path,
|
default_identity_path,
|
||||||
@@ -32,6 +32,23 @@ def _apply_config_file(cfg: HubRuntimeConfig, path: str) -> HubRuntimeConfig:
|
|||||||
if isinstance(hub, dict):
|
if isinstance(hub, dict):
|
||||||
data = {**data, **hub}
|
data = {**data, **hub}
|
||||||
|
|
||||||
|
log_table = data.get("logging") if isinstance(data, dict) else None
|
||||||
|
if isinstance(log_table, dict):
|
||||||
|
mapped: dict[str, object] = {}
|
||||||
|
if "level" in log_table:
|
||||||
|
mapped["log_level"] = log_table.get("level")
|
||||||
|
if "rns_level" in log_table:
|
||||||
|
mapped["log_rns_level"] = log_table.get("rns_level")
|
||||||
|
if "console" in log_table:
|
||||||
|
mapped["log_console"] = log_table.get("console")
|
||||||
|
if "file" in log_table:
|
||||||
|
mapped["log_file"] = log_table.get("file")
|
||||||
|
if "format" in log_table:
|
||||||
|
mapped["log_format"] = log_table.get("format")
|
||||||
|
if "datefmt" in log_table:
|
||||||
|
mapped["log_datefmt"] = log_table.get("datefmt")
|
||||||
|
data = {**data, **mapped}
|
||||||
|
|
||||||
allowed = set(asdict(cfg).keys())
|
allowed = set(asdict(cfg).keys())
|
||||||
# This identifies where to reload/persist from; do not let the file override it.
|
# This identifies where to reload/persist from; do not let the file override it.
|
||||||
allowed.discard("config_path")
|
allowed.discard("config_path")
|
||||||
@@ -50,6 +67,10 @@ def _apply_config_file(cfg: HubRuntimeConfig, path: str) -> HubRuntimeConfig:
|
|||||||
updates["configdir"] = None
|
updates["configdir"] = None
|
||||||
if "greeting" in updates and updates["greeting"] == "":
|
if "greeting" in updates and updates["greeting"] == "":
|
||||||
updates["greeting"] = None
|
updates["greeting"] = None
|
||||||
|
if "log_file" in updates and updates["log_file"] == "":
|
||||||
|
updates["log_file"] = None
|
||||||
|
if "log_datefmt" in updates and updates["log_datefmt"] == "":
|
||||||
|
updates["log_datefmt"] = None
|
||||||
return replace(cfg, **updates) if updates else cfg
|
return replace(cfg, **updates) if updates else cfg
|
||||||
|
|
||||||
|
|
||||||
@@ -133,6 +154,24 @@ rate_limit_msgs_per_minute = 240
|
|||||||
# Hub-initiated liveness checks (0 disables).
|
# Hub-initiated liveness checks (0 disables).
|
||||||
ping_interval_s = 0.0
|
ping_interval_s = 0.0
|
||||||
ping_timeout_s = 0.0
|
ping_timeout_s = 0.0
|
||||||
|
|
||||||
|
[logging]
|
||||||
|
|
||||||
|
# Log level for rrcd itself.
|
||||||
|
level = "INFO"
|
||||||
|
|
||||||
|
# Log level for Reticulum/RNS Python logging (if used by your install).
|
||||||
|
rns_level = "WARNING"
|
||||||
|
|
||||||
|
# Log to stderr (systemd/journald friendly).
|
||||||
|
console = true
|
||||||
|
|
||||||
|
# Optional file path for logs (leave empty to disable).
|
||||||
|
file = ""
|
||||||
|
|
||||||
|
# Log format and optional date format.
|
||||||
|
format = "%(asctime)s %(levelname)s %(name)s[%(threadName)s]: %(message)s"
|
||||||
|
datefmt = ""
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with open(config_path, "w", encoding="utf-8") as f:
|
with open(config_path, "w", encoding="utf-8") as f:
|
||||||
@@ -286,8 +325,14 @@ def _build_arg_parser() -> argparse.ArgumentParser:
|
|||||||
|
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
"--log-level",
|
"--log-level",
|
||||||
default="INFO",
|
default=None,
|
||||||
help="Logging level (DEBUG, INFO, WARNING, ERROR)",
|
help="Logging level override (DEBUG, INFO, WARNING, ERROR). Default comes from config.",
|
||||||
|
)
|
||||||
|
|
||||||
|
p.add_argument(
|
||||||
|
"--log-file",
|
||||||
|
default=None,
|
||||||
|
help="Log file path override (empty disables file logging). Default comes from config.",
|
||||||
)
|
)
|
||||||
|
|
||||||
return p
|
return p
|
||||||
@@ -349,10 +394,12 @@ def main(argv: list[str] | None = None) -> None:
|
|||||||
if args.ping_timeout is not None:
|
if args.ping_timeout is not None:
|
||||||
cfg = replace(cfg, ping_timeout_s=float(args.ping_timeout))
|
cfg = replace(cfg, ping_timeout_s=float(args.ping_timeout))
|
||||||
|
|
||||||
logging.basicConfig(
|
if args.log_level is not None:
|
||||||
level=getattr(logging, str(args.log_level).upper(), logging.INFO),
|
cfg = replace(cfg, log_level=str(args.log_level))
|
||||||
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
if args.log_file is not None:
|
||||||
)
|
cfg = replace(cfg, log_file=str(args.log_file) if str(args.log_file) else None)
|
||||||
|
|
||||||
|
configure_logging(cfg, override_level=args.log_level, override_file=args.log_file)
|
||||||
|
|
||||||
svc = HubService(cfg)
|
svc = HubService(cfg)
|
||||||
svc.start()
|
svc.start()
|
||||||
|
|||||||
@@ -14,26 +14,21 @@ class HubRuntimeConfig:
|
|||||||
announce_period_s: float = 0.0
|
announce_period_s: float = 0.0
|
||||||
hub_name: str = "rrc"
|
hub_name: str = "rrc"
|
||||||
greeting: str | None = None
|
greeting: str | None = None
|
||||||
# Hex-encoded Reticulum identity hashes trusted as operators.
|
|
||||||
trusted_identities: tuple[str, ...] = ()
|
trusted_identities: tuple[str, ...] = ()
|
||||||
# Hex-encoded Reticulum identity hashes banned from connecting.
|
|
||||||
banned_identities: tuple[str, ...] = ()
|
banned_identities: tuple[str, ...] = ()
|
||||||
|
|
||||||
# Room registry maintenance (registered rooms are stored in room_registry_path).
|
|
||||||
# Pruning only applies to registered rooms with no connected members.
|
|
||||||
room_registry_prune_after_s: float = 30 * 24 * 3600
|
room_registry_prune_after_s: float = 30 * 24 * 3600
|
||||||
room_registry_prune_interval_s: float = 3600.0
|
room_registry_prune_interval_s: float = 3600.0
|
||||||
# Invite timeout for keyed rooms (+k). Invites are removed on join or expiry.
|
|
||||||
room_invite_timeout_s: float = 900.0
|
room_invite_timeout_s: float = 900.0
|
||||||
include_joined_member_list: bool = False
|
include_joined_member_list: bool = False
|
||||||
|
|
||||||
# Optional policy controls.
|
|
||||||
# Maximum accepted/stored nickname length (Unicode characters). 0 disables
|
|
||||||
# length limiting.
|
|
||||||
nick_max_chars: int = 32
|
nick_max_chars: int = 32
|
||||||
|
|
||||||
max_rooms_per_session: int = 32
|
max_rooms_per_session: int = 32
|
||||||
max_room_name_len: int = 64
|
max_room_name_len: int = 64
|
||||||
rate_limit_msgs_per_minute: int = 240
|
rate_limit_msgs_per_minute: int = 240
|
||||||
ping_interval_s: float = 0.0
|
ping_interval_s: float = 0.0
|
||||||
ping_timeout_s: float = 0.0
|
ping_timeout_s: float = 0.0
|
||||||
|
log_level: str = "INFO"
|
||||||
|
log_rns_level: str = "WARNING"
|
||||||
|
log_console: bool = True
|
||||||
|
log_file: str | None = None
|
||||||
|
log_format: str = "%(asctime)s %(levelname)s %(name)s[%(threadName)s]: %(message)s"
|
||||||
|
log_datefmt: str | None = None
|
||||||
|
|||||||
103
rrcd/logging_config.py
Normal file
103
rrcd/logging_config.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from .config import HubRuntimeConfig
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_level(value: Any, default: int) -> int:
|
||||||
|
if value is None:
|
||||||
|
return default
|
||||||
|
if isinstance(value, int):
|
||||||
|
return value
|
||||||
|
text = str(value).strip().upper()
|
||||||
|
if not text:
|
||||||
|
return default
|
||||||
|
|
||||||
|
mapping: dict[str, int] = {
|
||||||
|
"CRITICAL": logging.CRITICAL,
|
||||||
|
"ERROR": logging.ERROR,
|
||||||
|
"WARN": logging.WARNING,
|
||||||
|
"WARNING": logging.WARNING,
|
||||||
|
"INFO": logging.INFO,
|
||||||
|
"DEBUG": logging.DEBUG,
|
||||||
|
"NOTSET": logging.NOTSET,
|
||||||
|
}
|
||||||
|
if text in mapping:
|
||||||
|
return mapping[text]
|
||||||
|
|
||||||
|
try:
|
||||||
|
return int(text)
|
||||||
|
except Exception:
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def _clean_optional_path(value: Any) -> str | None:
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
s = str(value)
|
||||||
|
if not s.strip():
|
||||||
|
return None
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def configure_logging(
|
||||||
|
cfg: HubRuntimeConfig,
|
||||||
|
*,
|
||||||
|
override_level: str | None = None,
|
||||||
|
override_file: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Configure Python logging for rrcd.
|
||||||
|
|
||||||
|
Intended to be safe to call multiple times (e.g., on /reload).
|
||||||
|
"""
|
||||||
|
|
||||||
|
level = _parse_level(override_level or cfg.log_level, logging.INFO)
|
||||||
|
rns_level = _parse_level(cfg.log_rns_level, logging.WARNING)
|
||||||
|
|
||||||
|
handlers: list[logging.Handler] = []
|
||||||
|
|
||||||
|
if bool(cfg.log_console):
|
||||||
|
handlers.append(logging.StreamHandler())
|
||||||
|
|
||||||
|
log_file = _clean_optional_path(override_file) if override_file is not None else None
|
||||||
|
if log_file is None:
|
||||||
|
log_file = _clean_optional_path(cfg.log_file)
|
||||||
|
|
||||||
|
if log_file:
|
||||||
|
p = Path(os.path.expanduser(log_file))
|
||||||
|
if p.parent:
|
||||||
|
p.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
file_handler = logging.FileHandler(p, encoding="utf-8")
|
||||||
|
try:
|
||||||
|
os.chmod(p, 0o600)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
handlers.append(file_handler)
|
||||||
|
|
||||||
|
fmt = str(cfg.log_format).strip() if str(cfg.log_format).strip() else None
|
||||||
|
if not fmt:
|
||||||
|
fmt = "%(asctime)s %(levelname)s %(name)s: %(message)s"
|
||||||
|
|
||||||
|
datefmt = _clean_optional_path(cfg.log_datefmt)
|
||||||
|
|
||||||
|
formatter = logging.Formatter(fmt=fmt, datefmt=datefmt)
|
||||||
|
for h in handlers:
|
||||||
|
h.setFormatter(formatter)
|
||||||
|
|
||||||
|
root = logging.getLogger()
|
||||||
|
for h in list(root.handlers):
|
||||||
|
root.removeHandler(h)
|
||||||
|
|
||||||
|
for h in handlers:
|
||||||
|
root.addHandler(h)
|
||||||
|
|
||||||
|
root.setLevel(level)
|
||||||
|
|
||||||
|
# Library loggers
|
||||||
|
logging.getLogger("RNS").setLevel(rns_level)
|
||||||
|
|
||||||
|
logging.captureWarnings(True)
|
||||||
170
rrcd/service.py
170
rrcd/service.py
@@ -35,6 +35,7 @@ from .constants import (
|
|||||||
T_WELCOME,
|
T_WELCOME,
|
||||||
)
|
)
|
||||||
from .envelope import make_envelope, validate_envelope
|
from .envelope import make_envelope, validate_envelope
|
||||||
|
from .logging_config import configure_logging
|
||||||
from .util import expand_path, normalize_nick
|
from .util import expand_path, normalize_nick
|
||||||
|
|
||||||
|
|
||||||
@@ -100,6 +101,21 @@ class HubService:
|
|||||||
"announces": 0,
|
"announces": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _fmt_hash(self, h: Any, *, prefix: int = 12) -> str:
|
||||||
|
if isinstance(h, (bytes, bytearray)):
|
||||||
|
s = bytes(h).hex()
|
||||||
|
return s if prefix <= 0 else s[: min(prefix, len(s))]
|
||||||
|
return "-"
|
||||||
|
|
||||||
|
def _fmt_link_id(self, link: RNS.Link) -> str:
|
||||||
|
lid = getattr(link, "link_id", None)
|
||||||
|
if isinstance(lid, (bytes, bytearray)):
|
||||||
|
return bytes(lid).hex()
|
||||||
|
h = getattr(link, "hash", None)
|
||||||
|
if isinstance(h, (bytes, bytearray)):
|
||||||
|
return bytes(h).hex()
|
||||||
|
return "-"
|
||||||
|
|
||||||
def _inc(self, key: str, delta: int = 1) -> None:
|
def _inc(self, key: str, delta: int = 1) -> None:
|
||||||
try:
|
try:
|
||||||
with self._state_lock:
|
with self._state_lock:
|
||||||
@@ -162,6 +178,13 @@ class HubService:
|
|||||||
self.config.dest_name,
|
self.config.dest_name,
|
||||||
self.destination.hash.hex() if self.destination else "-",
|
self.destination.hash.hex() if self.destination else "-",
|
||||||
)
|
)
|
||||||
|
self.log.info(
|
||||||
|
"Policy nick_max_chars=%s max_rooms=%s max_room_name_len=%s rate_limit_msgs_per_minute=%s",
|
||||||
|
self.config.nick_max_chars,
|
||||||
|
self.config.max_rooms_per_session,
|
||||||
|
self.config.max_room_name_len,
|
||||||
|
self.config.rate_limit_msgs_per_minute,
|
||||||
|
)
|
||||||
|
|
||||||
if self.config.ping_interval_s and self.config.ping_interval_s > 0:
|
if self.config.ping_interval_s and self.config.ping_interval_s > 0:
|
||||||
self._ping_thread = threading.Thread(target=self._ping_loop, daemon=True)
|
self._ping_thread = threading.Thread(target=self._ping_loop, daemon=True)
|
||||||
@@ -260,6 +283,23 @@ class HubService:
|
|||||||
if isinstance(hub, dict):
|
if isinstance(hub, dict):
|
||||||
data = {**data, **hub}
|
data = {**data, **hub}
|
||||||
|
|
||||||
|
log_table = data.get("logging") if isinstance(data, dict) else None
|
||||||
|
if isinstance(log_table, dict):
|
||||||
|
mapped: dict[str, object] = {}
|
||||||
|
if "level" in log_table:
|
||||||
|
mapped["log_level"] = log_table.get("level")
|
||||||
|
if "rns_level" in log_table:
|
||||||
|
mapped["log_rns_level"] = log_table.get("rns_level")
|
||||||
|
if "console" in log_table:
|
||||||
|
mapped["log_console"] = log_table.get("console")
|
||||||
|
if "file" in log_table:
|
||||||
|
mapped["log_file"] = log_table.get("file")
|
||||||
|
if "format" in log_table:
|
||||||
|
mapped["log_format"] = log_table.get("format")
|
||||||
|
if "datefmt" in log_table:
|
||||||
|
mapped["log_datefmt"] = log_table.get("datefmt")
|
||||||
|
data = {**data, **mapped}
|
||||||
|
|
||||||
allowed = set(asdict(base).keys())
|
allowed = set(asdict(base).keys())
|
||||||
# This identifies where to reload from; do not let the file override it.
|
# This identifies where to reload from; do not let the file override it.
|
||||||
allowed.discard("config_path")
|
allowed.discard("config_path")
|
||||||
@@ -279,6 +319,10 @@ class HubService:
|
|||||||
updates["configdir"] = None
|
updates["configdir"] = None
|
||||||
if "greeting" in updates and updates["greeting"] == "":
|
if "greeting" in updates and updates["greeting"] == "":
|
||||||
updates["greeting"] = None
|
updates["greeting"] = None
|
||||||
|
if "log_file" in updates and updates["log_file"] == "":
|
||||||
|
updates["log_file"] = None
|
||||||
|
if "log_datefmt" in updates and updates["log_datefmt"] == "":
|
||||||
|
updates["log_datefmt"] = None
|
||||||
|
|
||||||
return replace(base, **updates) if updates else base
|
return replace(base, **updates) if updates else base
|
||||||
|
|
||||||
@@ -663,6 +707,12 @@ class HubService:
|
|||||||
|
|
||||||
self._ensure_worker_threads()
|
self._ensure_worker_threads()
|
||||||
|
|
||||||
|
# Apply logging changes immediately.
|
||||||
|
try:
|
||||||
|
configure_logging(self.config)
|
||||||
|
except Exception:
|
||||||
|
self.log.exception("Failed to reconfigure logging")
|
||||||
|
|
||||||
cfg_changes = self._diff_config_summary(old_cfg, new_cfg)
|
cfg_changes = self._diff_config_summary(old_cfg, new_cfg)
|
||||||
room_changes = self._diff_room_registry_summary(old_registry, new_registry)
|
room_changes = self._diff_room_registry_summary(old_registry, new_registry)
|
||||||
|
|
||||||
@@ -1988,6 +2038,8 @@ class HubService:
|
|||||||
lambda identified_link, ident: self._on_remote_identified(identified_link, ident)
|
lambda identified_link, ident: self._on_remote_identified(identified_link, ident)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.log.info("Link established link_id=%s", self._fmt_link_id(link))
|
||||||
|
|
||||||
def _on_remote_identified(
|
def _on_remote_identified(
|
||||||
self, link: RNS.Link, identity: RNS.Identity | None
|
self, link: RNS.Link, identity: RNS.Identity | None
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -2007,6 +2059,11 @@ class HubService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if banned:
|
if banned:
|
||||||
|
self.log.warning(
|
||||||
|
"Disconnecting banned peer peer=%s link_id=%s",
|
||||||
|
self._fmt_hash(peer_hash),
|
||||||
|
self._fmt_link_id(link),
|
||||||
|
)
|
||||||
if self.identity is not None:
|
if self.identity is not None:
|
||||||
try:
|
try:
|
||||||
self._error(link, src=self.identity.hash, text="banned")
|
self._error(link, src=self.identity.hash, text="banned")
|
||||||
@@ -2018,6 +2075,13 @@ class HubService:
|
|||||||
pass
|
pass
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if identity is not None:
|
||||||
|
self.log.info(
|
||||||
|
"Remote identified peer=%s link_id=%s",
|
||||||
|
self._fmt_hash(identity.hash),
|
||||||
|
self._fmt_link_id(link),
|
||||||
|
)
|
||||||
|
|
||||||
def _welcome(self, link: RNS.Link, sess: dict[str, Any]) -> None:
|
def _welcome(self, link: RNS.Link, sess: dict[str, Any]) -> None:
|
||||||
if self.identity is None:
|
if self.identity is None:
|
||||||
return
|
return
|
||||||
@@ -2029,14 +2093,27 @@ class HubService:
|
|||||||
|
|
||||||
welcome = make_envelope(T_WELCOME, src=self.identity.hash, body=body)
|
welcome = make_envelope(T_WELCOME, src=self.identity.hash, body=body)
|
||||||
self._send(link, welcome)
|
self._send(link, welcome)
|
||||||
|
self.log.debug(
|
||||||
|
"Sent WELCOME peer=%s link_id=%s",
|
||||||
|
self._fmt_hash(sess.get("peer")),
|
||||||
|
self._fmt_link_id(link),
|
||||||
|
)
|
||||||
|
|
||||||
def _on_close(self, link: RNS.Link) -> None:
|
def _on_close(self, link: RNS.Link) -> None:
|
||||||
|
peer = None
|
||||||
|
nick = None
|
||||||
|
rooms_count = 0
|
||||||
|
|
||||||
with self._state_lock:
|
with self._state_lock:
|
||||||
sess = self.sessions.pop(link, None)
|
sess = self.sessions.pop(link, None)
|
||||||
self._rate.pop(link, None)
|
self._rate.pop(link, None)
|
||||||
if not sess:
|
if not sess:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
peer = sess.get("peer")
|
||||||
|
nick = sess.get("nick")
|
||||||
|
rooms_count = len(sess.get("rooms") or ())
|
||||||
|
|
||||||
for room in list(sess["rooms"]):
|
for room in list(sess["rooms"]):
|
||||||
self.rooms.get(room, set()).discard(link)
|
self.rooms.get(room, set()).discard(link)
|
||||||
if room in self.rooms and not self.rooms[room]:
|
if room in self.rooms and not self.rooms[room]:
|
||||||
@@ -2045,13 +2122,26 @@ class HubService:
|
|||||||
if st is not None and not st.get("registered"):
|
if st is not None and not st.get("registered"):
|
||||||
self._room_state.pop(room, None)
|
self._room_state.pop(room, None)
|
||||||
|
|
||||||
|
self.log.info(
|
||||||
|
"Link closed peer=%s nick=%r rooms=%s link_id=%s",
|
||||||
|
self._fmt_hash(peer),
|
||||||
|
nick,
|
||||||
|
rooms_count,
|
||||||
|
self._fmt_link_id(link),
|
||||||
|
)
|
||||||
|
|
||||||
def _send(self, link: RNS.Link, env: dict) -> None:
|
def _send(self, link: RNS.Link, env: dict) -> None:
|
||||||
payload = encode(env)
|
payload = encode(env)
|
||||||
self._inc("bytes_out", len(payload))
|
self._inc("bytes_out", len(payload))
|
||||||
try:
|
try:
|
||||||
RNS.Packet(link, payload).send()
|
RNS.Packet(link, payload).send()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
self.log.debug(
|
||||||
|
"Send failed link_id=%s bytes=%s",
|
||||||
|
self._fmt_link_id(link),
|
||||||
|
len(payload),
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
|
||||||
def _error(
|
def _error(
|
||||||
self, link: RNS.Link, src: bytes, text: str, room: str | None = None
|
self, link: RNS.Link, src: bytes, text: str, room: str | None = None
|
||||||
@@ -2097,7 +2187,12 @@ class HubService:
|
|||||||
try:
|
try:
|
||||||
RNS.Packet(out_link, payload).send()
|
RNS.Packet(out_link, payload).send()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
self.log.debug(
|
||||||
|
"Send failed link_id=%s bytes=%s",
|
||||||
|
self._fmt_link_id(out_link),
|
||||||
|
len(payload),
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
|
||||||
def _on_packet_locked(
|
def _on_packet_locked(
|
||||||
self,
|
self,
|
||||||
@@ -2124,6 +2219,12 @@ class HubService:
|
|||||||
|
|
||||||
if not self._refill_and_take(link, 1.0):
|
if not self._refill_and_take(link, 1.0):
|
||||||
self._inc("rate_limited")
|
self._inc("rate_limited")
|
||||||
|
if self.log.isEnabledFor(logging.DEBUG):
|
||||||
|
self.log.debug(
|
||||||
|
"Rate limited peer=%s link_id=%s",
|
||||||
|
self._fmt_hash(peer_hash),
|
||||||
|
self._fmt_link_id(link),
|
||||||
|
)
|
||||||
if self.identity is not None:
|
if self.identity is not None:
|
||||||
self._emit_error(outgoing, link, src=self.identity.hash, text="rate limited")
|
self._emit_error(outgoing, link, src=self.identity.hash, text="rate limited")
|
||||||
return
|
return
|
||||||
@@ -2133,6 +2234,13 @@ class HubService:
|
|||||||
validate_envelope(env)
|
validate_envelope(env)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._inc("pkts_bad")
|
self._inc("pkts_bad")
|
||||||
|
self.log.debug(
|
||||||
|
"Bad packet peer=%s link_id=%s bytes=%s err=%s",
|
||||||
|
self._fmt_hash(peer_hash),
|
||||||
|
self._fmt_link_id(link),
|
||||||
|
len(data),
|
||||||
|
e,
|
||||||
|
)
|
||||||
if self.identity is not None:
|
if self.identity is not None:
|
||||||
self._emit_error(outgoing, link, src=self.identity.hash, text=f"bad message: {e}")
|
self._emit_error(outgoing, link, src=self.identity.hash, text=f"bad message: {e}")
|
||||||
return
|
return
|
||||||
@@ -2141,6 +2249,23 @@ class HubService:
|
|||||||
room = env.get(K_ROOM)
|
room = env.get(K_ROOM)
|
||||||
body = env.get(K_BODY)
|
body = env.get(K_BODY)
|
||||||
|
|
||||||
|
if self.log.isEnabledFor(logging.DEBUG):
|
||||||
|
body_len = None
|
||||||
|
if isinstance(body, (bytes, bytearray)):
|
||||||
|
body_len = len(body)
|
||||||
|
elif isinstance(body, str):
|
||||||
|
body_len = len(body)
|
||||||
|
self.log.debug(
|
||||||
|
"RX peer=%s link_id=%s t=%s room=%r bytes=%s body_type=%s body_len=%s",
|
||||||
|
self._fmt_hash(peer_hash),
|
||||||
|
self._fmt_link_id(link),
|
||||||
|
t,
|
||||||
|
room,
|
||||||
|
len(data),
|
||||||
|
type(body).__name__,
|
||||||
|
body_len,
|
||||||
|
)
|
||||||
|
|
||||||
if t == T_PONG:
|
if t == T_PONG:
|
||||||
self._inc("pongs_in")
|
self._inc("pongs_in")
|
||||||
sess["awaiting_pong"] = None
|
sess["awaiting_pong"] = None
|
||||||
@@ -2158,6 +2283,13 @@ class HubService:
|
|||||||
if n is not None:
|
if n is not None:
|
||||||
sess["nick"] = n
|
sess["nick"] = n
|
||||||
|
|
||||||
|
self.log.info(
|
||||||
|
"HELLO peer=%s nick=%r link_id=%s",
|
||||||
|
self._fmt_hash(peer_hash),
|
||||||
|
sess.get("nick"),
|
||||||
|
self._fmt_link_id(link),
|
||||||
|
)
|
||||||
|
|
||||||
if self.identity is not None:
|
if self.identity is not None:
|
||||||
sess["welcomed"] = True
|
sess["welcomed"] = True
|
||||||
body_w: dict[int, Any] = {B_WELCOME_HUB: self.config.hub_name}
|
body_w: dict[int, Any] = {B_WELCOME_HUB: self.config.hub_name}
|
||||||
@@ -2183,6 +2315,13 @@ class HubService:
|
|||||||
if n is not None:
|
if n is not None:
|
||||||
sess["nick"] = n
|
sess["nick"] = n
|
||||||
|
|
||||||
|
self.log.info(
|
||||||
|
"Re-HELLO peer=%s nick=%r link_id=%s",
|
||||||
|
self._fmt_hash(peer_hash),
|
||||||
|
sess.get("nick"),
|
||||||
|
self._fmt_link_id(link),
|
||||||
|
)
|
||||||
|
|
||||||
# Send WELCOME
|
# Send WELCOME
|
||||||
sess["welcomed"] = True
|
sess["welcomed"] = True
|
||||||
body_w: dict[int, Any] = {B_WELCOME_HUB: self.config.hub_name}
|
body_w: dict[int, Any] = {B_WELCOME_HUB: self.config.hub_name}
|
||||||
@@ -2255,6 +2394,14 @@ class HubService:
|
|||||||
sess["rooms"].add(r)
|
sess["rooms"].add(r)
|
||||||
self.rooms.setdefault(r, set()).add(link)
|
self.rooms.setdefault(r, set()).add(link)
|
||||||
|
|
||||||
|
self.log.info(
|
||||||
|
"JOIN peer=%s nick=%r room=%s link_id=%s",
|
||||||
|
self._fmt_hash(peer_hash),
|
||||||
|
sess.get("nick"),
|
||||||
|
r,
|
||||||
|
self._fmt_link_id(link),
|
||||||
|
)
|
||||||
|
|
||||||
self._touch_room(r)
|
self._touch_room(r)
|
||||||
|
|
||||||
joined_body = None
|
joined_body = None
|
||||||
@@ -2334,6 +2481,14 @@ class HubService:
|
|||||||
if self.identity is not None:
|
if self.identity is not None:
|
||||||
parted = make_envelope(T_PARTED, src=self.identity.hash, room=r, body=parted_body)
|
parted = make_envelope(T_PARTED, src=self.identity.hash, room=r, body=parted_body)
|
||||||
self._queue_env(outgoing, link, parted)
|
self._queue_env(outgoing, link, parted)
|
||||||
|
|
||||||
|
self.log.info(
|
||||||
|
"PART peer=%s nick=%r room=%s link_id=%s",
|
||||||
|
self._fmt_hash(peer_hash),
|
||||||
|
sess.get("nick"),
|
||||||
|
r,
|
||||||
|
self._fmt_link_id(link),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if t in (T_MSG, T_NOTICE):
|
if t in (T_MSG, T_NOTICE):
|
||||||
@@ -2401,6 +2556,17 @@ class HubService:
|
|||||||
for other in list(self.rooms.get(r, set())):
|
for other in list(self.rooms.get(r, set())):
|
||||||
self._queue_payload(outgoing, other, payload)
|
self._queue_payload(outgoing, other, payload)
|
||||||
|
|
||||||
|
if self.log.isEnabledFor(logging.DEBUG):
|
||||||
|
self.log.debug(
|
||||||
|
"Forwarded t=%s peer=%s nick=%r room=%s recipients=%s body_type=%s",
|
||||||
|
t,
|
||||||
|
self._fmt_hash(peer_hash),
|
||||||
|
sess.get("nick"),
|
||||||
|
r,
|
||||||
|
len(self.rooms.get(r, set())),
|
||||||
|
type(body).__name__,
|
||||||
|
)
|
||||||
|
|
||||||
if t == T_MSG:
|
if t == T_MSG:
|
||||||
self._inc("msgs_forwarded")
|
self._inc("msgs_forwarded")
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user