Files
qlockify-backend-deployment/config/services/logging.py
2026-03-11 18:01:27 +08:00

245 lines
8.3 KiB
Python

import logging
import logging.config
import time
from logging.handlers import RotatingFileHandler
from pathlib import Path
from pythonjsonlogger import jsonlogger
BASE_DIR = Path(__file__).resolve().parents[2]
LOG_DIR = BASE_DIR / "logs"
LOG_DIR.mkdir(exist_ok=True)
# Custom formatter for console output (ERROR logs)
class CustomConsoleFormatter(logging.Formatter):
def format(self, record):
if isinstance(record.msg, dict):
msg_dict = record.msg
return (
f"{record.levelname} {self.formatTime(record, self.datefmt)} "
f"{msg_dict.get('request_method', '')} "
f"{msg_dict.get('request_url', '')} "
f"{msg_dict.get('status_code', '')} "
f"{msg_dict.get('remote_addr', '')} "
f"{msg_dict.get('duration_ms', 0)}ms "
f"{msg_dict.get('message', record.getMessage())}"
)
return super().format(record)
# Custom formatter for INFO console output (simpler format)
class CustomConsoleInfoFormatter(logging.Formatter):
def format(self, record):
if isinstance(record.msg, dict):
msg_dict = record.msg
return (
f"{record.levelname} {self.formatTime(record, self.datefmt)} "
f"{msg_dict.get('request_method', '')} "
f"{msg_dict.get('request_url', '')} "
f"{msg_dict.get('status_code', '')} "
f"{msg_dict.get('remote_addr', '')} "
f"{msg_dict.get('duration_ms', 0)}ms"
)
return super().format(record)
LOGGING = {
"version": 1,
"disable_existing_loggers": True,
"formatters": {
"verbose": {
"format": "{levelname} {asctime} {name} {module} {funcName} {lineno} {message}",
"style": "{",
},
"json": {
"()": "pythonjsonlogger.jsonlogger.JsonFormatter",
"format": "%(levelname)s %(asctime)s %(name)s %(module)s %(funcName)s %(lineno)d %(message)s %(pathname)s",
},
"request_formatter": {
"()": "pythonjsonlogger.jsonlogger.JsonFormatter",
"format": "%(levelname)s %(asctime)s %(name)s %(message)s %(pathname)s %(lineno)d %(request_method)s %(request_url)s %(status_code)d %(remote_addr)s %(user_agent)s %(duration_ms)d", # noqa: E501
},
# Custom formatters for console
"error_console": {
"()": CustomConsoleFormatter,
"format": "%(levelname)s %(asctime)s %(name)s %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
},
"info_console": {
"()": CustomConsoleInfoFormatter,
"format": "%(levelname)s %(asctime)s %(name)s %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
},
},
"handlers": {
"console": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "verbose",
},
"error_console": {
"level": "ERROR",
"class": "logging.StreamHandler",
"formatter": "error_console",
},
"info_console": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "info_console",
},
"file": {
"level": "INFO",
"class": "logging.handlers.RotatingFileHandler",
"filename": str(LOG_DIR / "django.log"),
"maxBytes": 1024 * 1024 * 15, # 15MB
"backupCount": 10,
"formatter": "json",
},
"error_file": {
"level": "ERROR",
"class": "logging.handlers.RotatingFileHandler",
"filename": str(LOG_DIR / "errors.log"),
"maxBytes": 1024 * 1024 * 15, # 15MB
"backupCount": 5,
"formatter": "json",
},
"debug_file": {
"level": "DEBUG",
"class": "logging.handlers.RotatingFileHandler",
"filename": str(LOG_DIR / "debug.log"),
"maxBytes": 1024 * 1024 * 15, # 15MB
"backupCount": 5,
"formatter": "verbose",
},
"request_info_file": {
"level": "INFO",
"class": "logging.handlers.RotatingFileHandler",
"filename": str(LOG_DIR / "info.log"),
"maxBytes": 1024 * 1024 * 15, # 15MB
"backupCount": 10,
"formatter": "request_formatter",
},
"request_error_file": {
"level": "ERROR",
"class": "logging.handlers.RotatingFileHandler",
"filename": str(LOG_DIR / "errors.log"),
"maxBytes": 1024 * 1024 * 15, # 15MB
"backupCount": 10,
"formatter": "request_formatter",
},
},
"loggers": {
"django.request": {
"handlers": ["error_console", "error_file"],
"level": "ERROR",
"propagate": False,
},
"django.server": {
"handlers": ["error_console"],
"level": "ERROR",
"propagate": False,
},
"myapp": {
"handlers": ["console", "file", "debug_file", "error_file"],
"level": "DEBUG",
"propagate": False,
},
"project.requests.info": {
"handlers": [
"request_info_file",
"info_console",
], # Use info_console for INFO
"level": "INFO",
"propagate": False,
},
"project.requests.error": {
"handlers": [
"request_error_file",
"error_console",
], # Use error_console for ERROR
"level": "ERROR",
"propagate": False,
},
"common.exceptions": {
"handlers": ["error_console", "error_file", "debug_file"],
"level": "ERROR",
"propagate": False,
},
"common.middleware": {
"handlers": ["error_console", "error_file", "debug_file"],
"level": "ERROR",
"propagate": False,
},
},
}
class RequestLoggingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.info_logger = logging.getLogger("project.requests.info")
self.error_logger = logging.getLogger("project.requests.error")
def __call__(self, request):
start = time.perf_counter()
try:
response = self.get_response(request)
except Exception:
duration_ms = int((time.perf_counter() - start) * 1000)
message = f"HTTP {request.method} request to {request.get_full_path()}"
log_data = {
"request_method": request.method,
"request_url": request.get_full_path(),
"status_code": 500,
"remote_addr": request.META.get("REMOTE_ADDR"),
"user_agent": request.META.get("HTTP_USER_AGENT", ""),
"duration_ms": duration_ms,
"message": message,
}
self.error_logger.exception(log_data)
raise
duration_ms = int((time.perf_counter() - start) * 1000)
message = f"HTTP {request.method} request to {request.get_full_path()}"
log_data = {
"request_method": request.method,
"request_url": request.get_full_path(),
"status_code": response.status_code,
"remote_addr": request.META.get("REMOTE_ADDR"),
"user_agent": request.META.get("HTTP_USER_AGENT", ""),
"duration_ms": duration_ms,
"message": message,
}
if response.status_code == 404:
log_data["message"] = f"Not Found: {request.get_full_path()}"
elif response.status_code == 401:
log_data["message"] = f"Unauthorized: {request.get_full_path()}"
if response.status_code >= 400:
self.error_logger.error(log_data)
else:
self.info_logger.info(log_data)
return response
def get_custom_logger(name):
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
custom_handler = RotatingFileHandler(
filename=str(LOG_DIR / f"{name}.log"),
maxBytes=1024 * 1024 * 15, # 15MB
backupCount=10,
)
custom_handler.setFormatter(jsonlogger.JsonFormatter())
if not logger.handlers:
logger.addHandler(custom_handler)
return logger
logging.config.dictConfig(LOGGING)