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>
This commit is contained in:
2026-04-16 14:58:39 +10:00
co-authored by Copilot
parent 3d481385b0
commit 611305b0ab
4 changed files with 12 additions and 19 deletions
+2
View File
@@ -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)
+4 -17
View File
@@ -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,
}
+5 -1
View File
@@ -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
+1 -1
View File
@@ -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'}},