Repository: elastic/apm-agent-python

Issue:
APM Agent Serialization Error
**Describe the bug**: APM Agent crashes when using Python AWS SDK's builder for DynamoDB Key condition expressions.

**To Reproduce**

1. Import `from boto3.dynamodb.conditions import Key` and make a DynamoDB query that includes `KeyConditionExpression` option with a value generated by `Key` class. Example Broken Code:

```
SESSION = boto3.Session(region_name="us-west-2",)
DYNAMODB_TABLE = SESSION.resource("dynamodb").Table("TableName")

options = {
    "KeyConditionExpression": Key("primary_key").eq(1)
}

result = DYNAMODB_TABLE.query(**options)
```

2. If APM Agent is running it will crash with the following exception when above code executes:
```
Exception in thread eapm event processor thread:
Traceback (most recent call last):
  File "/var/lang/lib/python3.8/threading.py", line 932, in _bootstrap_inner
    self.run()
  File "/var/lang/lib/python3.8/threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "/var/task/elasticapm/transport/base.py", line 145, in _process_queue
    buffer.write((self._json_serializer({event_type: data}) + "\n").encode("utf-8"))
  File "/var/task/elasticapm/utils/json_encoder.py", line 63, in dumps
    return json.dumps(value, cls=BetterJSONEncoder, **kwargs)
  File "/var/lang/lib/python3.8/json/__init__.py", line 234, in dumps
    return cls(
  File "/var/lang/lib/python3.8/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/var/lang/lib/python3.8/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/var/task/elasticapm/utils/json_encoder.py", line 55, in default
    return super(BetterJSONEncoder, self).default(obj)
  File "/var/lang/lib/python3.8/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Equals is not JSON serializable
```

3. Excluding the usage of `Key` class for building Condition Expressions avoids the bug. Example Working Code:
```
SESSION = boto3.Session(region_name="us-west-2",)
DYNAMODB_TABLE = SESSION.resource("dynamodb").Table("TableName")

options = {
    "KeyConditionExpression": "#PrimaryKey = :value",
    "ExpressionAttributeNames": {"#PrimaryKey": "primary_key"},
    "ExpressionAttributeValues": {":value": 1}
}

result = DYNAMODB_TABLE.query(**options)
```

**Environment (please complete the following information)**
- OS: Linux
- Python version: 3.8
- Framework and version [e.g. Django 2.1]: Not Applicable
- APM Server version: 7.13.4
- Agent version: 6.3.3


Discussion:

Resolving patch:
diff --git a/elasticapm/utils/json_encoder.py b/elasticapm/utils/json_encoder.py
--- a/elasticapm/utils/json_encoder.py
+++ b/elasticapm/utils/json_encoder.py
@@ -52,7 +52,10 @@ class BetterJSONEncoder(json.JSONEncoder):
     def default(self, obj):
         if type(obj) in self.ENCODERS:
             return self.ENCODERS[type(obj)](obj)
-        return super(BetterJSONEncoder, self).default(obj)
+        try:
+            return super(BetterJSONEncoder, self).default(obj)
+        except TypeError:
+            return str(obj)
 
 
 def better_decoder(data):


Test patch:
diff --git a/tests/utils/json_utils/tests.py b/tests/utils/json_utils/tests.py
--- a/tests/utils/json_utils/tests.py
+++ b/tests/utils/json_utils/tests.py
@@ -71,3 +71,8 @@ def test_bytes():
 def test_decimal():
     res = decimal.Decimal("1.0")
     assert json.dumps(res) == "1.0"
+
+
+def test_unsupported():
+    res = object()
+    assert json.dumps(res).startswith('"<object object at')