Files
venus/dbus-anchor-alarm/debug_logger.py
2026-03-26 14:15:02 +00:00

167 lines
5.5 KiB
Python

"""
Optional SQLite debug logger — default OFF.
Controlled at runtime via /Settings/DebugLogging D-Bus path.
When enabled, logs raw_points and estimation_log at the tracker rate (1/15s)
with buffered commits (flush every FLUSH_INTERVAL_SEC).
Schema is identical to the original TrackLogger so analysis scripts still work.
"""
import logging
import os
import sqlite3
import time
from config import DATA_DIR
logger = logging.getLogger('dbus-anchor-alarm.debug_logger')
DB_FILE = 'track.db'
FLUSH_INTERVAL_SEC = 30.0
ESTIMATION_LOG_MAX_AGE_SEC = 7 * 86400
_SCHEMA = """
CREATE TABLE IF NOT EXISTS raw_points (
ts REAL PRIMARY KEY,
lat REAL,
lon REAL,
hdg REAL,
cog REAL,
spd REAL,
ws REAL,
wd REAL,
dist REAL,
depth REAL
);
CREATE TABLE IF NOT EXISTS estimation_log (
ts REAL PRIMARY KEY,
marked_lat REAL,
marked_lon REAL,
est_lat REAL,
est_lon REAL,
uncertainty_ft REAL,
drift_ft REAL,
hdg_est_lat REAL,
hdg_est_lon REAL,
arc_center_lat REAL,
arc_center_lon REAL,
arc_radius_ft REAL,
arc_residual REAL,
arc_coverage REAL,
arc_valid INTEGER,
arc_point_count INTEGER,
weight_arc REAL,
weight_heading REAL,
cat_dist_ft REAL,
vessel_lat REAL,
vessel_lon REAL
);
"""
class DebugLogger:
"""SQLite debug logger, only active when explicitly enabled."""
def __init__(self, data_dir=DATA_DIR):
self._data_dir = data_dir
self._conn = None
self._last_flush = 0.0
self._pending = 0
@property
def active(self):
return self._conn is not None
def enable(self):
"""Open SQLite connection and create tables."""
if self._conn is not None:
return
os.makedirs(self._data_dir, exist_ok=True)
db_path = os.path.join(self._data_dir, DB_FILE)
self._conn = sqlite3.connect(db_path, check_same_thread=False)
self._conn.execute('PRAGMA journal_mode=WAL')
self._conn.execute('PRAGMA synchronous=NORMAL')
self._conn.executescript(_SCHEMA)
self._conn.commit()
self._last_flush = time.time()
self._pending = 0
logger.info('Debug logging enabled -> %s', db_path)
def disable(self):
"""Flush and close the SQLite connection."""
if self._conn is None:
return
try:
self._conn.commit()
self._conn.close()
except sqlite3.Error:
logger.exception('Error closing debug logger')
self._conn = None
self._pending = 0
logger.info('Debug logging disabled')
def log(self, snapshot, tracker):
"""Log both raw point and estimation state in a single call."""
if self._conn is None:
return
ts = snapshot.timestamp or time.time()
try:
if snapshot.latitude is not None and snapshot.longitude is not None:
self._conn.execute(
'INSERT OR REPLACE INTO raw_points '
'(ts, lat, lon, hdg, cog, spd, ws, wd, dist, depth) '
'VALUES (?,?,?,?,?,?,?,?,?,?)',
(ts, snapshot.latitude, snapshot.longitude,
snapshot.heading, snapshot.course, snapshot.speed,
snapshot.wind_speed, snapshot.wind_direction,
tracker.estimated_distance_ft, snapshot.depth),
)
self._pending += 1
if tracker.anchor_set:
self._conn.execute(
'INSERT OR REPLACE INTO estimation_log '
'(ts, marked_lat, marked_lon, est_lat, est_lon, '
'uncertainty_ft, drift_ft, hdg_est_lat, hdg_est_lon, '
'arc_center_lat, arc_center_lon, arc_radius_ft, '
'arc_residual, arc_coverage, arc_valid, arc_point_count, '
'weight_arc, weight_heading, cat_dist_ft, '
'vessel_lat, vessel_lon) '
'VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
(ts,
tracker.marked_lat, tracker.marked_lon,
tracker.estimated_lat, tracker.estimated_lon,
tracker.uncertainty_radius_ft, tracker.drift_ft,
tracker.last_heading_est_lat, tracker.last_heading_est_lon,
tracker.last_arc_center_lat, tracker.last_arc_center_lon,
tracker.last_arc_radius_ft, tracker.last_arc_residual,
tracker.last_arc_coverage,
int(tracker.last_arc_valid),
tracker.last_arc_point_count,
tracker.last_weight_arc, tracker.last_weight_heading,
tracker.estimated_distance_ft,
snapshot.latitude, snapshot.longitude),
)
self._pending += 1
except sqlite3.Error:
logger.exception('Debug log write failed')
now = time.time()
if now - self._last_flush >= FLUSH_INTERVAL_SEC and self._pending > 0:
self._flush(now)
def _flush(self, now):
try:
self._conn.commit()
cutoff = now - ESTIMATION_LOG_MAX_AGE_SEC
self._conn.execute('DELETE FROM estimation_log WHERE ts < ?', (cutoff,))
self._conn.commit()
except sqlite3.Error:
logger.exception('Debug logger flush failed')
self._pending = 0
self._last_flush = now