diff --git a/passbook/core/middleware.py b/passbook/core/middleware.py index f1631beb9..116050bad 100644 --- a/passbook/core/middleware.py +++ b/passbook/core/middleware.py @@ -1,11 +1,14 @@ """passbook admin Middleware to impersonate users""" - +from logging import Logger +from threading import local from typing import Callable +from uuid import uuid4 from django.http import HttpRequest, HttpResponse SESSION_IMPERSONATE_USER = "passbook_impersonate_user" SESSION_IMPERSONATE_ORIGINAL_USER = "passbook_impersonate_original_user" +LOCAL = local() class ImpersonateMiddleware: @@ -24,3 +27,30 @@ class ImpersonateMiddleware: request.user = request.session[SESSION_IMPERSONATE_USER] return self.get_response(request) + + +class RequestIDMiddleware: + """Add a unique ID to every request""" + + get_response: Callable[[HttpRequest], HttpResponse] + + def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]): + self.get_response = get_response + + def __call__(self, request: HttpRequest) -> HttpResponse: + if not hasattr(request, "request_id"): + request_id = uuid4().hex + setattr(request, "request_id", request_id) + LOCAL.passbook = {"request_id": request_id} + response = self.get_response(request) + response["X-passbook-id"] = request.request_id + del LOCAL.passbook["request_id"] + return response + + +# pylint: disable=unused-argument +def structlog_add_request_id(logger: Logger, method_name: str, event_dict): + """If threadlocal has passbook defined, add request_id to log""" + if hasattr(LOCAL, "passbook"): + event_dict["request_id"] = LOCAL.passbook.get("request_id", "") + return event_dict diff --git a/passbook/lib/logging.py b/passbook/lib/logging.py index 90cf7e483..9024c658c 100644 --- a/passbook/lib/logging.py +++ b/passbook/lib/logging.py @@ -1,9 +1,23 @@ """logging helpers""" +from logging import Logger from os import getpid +from typing import Callable # pylint: disable=unused-argument -def add_process_id(logger, method_name, event_dict): +def add_process_id(logger: Logger, method_name: str, event_dict): """Add the current process ID""" event_dict["pid"] = getpid() return event_dict + + +def add_common_fields(environment: str) -> Callable: + """Add a common field to easily search for passbook logs""" + + def add_common_field(logger: Logger, method_name: str, event_dict): + """Add a common field to easily search for passbook logs""" + event_dict["app"] = "passbook" + event_dict["app_environment"] = environment + return event_dict + + return add_common_field diff --git a/passbook/root/settings.py b/passbook/root/settings.py index aeef6e9c3..207db6c70 100644 --- a/passbook/root/settings.py +++ b/passbook/root/settings.py @@ -22,8 +22,9 @@ from sentry_sdk.integrations.celery import CeleryIntegration from sentry_sdk.integrations.django import DjangoIntegration from passbook import __version__ +from passbook.core.middleware import structlog_add_request_id from passbook.lib.config import CONFIG -from passbook.lib.logging import add_process_id +from passbook.lib.logging import add_common_fields, add_process_id from passbook.lib.sentry import before_send @@ -175,6 +176,7 @@ MIDDLEWARE = [ "django_prometheus.middleware.PrometheusBeforeMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", + "passbook.core.middleware.RequestIDMiddleware", "django.middleware.security.SecurityMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", @@ -330,6 +332,8 @@ structlog.configure_once( structlog.stdlib.add_log_level, structlog.stdlib.add_logger_name, add_process_id, + add_common_fields(CONFIG.y("error_reporting.environment", "customer")), + structlog_add_request_id, structlog.stdlib.PositionalArgumentsFormatter(), structlog.processors.TimeStamper(), structlog.processors.StackInfoRenderer(),