Skip to content

Commit

Permalink
MP-432 fix graphene logger
Browse files Browse the repository at this point in the history
  • Loading branch information
Sheripov committed Sep 15, 2023
1 parent 23b90be commit e76679f
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 61 deletions.
69 changes: 46 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,54 @@
1. Add `GoogleFormatter` to your Django's `LOGGING` setting.
Example:
```python
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"json": {
"()": "django_google_structured_logger.formatter.GoogleFormatter",
},
},
"handlers": {
"google-json-handler": {
"class": "logging.StreamHandler",
"formatter": "json",
},
},
"root": {
"handlers": ["google-json-handler"],
"level": logging.INFO,
}
}
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"json": {
"()": "django_google_structured_logger.formatter.GoogleFormatter",
},
},
"handlers": {
"console": {
"level": "INFO",
"class": "logging.StreamHandler",
},
"google-json-handler": {
"class": "logging.StreamHandler",
"formatter": "json",
},
},
"root": {
"handlers": [env.str("DJANGO_LOG_HANDLER", "google-json-handler")],
"level": env.str("ROOT_LOG_LEVEL", "INFO"),
},
"loggers": {
"()": {
"handlers": [env.str("DJANGO_LOG_HANDLER", "google-json-handler")],
"level": env.str("DJANGO_LOG_LEVEL", "INFO"),
},
"django": {
"handlers": [env.str("DJANGO_LOG_HANDLER", "google-json-handler")],
"level": env.str("DJANGO_LOG_LEVEL", "INFO"),
"propagate": False,
},
"django.server": {
"handlers": [env.str("DJANGO_LOG_HANDLER", "google-json-handler")],
"level": env.str("DJANGO_SERVER_LEVEL", "ERROR"),
"propagate": False,
},
"django.request": {
"handlers": [env.str("DJANGO_LOG_HANDLER", "google-json-handler")],
"level": env.str("DJANGO_REQUEST_LEVEL", "ERROR"),
"propagate": False,
},
},
}
```
2. Add `SetRequestToLoggerMiddleware` to your Django's `MIDDLEWARE` setting.

Example for Django middleware:
Django middleware:
```python
MIDDLEWARE = [
...
Expand All @@ -52,14 +77,12 @@
"django_google_structured_logger.middlewares.LogRequestAndResponseMiddleware", # Log request and response.
]
```
Example for GRAPHENE middleware:
GRAPHENE middleware:
```python
GRAPHENE = {
"MIDDLEWARE": [
...
# Ordering is important:
"django_google_structured_logger.graphene_middlewares.GrapheneSetUserContextMiddleware", # Set user context to logger.
"django_google_structured_logger.graphene_middlewares.GrapheneLogRequestAndResponseMiddleware", # Log request and response.
]
}
```
Expand Down
27 changes: 0 additions & 27 deletions django_google_structured_logger/graphene_middlewares.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import json
import logging
import uuid
from typing import Any

from django.http import HttpResponse

from . import settings
from .middlewares import LogRequestAndResponseMiddleware
from .storages import RequestStorage, _current_request

logger = logging.getLogger(__name__)
Expand All @@ -31,26 +27,3 @@ def resolve(self, next, root, info, **args):
@staticmethod
def _get_user_attribute(user, attribute) -> Any:
return getattr(user, attribute, None)


class GrapheneLogRequestAndResponseMiddleware(LogRequestAndResponseMiddleware):
def resolve(self, next, root, info, **args):
if not settings.LOG_MIDDLEWARE_ENABLED:
return next(root, info, **args)

# Graphene middleware doesn't give access to the raw request/response
# Instead, the `info` argument provides a `context` attribute which usually contains the request
request = info.context

self.process_request(request)

# Since there's no direct access to the response,
# we can't process the response in the same way.
# But we can capture the result of the GraphQL execution.
result = next(root, info, **args)
# Here, `result` is the data returned from your GraphQL resolvers.
# We're wrapping it in a Django HttpResponse to use the existing process_response function.
fake_response = HttpResponse(content=json.dumps(result))
self.process_response(request, fake_response)

return result
29 changes: 18 additions & 11 deletions django_google_structured_logger/middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ class LogRequestAndResponseMiddleware:

def __init__(self, get_response):
self.get_response = get_response
self.request_body = None
self.log_excluded_headers_set = set(
map(str.lower, settings.LOG_EXCLUDED_HEADERS)
)
Expand All @@ -50,9 +49,8 @@ def __call__(self, request: HttpRequest) -> HttpResponse:
if not settings.LOG_MIDDLEWARE_ENABLED:
return self.get_response(request)

self.request_body = getattr(request, "body", None)
response = self.get_response(request)
self.process_request(request)
response = self.get_response(request)
self.process_response(request, response)
return response

Expand All @@ -70,15 +68,17 @@ def process_request(self, request):
try:
path = self._empty_value_none(getattr(request, "path", None))
method = self._empty_value_none(getattr(request, "method", None))
content_type = self._empty_value_none(
getattr(request, "content_type", None)
)
request_body = self._empty_value_none(getattr(request, "body", None))
request_data = {
"request": {
"body": self._get_request_body(request),
"body": self._get_request_body(content_type, request_body),
"query_params": self._empty_value_none(
getattr(request, "GET", None)
),
"content_type": self._empty_value_none(
getattr(request, "content_type", None)
),
"content_type": content_type,
"method": method,
"path": path,
"headers": self._empty_value_none(
Expand Down Expand Up @@ -108,6 +108,14 @@ def process_response(self, request, response):

try:
response_data = self._abridge(getattr(response, "data", None))
if response_data is None:
response_content = self._abridge(getattr(response, "content", None))
content_type = self._empty_value_none(
getattr(request, "content_type", None)
)
response_data = (
self._get_request_body(content_type, response_content),
)
response_status_code = getattr(response, "status_code", 0)
response_headers = self._exclude_keys(getattr(response, "headers", None))

Expand Down Expand Up @@ -255,15 +263,14 @@ def _exclude_keys(self, obj: Optional[Dict]) -> Optional[Dict]:
if k.lower() not in self.log_excluded_headers_set
}

def _get_request_body(self, request) -> Union[str, Dict, None]:
def _get_request_body(self, content_type, request_body) -> Union[str, Dict, None]:
"""
Extract request body and mask sensitive data.
Example:
Input: Django request object with JSON body {"key": "value" }
Output: {"key": "value"}
"""
content_type = getattr(request, "content_type", None)

def decode_and_abridge(body_bytes):
body_str = body_bytes.decode("UTF-8") if body_bytes else None
Expand All @@ -276,9 +283,9 @@ def decode_and_abridge(body_bytes):
if content_type == "multipart/form-data":
return "The image was uploaded to the server"
elif content_type == "application/json":
return self._mask_sensitive_data(decode_and_abridge(self.request_body))
return self._mask_sensitive_data(decode_and_abridge(request_body))
elif content_type == "text/plain":
return self._mask_sensitive_data(self._abridge(self.request_body))
return self._mask_sensitive_data(self._abridge(request_body))
else:
return self._mask_sensitive_data(content_type)

Expand Down

0 comments on commit e76679f

Please sign in to comment.