From df65b8c34a3fc0ea515af4379f0f2b8055402f92 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Fri, 11 Apr 2025 17:59:19 +0530 Subject: [PATCH] fix: adding request logger middleware --- apiserver/plane/middleware/logger.py | 71 +++++++++++++++++++++++ apiserver/plane/settings/common.py | 1 + apiserver/plane/settings/local.py | 17 ++++-- apiserver/plane/settings/production.py | 23 ++++---- apiserver/plane/utils/exception_logger.py | 4 +- apiserver/requirements/base.txt | 2 +- 6 files changed, 99 insertions(+), 19 deletions(-) create mode 100644 apiserver/plane/middleware/logger.py diff --git a/apiserver/plane/middleware/logger.py b/apiserver/plane/middleware/logger.py new file mode 100644 index 000000000..9979be2ed --- /dev/null +++ b/apiserver/plane/middleware/logger.py @@ -0,0 +1,71 @@ +# Python imports +import logging +import time + +# Django imports +from django.http import HttpRequest + +# Third party imports +from rest_framework.request import Request + +# Module imports +from plane.utils.ip_address import get_client_ip + +api_logger = logging.getLogger("plane.api") + + +class RequestLoggerMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def _should_log_route(self, request: Request | HttpRequest) -> bool: + """ + Determines whether a route should be logged based on the request and status code. + """ + # Don't log health checks + if request.path == "/" and request.method == "GET": + return False + return True + + def __call__(self, request): + # get the start time + start_time = time.time() + + # Get the response + response = self.get_response(request) + + # calculate the duration + duration = time.time() - start_time + + # Check if logging is required + log_true = self._should_log_route(request=request) + + # If logging is not required, return the response + if not log_true: + return response + + user_id = ( + request.user.id + if getattr(request, "user") + and getattr(request.user, "is_authenticated", False) + else None + ) + + user_agent = request.META.get("HTTP_USER_AGENT", "") + + # Log the request information + api_logger.info( + f"{request.method} {request.get_full_path()} {response.status_code}", + extra={ + "path": request.path, + "method": request.method, + "status_code": response.status_code, + "duration_ms": int(duration * 1000), + "remote_addr": get_client_ip(request), + "user_agent": user_agent, + "user_id": user_id, + }, + ) + + # return the response + return response diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py index 7712da894..5355d2049 100644 --- a/apiserver/plane/settings/common.py +++ b/apiserver/plane/settings/common.py @@ -59,6 +59,7 @@ MIDDLEWARE = [ "crum.CurrentRequestUserMiddleware", "django.middleware.gzip.GZipMiddleware", "plane.middleware.api_log_middleware.APITokenLogMiddleware", + "plane.middleware.logger.RequestLoggerMiddleware", ] # Rest Framework settings diff --git a/apiserver/plane/settings/local.py b/apiserver/plane/settings/local.py index d33115e2b..e67f63160 100644 --- a/apiserver/plane/settings/local.py +++ b/apiserver/plane/settings/local.py @@ -37,26 +37,31 @@ if not os.path.exists(LOG_DIR): LOGGING = { "version": 1, - "disable_existing_loggers": False, + "disable_existing_loggers": True, "formatters": { "verbose": { "format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}", "style": "{", - } + }, + "json": { + "()": "pythonjsonlogger.jsonlogger.JsonFormatter", + "fmt": "%(levelname)s %(asctime)s %(module)s %(name)s %(message)s", + }, }, "handlers": { "console": { "level": "DEBUG", "class": "logging.StreamHandler", - "formatter": "verbose", + "formatter": "json", } }, "loggers": { - "django.request": { + "plane.api": {"level": "INFO", "handlers": ["console"], "propagate": False}, + "plane.worker": {"level": "INFO", "handlers": ["console"], "propagate": False}, + "plane.exception": { + "level": "ERROR", "handlers": ["console"], - "level": "DEBUG", "propagate": False, }, - "plane": {"handlers": ["console"], "level": "DEBUG", "propagate": False}, }, } diff --git a/apiserver/plane/settings/production.py b/apiserver/plane/settings/production.py index 9390a2847..f632e793d 100644 --- a/apiserver/plane/settings/production.py +++ b/apiserver/plane/settings/production.py @@ -26,11 +26,10 @@ if not os.path.exists(LOG_DIR): # Logging configuration LOGGING = { "version": 1, - "disable_existing_loggers": False, + "disable_existing_loggers": True, "formatters": { "verbose": { - "format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}", - "style": "{", + "format": "%(asctime)s [%(process)d] %(levelname)s %(name)s: %(message)s" }, "json": { "()": "pythonjsonlogger.jsonlogger.JsonFormatter", @@ -40,7 +39,7 @@ LOGGING = { "handlers": { "console": { "class": "logging.StreamHandler", - "formatter": "verbose", + "formatter": "json", "level": "INFO", }, "file": { @@ -59,15 +58,19 @@ LOGGING = { }, }, "loggers": { - "django": {"handlers": ["console", "file"], "level": "INFO", "propagate": True}, - "django.request": { - "handlers": ["console", "file"], - "level": "INFO", + "plane.api": { + "level": "DEBUG" if DEBUG else "INFO", + "handlers": ["console"], "propagate": False, }, - "plane": { + "plane.worker": { + "level": "DEBUG" if DEBUG else "INFO", + "handlers": ["console"], + "propagate": False, + }, + "plane.exception": { "level": "DEBUG" if DEBUG else "ERROR", - "handlers": ["console", "file"], + "handlers": ["console"], "propagate": False, }, }, diff --git a/apiserver/plane/utils/exception_logger.py b/apiserver/plane/utils/exception_logger.py index e261c7384..72fdc70d8 100644 --- a/apiserver/plane/utils/exception_logger.py +++ b/apiserver/plane/utils/exception_logger.py @@ -8,8 +8,8 @@ from django.conf import settings def log_exception(e): # Log the error - logger = logging.getLogger("plane") - logger.error(e) + logger = logging.getLogger("plane.exception") + logger.error(str(e)) if settings.DEBUG: # Print the traceback if in debug mode diff --git a/apiserver/requirements/base.txt b/apiserver/requirements/base.txt index a3d660408..ad48a32ec 100644 --- a/apiserver/requirements/base.txt +++ b/apiserver/requirements/base.txt @@ -43,7 +43,7 @@ scout-apm==3.1.0 # xlsx generation openpyxl==3.1.2 # logging -python-json-logger==2.0.7 +python-json-logger==3.3.0 # html parser beautifulsoup4==4.12.3 # analytics