245 lines
8.3 KiB
Python
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)
|