Rename project to ddns-service
This commit is contained in:
141
src/ddns_service/logging.py
Normal file
141
src/ddns_service/logging.py
Normal 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)
|
||||
Reference in New Issue
Block a user