Repository: elastic/apm-agent-python

Issue:
Sanic 21.9.2 breaks exception tracking
For yet unknown reasons, Sanic 21.9.2+ broke exception tracking. The changes between 21.9.1 and 21.9.2 are here:

https://github.com/sanic-org/sanic/compare/v21.9.1...v21.9.2

The test failures are here: https://apm-ci.elastic.co/blue/organizations/jenkins/apm-agent-python%2Fapm-agent-python-nightly-mbp%2Fmaster/detail/master/787/tests/

Example:

    ----------------------------------------------------- Captured log call ------------------------------------------------------INFO     sanic.root:testing.py:82 http://127.0.0.1:50003/fallback-value-error
    ERROR    sanic.error:request.py:193 Exception occurred in one of response middleware handlers
    Traceback (most recent call last):
    File "/home/user/.local/lib/python3.10/site-packages/sanic_routing/router.py", line 79, in resolve
        route, param_basket = self.find_route(
    File "", line 24, in find_route
    sanic_routing.exceptions.NotFound: Not Found

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
    File "/home/user/.local/lib/python3.10/site-packages/sanic/router.py", line 38, in _get
        return self.resolve(
    File "/home/user/.local/lib/python3.10/site-packages/sanic_routing/router.py", line 96, in resolve
        raise self.exception(str(e), path=path)
    sanic_routing.exceptions.NotFound: Not Found

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
    File "handle_request", line 26, in handle_request
        Any,
    File "/home/user/.local/lib/python3.10/site-packages/sanic/router.py", line 66, in get
        return self._get(path, method, host)
    File "/home/user/.local/lib/python3.10/site-packages/sanic/router.py", line 44, in _get
        raise NotFound("Requested URL {} not found".format(e.path))
    sanic.exceptions.NotFound: Requested URL /fallback-value-error not found

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
    File "/home/user/.local/lib/python3.10/site-packages/sanic/request.py", line 187, in respond
        response = await self.app._run_response_middleware(
    File "_run_response_middleware", line 22, in _run_response_middleware
        from ssl import Purpose, SSLContext, create_default_context
    File "/app/elasticapm/contrib/sanic/__init__.py", line 279, in _instrument_response
        await set_context(
    File "/app/elasticapm/contrib/asyncio/traces.py", line 93, in set_context
        data = await data()
    File "/app/elasticapm/contrib/sanic/utils.py", line 121, in get_response_info
        if config.capture_body and "octet-stream" not in response.content_type:
    TypeError: argument of type 'NoneType' is not iterable

Checking for `response.content_type is not None` in `elasticapm/contrib/sanic/utils.py:121` doesn't resolve the issue.

@ahopkins do you happen to have an idea what could cause these failures?


Discussion:
Thanks @beniwohli 

I'll handle this.
Looks like this line is swallowing up an error.

https://github.com/ahopkins/apm-agent-python/blob/master/elasticapm/base.py#L456

```
no default __reduce__ due to non-trivial __cinit__
```

That context object has a Sanic instance in it. It would seem that one of the changes makes it not so happy to be copied. Our own internal test suite does do some pickling on that object. The change in 21.9.3 seems somewhat simple so I am not sure how this was impacted. Still investigating.

```
{..., "_app": Sanic(), ...}
```
AH HA! That is precisely the difference, that value is in the Config object which did not previously exist.

Resolving patch:
diff --git a/elasticapm/contrib/sanic/utils.py b/elasticapm/contrib/sanic/utils.py
--- a/elasticapm/contrib/sanic/utils.py
+++ b/elasticapm/contrib/sanic/utils.py
@@ -28,6 +28,7 @@
 #  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 #  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 
+from string import ascii_uppercase
 from typing import Dict
 
 from sanic import Sanic
@@ -71,7 +72,8 @@ async def get_request_info(config: Config, request: Request) -> Dict[str, str]:
     :return: A dictionary containing the context information of the ongoing transaction
     """
     env = dict(get_env(request=request))
-    env.update(dict(request.app.config))
+    app_config = {k: v for k, v in dict(request.app.config).items() if all(letter in ascii_uppercase for letter in k)}
+    env.update(app_config)
     result = {
         "env": env,
         "method": request.method,


Test patch: