Rename project to ddns-service

This commit is contained in:
2026-01-18 14:32:41 +01:00
parent d0ac96bad8
commit 27fd8ab438
18 changed files with 61 additions and 61 deletions

141
src/ddns_service/logging.py Normal file
View File

@@ -0,0 +1,141 @@
"""Centralized logging configuration."""
import logging
import logging.handlers
import secrets
from contextvars import ContextVar
# Transaction ID context (thread-safe per-request)
_txn_id = ContextVar("txn_id", default="-")
# Syslog facility mapping
SYSLOG_FACILITIES = {
"daemon": logging.handlers.SysLogHandler.LOG_DAEMON,
"user": logging.handlers.SysLogHandler.LOG_USER,
"local0": logging.handlers.SysLogHandler.LOG_LOCAL0,
"local1": logging.handlers.SysLogHandler.LOG_LOCAL1,
"local2": logging.handlers.SysLogHandler.LOG_LOCAL2,
"local3": logging.handlers.SysLogHandler.LOG_LOCAL3,
"local4": logging.handlers.SysLogHandler.LOG_LOCAL4,
"local5": logging.handlers.SysLogHandler.LOG_LOCAL5,
"local6": logging.handlers.SysLogHandler.LOG_LOCAL6,
"local7": logging.handlers.SysLogHandler.LOG_LOCAL7,
}
def get_txn_id():
"""Get current transaction ID."""
return _txn_id.get()
def set_txn_id(txn_id=None):
"""Set transaction ID, generate if not provided."""
if txn_id is None:
txn_id = secrets.token_hex(4)
_txn_id.set(txn_id)
return txn_id
def clear_txn_id():
"""Reset transaction ID to default."""
_txn_id.set("-")
class TxnIdFilter(logging.Filter):
"""Inject txn_id into log records."""
def filter(self, record):
record.txn_id = get_txn_id()
return True
class TxnIdFormatter(logging.Formatter):
"""Formatter that conditionally includes transaction ID."""
def __init__(self, fmt_with_txn, fmt_without_txn, datefmt=None):
super().__init__(fmt_with_txn, datefmt)
self.fmt_with_txn = fmt_with_txn
self.fmt_without_txn = fmt_without_txn
def format(self, record):
if hasattr(record, "txn_id") and record.txn_id != "-":
self._style._fmt = self.fmt_with_txn
else:
self._style._fmt = self.fmt_without_txn
return super().format(record)
def setup_logging(
level="INFO",
target="stdout",
syslog_socket="/dev/log",
syslog_facility="daemon",
log_file="/var/log/ddns-service.log",
log_file_size=52428800,
log_versions=5,
):
"""
Configure global logging.
Args:
level: Log level name (DEBUG, INFO, WARNING, ERROR)
target: "stdout", "syslog", or "file"
syslog_socket: Path to syslog socket
syslog_facility: Syslog facility name
log_file: Path to log file (for target="file")
log_file_size: Max log file size in bytes before rotation
log_versions: Number of backup files to keep
"""
root = logging.getLogger()
root.setLevel(getattr(logging, level.upper(), logging.INFO))
# Clear existing handlers
root.handlers.clear()
txn_filter = TxnIdFilter()
if target == "syslog":
facility = SYSLOG_FACILITIES.get(
syslog_facility.lower(),
logging.handlers.SysLogHandler.LOG_DAEMON,
)
handler = logging.handlers.SysLogHandler(
address=syslog_socket,
facility=facility,
)
formatter = TxnIdFormatter(
"ddns-service[%(process)d]: [%(levelname)s] [%(txn_id)s] %(message)s",
"ddns-service[%(process)d]: [%(levelname)s] %(message)s",
)
elif target == "file":
handler = logging.handlers.RotatingFileHandler(
log_file,
maxBytes=log_file_size,
backupCount=log_versions,
)
formatter = TxnIdFormatter(
"%(asctime)s [%(levelname)s] [%(txn_id)s] %(message)s",
"%(asctime)s [%(levelname)s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
else:
handler = logging.StreamHandler()
formatter = TxnIdFormatter(
"%(asctime)s [%(levelname)s] [%(txn_id)s] %(message)s",
"%(asctime)s [%(levelname)s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
handler.addFilter(txn_filter)
handler.setFormatter(formatter)
root.addHandler(handler)
def disable_logging():
"""Disable all logging (for CLI quiet mode)."""
logging.disable(logging.CRITICAL)
def enable_logging():
"""Re-enable logging."""
logging.disable(logging.NOTSET)