Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ClientErrorLogMiddleware for detailed 4xx error logging #1128

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,21 @@ decorator:
# ...
path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))),
]

Logging Errors
--------------

By default, when there is a GraphQL error because of client inputs , Django logs a very simple message
with 4xx HTTP status code. If you would like to see more details, you can enable
``ClientErrorLogMiddleware`` as follows:

.. code:: python

# settings.py

MIDDLEWARE = [
"graphene_django.middlewares.ClientErrorLogMiddleware",
# ...
]

This middleware works when your endpoint is ``/graphql``.
39 changes: 39 additions & 0 deletions graphene_django/middlewares.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import json
import logging

from django.utils.log import log_response

logger = logging.getLogger("django.graphene")


class ClientErrorLogMiddleware:
"""
Logs graphql requests 4xx errors. (Except 401, 403)
"""

def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
response = self.get_response(request)

try:
if (
400 <= response.status_code < 500
and response.status_code not in (401, 403)
and "graphql" in request.path.lower()
):
response_json = json.loads(response.content)

if "errors" in response_json:
log_response(
message=(
f"Graphql Error: {response_json['errors']}\n"
f"The Query is: {json.loads(request.body)}"
),
response=response,
)
except Exception:
logger.error(f"Error logging graphql error.", exc_info=True)

return response
107 changes: 107 additions & 0 deletions graphene_django/tests/test_middlewares.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import json
import logging
import graphene
import mock
from django.http.response import HttpResponse
from django.test import RequestFactory
from graphene.test import Client

from .models import Reporter
from .. import DjangoObjectType
from ..middlewares import ClientErrorLogMiddleware


class ReporterType(DjangoObjectType):
class Meta:
model = Reporter
fields = "__all__"


class Query(graphene.ObjectType):
reporter = graphene.Field(ReporterType)

def resolve_reporter(self, info, **args):
return Reporter.objects.first()


def test_should_log_error(caplog):
Reporter.objects.create(last_name="ABA")

invalid_query = """
query ReporterQuery {
reporter {
invalidAttrName
}
}
"""

schema = graphene.Schema(query=Query)
client = Client(schema)
response = client.execute(invalid_query)

factory = RequestFactory()
request = factory.post(
"/graphql", data=json.dumps(invalid_query), content_type="application/json"
)

http_res = HttpResponse(json.dumps(response).encode(), status=400)

get_response = mock.MagicMock()
get_response.return_value = http_res

middleware = ClientErrorLogMiddleware(get_response)
middleware(request)

assert len(caplog.records) == 1
assert caplog.records[0] != "WARNING"
assert str(response["errors"]) in caplog.text
assert invalid_query in caplog.text


def test_should_not_log_success(caplog):
Reporter.objects.create(last_name="ABA")

valid_query = """
query ReporterQuery {
reporter {
lastName
}
}
"""

schema = graphene.Schema(query=Query)
client = Client(schema)
response = client.execute(valid_query)

factory = RequestFactory()
request = factory.post(
"/graphql", data=json.dumps(valid_query), content_type="application/json"
)

http_res = HttpResponse(json.dumps(response).encode(), status=200)

get_response = mock.MagicMock()
get_response.return_value = http_res

middleware = ClientErrorLogMiddleware(get_response)
middleware(request)

assert len(caplog.records) == 0


def test_should_not_log_non_graphql_error(caplog):
factory = RequestFactory()
request = factory.post(
"/users", data=json.dumps({"name": "Mario"}), content_type="application/json"
)
http_res = HttpResponse(
json.dumps({"errors": ["Got to be Luigi"]}).encode(), status=400
)

get_response = mock.MagicMock()
get_response.return_value = http_res

middleware = ClientErrorLogMiddleware(get_response)
middleware(request)

assert len(caplog.records) == 0