From 611305b0ab9de9e19dace56da08dc93ff7cdf9b0 Mon Sep 17 00:00:00 2001 From: Junwei Zhao Date: Thu, 16 Apr 2026 14:58:39 +1000 Subject: [PATCH] fix(routermon): fix receiver startup, logging config, and JS template escaping - Remove SO_REUSEADDR from UDP socket so only one gunicorn worker binds port 5514 (second worker intentionally gets EADDRINUSE and skips) - Add print() fallback for bind confirmation/errors so startup is visible even when Django logging config is not fully set up - Fix duplicate LOGGING dict in settings.py: second assignment was overriding the first and removing the root logger handler, silently swallowing all routermon/core startup logs - Add sys.stderr fallback in core/apps.py ready() for startup exceptions - Fix JS SyntaxError: {{ top_domains_json }} needs |safe filter since Django auto-escapes quotes to " inside script tags Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- core/apps.py | 2 ++ core/settings.py | 21 ++++---------------- routermon/receiver.py | 6 +++++- routermon/templates/routermon/dashboard.html | 2 +- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/core/apps.py b/core/apps.py index 68bd57f..6c6b5ca 100644 --- a/core/apps.py +++ b/core/apps.py @@ -89,4 +89,6 @@ class CoreConfig(AppConfig): ) logger.info("routermon: scheduled cleanup job (every 6h)") except Exception as exc: + import sys + print(f'routermon: startup error (non-fatal): {exc}', file=sys.stderr, flush=True) logger.warning("routermon: startup error (non-fatal): %s", exc) diff --git a/core/settings.py b/core/settings.py index fc9ee78..8521143 100644 --- a/core/settings.py +++ b/core/settings.py @@ -232,21 +232,8 @@ CRAWL4AI_ENABLED = os.environ.get('CRAWL4AI_ENABLED', 'True').lower() in ('true' BUILD_TIME = os.environ.get('BUILD_TIME', '') BUILD_VERSION = os.environ.get('BUILD_VERSION', '') -# For debugging -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'handlers': { - 'console': { - 'class': 'logging.StreamHandler', - 'level': 'DEBUG', - }, - }, - 'loggers': { - 'links': { - 'handlers': ['console'], - 'level': 'DEBUG', - 'propagate': True, - }, - }, +LOGGING['loggers']['links'] = { + 'handlers': ['console'], + 'level': 'DEBUG', + 'propagate': False, } diff --git a/routermon/receiver.py b/routermon/receiver.py index fd7004c..f2cbdf4 100644 --- a/routermon/receiver.py +++ b/routermon/receiver.py @@ -54,15 +54,19 @@ def start_receiver(port: int): try: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + # No SO_REUSEADDR: we intentionally want only one worker to bind. + # Other workers receive EADDRINUSE and skip cleanly. sock.bind(('0.0.0.0', port)) sock.settimeout(1.0) _sock = sock + print(f'routermon: UDP syslog receiver bound on port {port}', flush=True) logger.info('routermon: UDP syslog receiver bound on port %d', port) except OSError as exc: if exc.errno == errno.EADDRINUSE: logger.debug('routermon: port %d already bound (another worker), skipping', port) else: + import sys + print(f'routermon: failed to bind UDP port {port}: {exc}', file=sys.stderr, flush=True) logger.error('routermon: failed to bind UDP port %d: %s', port, exc) return diff --git a/routermon/templates/routermon/dashboard.html b/routermon/templates/routermon/dashboard.html index 009a000..e1fbd0a 100644 --- a/routermon/templates/routermon/dashboard.html +++ b/routermon/templates/routermon/dashboard.html @@ -324,7 +324,7 @@ async function loadChart() { }); // Top domains mini bar chart - const topData = {{ top_domains_json }}; + const topData = {{ top_domains_json|safe }}; const top10 = topData.slice(0, 10).reverse(); domainsChartEl.setOption({ tooltip: {trigger: 'axis', axisPointer: {type: 'shadow'}},