Repository: strawberry-graphql/strawberry

Issue:
Improve errors logging
Right now errors are being caught by GraphQL core and sent into the `errors` fields of the GraphQL response. Unfortunately that makes things harder to debug locally; we should print better exceptions locally, by using one of the following options:

```python
    def process_result(
        self, request: HttpRequest, result: ExecutionResult
    ) -> GraphQLHTTPResponse:
        data: GraphQLHTTPResponse = {"data": result.data}

            for error in result.errors:
                actual_error = error.original_error or error

                traceback.print_exception(
                    type(actual_error), actual_error, actual_error.__traceback__
                )

        return data
```

```python
    def process_result(self, request, result):
        data = super().process_result(request, result)

        # Log errors
        if result.errors:
            for error in result.errors:
                try:
                    if isinstance(error, GraphQLError) and error.original_error:
                        raise error.original_error
                    else:
                        raise error
                except Exception as error:
                    logger.exception(error)

        return data
```


Discussion:

Resolving patch:
diff --git a/strawberry/schema/schema.py b/strawberry/schema/schema.py
--- a/strawberry/schema/schema.py
+++ b/strawberry/schema/schema.py
@@ -1,3 +1,4 @@
+import logging
 from typing import Any, Dict, List, Optional, Sequence, Type, Union
 
 from graphql import (
@@ -7,6 +8,7 @@
     parse,
     validate_schema,
 )
+from graphql.error import GraphQLError
 from graphql.subscription import subscribe
 from graphql.type.directives import specified_directives
 
@@ -23,6 +25,9 @@
 from .execute import execute, execute_sync
 
 
+logger = logging.getLogger("strawberry.execution")
+
+
 class Schema:
     def __init__(
         self,
@@ -83,6 +88,11 @@ def get_type_by_name(
 
         return None
 
+    def process_errors(self, errors: List[GraphQLError]) -> None:
+        for error in errors:
+            actual_error = error.original_error or error
+            logger.error(actual_error, exc_info=actual_error)
+
     async def execute(
         self,
         query: str,
@@ -105,6 +115,9 @@ async def execute(
             validate_queries=validate_queries,
         )
 
+        if result.errors:
+            self.process_errors(result.errors)
+
         return ExecutionResult(
             data=result.data,
             errors=result.errors,
@@ -133,6 +146,9 @@ def execute_sync(
             validate_queries=validate_queries,
         )
 
+        if result.errors:
+            self.process_errors(result.errors)
+
         return ExecutionResult(
             data=result.data,
             errors=result.errors,


Test patch:
diff --git a/tests/schema/test_execution.py b/tests/schema/test_execution.py
--- a/tests/schema/test_execution.py
+++ b/tests/schema/test_execution.py
@@ -159,3 +159,68 @@ def example(self, value: str) -> int:
             """
         ).strip()
     )
+
+
+@pytest.mark.asyncio
+async def test_logging_exceptions(caplog):
+    @strawberry.type
+    class Query:
+        @strawberry.field
+        def example(self) -> int:
+            raise ValueError("test")
+
+    schema = strawberry.Schema(query=Query)
+
+    query = """
+        query {
+            example
+        }
+    """
+
+    result = await schema.execute(
+        query,
+        root_value=Query(),
+    )
+
+    assert len(result.errors) == 1
+
+    # Exception was logged
+    assert len(caplog.records) == 1
+    record = caplog.records[0]
+
+    assert record.levelname == "ERROR"
+    assert record.message == "test"
+    assert record.name == "strawberry.execution"
+    assert record.exc_info[0] is ValueError
+
+
+@pytest.mark.asyncio
+async def test_logging_graphql_exceptions(caplog):
+    @strawberry.type
+    class Query:
+        @strawberry.field
+        def example(self) -> int:
+            return None  # type: ignore
+
+    schema = strawberry.Schema(query=Query)
+
+    query = """
+        query {
+            example
+        }
+    """
+
+    result = await schema.execute(
+        query,
+        root_value=Query(),
+    )
+
+    assert len(result.errors) == 1
+
+    # Exception was logged
+    assert len(caplog.records) == 1
+    record = caplog.records[0]
+
+    assert record.levelname == "ERROR"
+    assert record.name == "strawberry.execution"
+    assert record.exc_info[0] is TypeError