truenas-burnin/app/logging_config.py
Brandon Walter b73b5251ae Initial commit — TrueNAS Burn-In Dashboard v0.5.0
Full-stack burn-in orchestration dashboard (Stages 1–6d complete):
FastAPI backend, SQLite/WAL, SSE live dashboard, mock TrueNAS server,
SMTP/webhook notifications, batch burn-in, settings UI, audit log,
stats page, cancel SMART/burn-in, drag-to-reorder stages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 00:08:29 -05:00

50 lines
1.6 KiB
Python

"""
Structured JSON logging configuration.
Each log line is a single JSON object:
{"ts":"2026-02-21T21:34:36","level":"INFO","logger":"app.burnin","msg":"...","job_id":1}
Extra context fields (job_id, drive_id, devname, stage) are included when
passed via the logging `extra=` kwarg.
"""
import json
import logging
import traceback
from app.config import settings
# Standard LogRecord attributes to exclude from the "extra" dump
_STDLIB_ATTRS = frozenset(logging.LogRecord("", 0, "", 0, "", (), None).__dict__)
class _JsonFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> str:
data: dict = {
"ts": self.formatTime(record, "%Y-%m-%dT%H:%M:%S"),
"level": record.levelname,
"logger": record.name,
"msg": record.getMessage(),
}
# Include any non-standard fields passed via extra={}
for key, val in record.__dict__.items():
if key not in _STDLIB_ATTRS and not key.startswith("_"):
data[key] = val
if record.exc_info:
data["exc"] = "".join(traceback.format_exception(*record.exc_info)).strip()
return json.dumps(data)
def configure() -> None:
handler = logging.StreamHandler()
handler.setFormatter(_JsonFormatter())
level = getattr(logging, settings.log_level.upper(), logging.INFO)
root = logging.getLogger()
root.setLevel(level)
root.handlers = [handler]
# Quiet chatty third-party loggers
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)