142 lines
4.1 KiB
Python
142 lines
4.1 KiB
Python
"""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/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)
|