We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
FastAPI/Starlette: Truncate exception message to prevent "event exceeded the permitted size" error from APM Server
**Describe the bug**: 
The capture_exception() method is sending the whole exception message to the APM Server and when the message exceeds the maximum size, the APM Server returns the  "event exceeded the permitted size" error. 

I'm sorry if it is not a bug, but I believe that the apm-agent should truncate the message before sending it to the APM Server.


**To Reproduce**
Create a FastAPI route that always raise an error with a large exception message (as the example below) and it will show an "event exceeded the permitted size" error on the next attempt to send events to APM server

```
@app.get('/apm_test/')
async def apm_test():
    try:
        raise Exception('test' * 1000000)
    except Exception as e:
        apm_client.capture_exception()
    return PlainTextResponse('', 200)
```

**Environment**
- OS: ubuntu 20.04
- Python version: 3.9.13
- Framework and version: fastapi==0.79.0
- APM Server version: 8.3.0
- Agent version: 6.11.0



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `elasticapm/events.py`
Content:
```
1 #  BSD 3-Clause License
2 #
3 #  Copyright (c) 2012, the Sentry Team, see AUTHORS for more details
4 #  Copyright (c) 2019, Elasticsearch BV
5 #  All rights reserved.
6 #
7 #  Redistribution and use in source and binary forms, with or without
8 #  modification, are permitted provided that the following conditions are met:
9 #
10 #  * Redistributions of source code must retain the above copyright notice, this
11 #    list of conditions and the following disclaimer.
12 #
13 #  * Redistributions in binary form must reproduce the above copyright notice,
14 #    this list of conditions and the following disclaimer in the documentation
15 #    and/or other materials provided with the distribution.
16 #
17 #  * Neither the name of the copyright holder nor the names of its
18 #    contributors may be used to endorse or promote products derived from
19 #    this software without specific prior written permission.
20 #
21 #  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 #  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 #  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 #  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 #  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 #  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 #  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 #  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 #  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 
31 
32 import random
33 import sys
34 
35 from elasticapm.conf.constants import EXCEPTION_CHAIN_MAX_DEPTH
36 from elasticapm.utils import varmap
37 from elasticapm.utils.encoding import keyword_field, shorten, to_unicode
38 from elasticapm.utils.logging import get_logger
39 from elasticapm.utils.stacks import get_culprit, get_stack_info, iter_traceback_frames
40 
41 __all__ = ("BaseEvent", "Exception", "Message")
42 
43 logger = get_logger("elasticapm.events")
44 
45 
46 class BaseEvent(object):
47     @staticmethod
48     def to_string(client, data):
49         raise NotImplementedError
50 
51     @staticmethod
52     def capture(client, **kwargs):
53         return {}
54 
55 
56 class Exception(BaseEvent):
57     """
58     Exceptions store the following metadata:
59 
60     - value: 'My exception value'
61     - type: 'ClassName'
62     - module '__builtin__' (i.e. __builtin__.TypeError)
63     - frames: a list of serialized frames (see _get_traceback_frames)
64     """
65 
66     @staticmethod
67     def to_string(client, data):
68         exc = data["exception"]
69         if exc["value"]:
70             return "%s: %s" % (exc["type"], exc["value"])
71         return exc["type"]
72 
73     @staticmethod
74     def get_hash(data):
75         exc = data["exception"]
76         output = [exc["type"]]
77         for frame in data["stacktrace"]["frames"]:
78             output.append(frame["module"])
79             output.append(frame["function"])
80         return output
81 
82     @staticmethod
83     def capture(client, exc_info=None, **kwargs):
84         culprit = exc_value = exc_type = exc_module = frames = exc_traceback = None
85         new_exc_info = False
86         if not exc_info or exc_info is True:
87             new_exc_info = True
88             exc_info = sys.exc_info()
89 
90         if exc_info == (None, None, None):
91             raise ValueError("No exception found: capture_exception requires an active exception.")
92 
93         try:
94             exc_type, exc_value, exc_traceback = exc_info
95 
96             frames = get_stack_info(
97                 iter_traceback_frames(exc_traceback, config=client.config),
98                 with_locals=client.config.collect_local_variables in ("errors", "all"),
99                 library_frame_context_lines=client.config.source_lines_error_library_frames,
100                 in_app_frame_context_lines=client.config.source_lines_error_app_frames,
101                 include_paths_re=client.include_paths_re,
102                 exclude_paths_re=client.exclude_paths_re,
103                 locals_processor_func=lambda local_var: varmap(
104                     lambda k, val: shorten(
105                         val,
106                         list_length=client.config.local_var_list_max_length,
107                         string_length=client.config.local_var_max_length,
108                         dict_length=client.config.local_var_dict_max_length,
109                     ),
110                     local_var,
111                 ),
112             )
113 
114             culprit = kwargs.get("culprit", None) or get_culprit(
115                 frames, client.config.include_paths, client.config.exclude_paths
116             )
117 
118             if hasattr(exc_type, "__module__"):
119                 exc_module = exc_type.__module__
120                 exc_type = exc_type.__name__
121             else:
122                 exc_module = None
123                 exc_type = exc_type.__name__
124         finally:
125             if new_exc_info:
126                 try:
127                     del exc_info
128                     del exc_traceback
129                 except Exception as e:
130                     logger.exception(e)
131         if "message" in kwargs:
132             message = kwargs["message"]
133         else:
134             message = "%s: %s" % (exc_type, to_unicode(exc_value)) if exc_value else str(exc_type)
135 
136         data = {
137             "id": "%032x" % random.getrandbits(128),
138             "culprit": keyword_field(culprit),
139             "exception": {
140                 "message": message,
141                 "type": keyword_field(str(exc_type)),
142                 "module": keyword_field(str(exc_module)),
143                 "stacktrace": frames,
144             },
145         }
146         if hasattr(exc_value, "_elastic_apm_span_id"):
147             data["parent_id"] = exc_value._elastic_apm_span_id
148             del exc_value._elastic_apm_span_id
149         depth = kwargs.get("_exc_chain_depth", 0)
150         if depth > EXCEPTION_CHAIN_MAX_DEPTH:
151             return
152         cause = exc_value.__cause__
153         chained_context = exc_value.__context__
154 
155         # we follow the pattern of Python itself here and only capture the chained exception
156         # if cause is not None and __suppress_context__ is False
157         if chained_context and not (exc_value.__suppress_context__ and cause is None):
158             if cause:
159                 chained_exc_type = type(cause)
160                 chained_exc_value = cause
161             else:
162                 chained_exc_type = type(chained_context)
163                 chained_exc_value = chained_context
164             chained_exc_info = chained_exc_type, chained_exc_value, chained_context.__traceback__
165 
166             chained_cause = Exception.capture(
167                 client, exc_info=chained_exc_info, culprit="None", _exc_chain_depth=depth + 1
168             )
169             if chained_cause:
170                 data["exception"]["cause"] = [chained_cause["exception"]]
171         return data
172 
173 
174 class Message(BaseEvent):
175     """
176     Messages store the following metadata:
177 
178     - message: 'My message from %s about %s'
179     - params: ('foo', 'bar')
180     """
181 
182     @staticmethod
183     def to_string(client, data):
184         return data["log"]["message"]
185 
186     @staticmethod
187     def get_hash(data):
188         msg = data["param_message"]
189         return [msg["message"]]
190 
191     @staticmethod
192     def capture(client, param_message=None, message=None, level=None, logger_name=None, **kwargs):
193         if message:
194             param_message = {"message": message}
195         params = param_message.get("params")
196         message = param_message["message"] % params if params else param_message["message"]
197         data = kwargs.get("data", {})
198         message_data = {
199             "id": "%032x" % random.getrandbits(128),
200             "log": {
201                 "level": keyword_field(level or "error"),
202                 "logger_name": keyword_field(logger_name or "__root__"),
203                 "message": message,
204                 "param_message": keyword_field(param_message["message"]),
205             },
206         }
207         if isinstance(data.get("stacktrace"), dict):
208             message_data["log"]["stacktrace"] = data["stacktrace"]["frames"]
209         if kwargs.get("exception"):
210             message_data["culprit"] = kwargs["exception"]["culprit"]
211             message_data["exception"] = kwargs["exception"]["exception"]
212         return message_data
213
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/elasticapm/events.py b/elasticapm/events.py
--- a/elasticapm/events.py
+++ b/elasticapm/events.py
@@ -33,7 +33,7 @@
 import sys
 
 from elasticapm.conf.constants import EXCEPTION_CHAIN_MAX_DEPTH
-from elasticapm.utils import varmap
+from elasticapm.utils import encoding, varmap
 from elasticapm.utils.encoding import keyword_field, shorten, to_unicode
 from elasticapm.utils.logging import get_logger
 from elasticapm.utils.stacks import get_culprit, get_stack_info, iter_traceback_frames
@@ -133,6 +133,8 @@
         else:
             message = "%s: %s" % (exc_type, to_unicode(exc_value)) if exc_value else str(exc_type)
 
+        message = encoding.long_field(message)
+
         data = {
             "id": "%032x" % random.getrandbits(128),
             "culprit": keyword_field(culprit),

{"golden_diff": "diff --git a/elasticapm/events.py b/elasticapm/events.py\n--- a/elasticapm/events.py\n+++ b/elasticapm/events.py\n@@ -33,7 +33,7 @@\n import sys\n \n from elasticapm.conf.constants import EXCEPTION_CHAIN_MAX_DEPTH\n-from elasticapm.utils import varmap\n+from elasticapm.utils import encoding, varmap\n from elasticapm.utils.encoding import keyword_field, shorten, to_unicode\n from elasticapm.utils.logging import get_logger\n from elasticapm.utils.stacks import get_culprit, get_stack_info, iter_traceback_frames\n@@ -133,6 +133,8 @@\n         else:\n             message = \"%s: %s\" % (exc_type, to_unicode(exc_value)) if exc_value else str(exc_type)\n \n+        message = encoding.long_field(message)\n+\n         data = {\n             \"id\": \"%032x\" % random.getrandbits(128),\n             \"culprit\": keyword_field(culprit),\n", "issue": "FastAPI/Starlette: Truncate exception message to prevent \"event exceeded the permitted size\" error from APM Server\n**Describe the bug**: \r\nThe capture_exception() method is sending the whole exception message to the APM Server and when the message exceeds the maximum size, the APM Server returns the  \"event exceeded the permitted size\" error. \r\n\r\nI'm sorry if it is not a bug, but I believe that the apm-agent should truncate the message before sending it to the APM Server.\r\n\r\n\r\n**To Reproduce**\r\nCreate a FastAPI route that always raise an error with a large exception message (as the example below) and it will show an \"event exceeded the permitted size\" error on the next attempt to send events to APM server\r\n\r\n```\r\n@app.get('/apm_test/')\r\nasync def apm_test():\r\n    try:\r\n        raise Exception('test' * 1000000)\r\n    except Exception as e:\r\n        apm_client.capture_exception()\r\n    return PlainTextResponse('', 200)\r\n```\r\n\r\n**Environment**\r\n- OS: ubuntu 20.04\r\n- Python version: 3.9.13\r\n- Framework and version: fastapi==0.79.0\r\n- APM Server version: 8.3.0\r\n- Agent version: 6.11.0\r\n\r\n\n", "before_files": [{"content": "#  BSD 3-Clause License\n#\n#  Copyright (c) 2012, the Sentry Team, see AUTHORS for more details\n#  Copyright (c) 2019, Elasticsearch BV\n#  All rights reserved.\n#\n#  Redistribution and use in source and binary forms, with or without\n#  modification, are permitted provided that the following conditions are met:\n#\n#  * Redistributions of source code must retain the above copyright notice, this\n#    list of conditions and the following disclaimer.\n#\n#  * Redistributions in binary form must reproduce the above copyright notice,\n#    this list of conditions and the following disclaimer in the documentation\n#    and/or other materials provided with the distribution.\n#\n#  * Neither the name of the copyright holder nor the names of its\n#    contributors may be used to endorse or promote products derived from\n#    this software without specific prior written permission.\n#\n#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n#  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n#  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n#  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n#  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n#  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n#  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n\n\nimport random\nimport sys\n\nfrom elasticapm.conf.constants import EXCEPTION_CHAIN_MAX_DEPTH\nfrom elasticapm.utils import varmap\nfrom elasticapm.utils.encoding import keyword_field, shorten, to_unicode\nfrom elasticapm.utils.logging import get_logger\nfrom elasticapm.utils.stacks import get_culprit, get_stack_info, iter_traceback_frames\n\n__all__ = (\"BaseEvent\", \"Exception\", \"Message\")\n\nlogger = get_logger(\"elasticapm.events\")\n\n\nclass BaseEvent(object):\n    @staticmethod\n    def to_string(client, data):\n        raise NotImplementedError\n\n    @staticmethod\n    def capture(client, **kwargs):\n        return {}\n\n\nclass Exception(BaseEvent):\n    \"\"\"\n    Exceptions store the following metadata:\n\n    - value: 'My exception value'\n    - type: 'ClassName'\n    - module '__builtin__' (i.e. __builtin__.TypeError)\n    - frames: a list of serialized frames (see _get_traceback_frames)\n    \"\"\"\n\n    @staticmethod\n    def to_string(client, data):\n        exc = data[\"exception\"]\n        if exc[\"value\"]:\n            return \"%s: %s\" % (exc[\"type\"], exc[\"value\"])\n        return exc[\"type\"]\n\n    @staticmethod\n    def get_hash(data):\n        exc = data[\"exception\"]\n        output = [exc[\"type\"]]\n        for frame in data[\"stacktrace\"][\"frames\"]:\n            output.append(frame[\"module\"])\n            output.append(frame[\"function\"])\n        return output\n\n    @staticmethod\n    def capture(client, exc_info=None, **kwargs):\n        culprit = exc_value = exc_type = exc_module = frames = exc_traceback = None\n        new_exc_info = False\n        if not exc_info or exc_info is True:\n            new_exc_info = True\n            exc_info = sys.exc_info()\n\n        if exc_info == (None, None, None):\n            raise ValueError(\"No exception found: capture_exception requires an active exception.\")\n\n        try:\n            exc_type, exc_value, exc_traceback = exc_info\n\n            frames = get_stack_info(\n                iter_traceback_frames(exc_traceback, config=client.config),\n                with_locals=client.config.collect_local_variables in (\"errors\", \"all\"),\n                library_frame_context_lines=client.config.source_lines_error_library_frames,\n                in_app_frame_context_lines=client.config.source_lines_error_app_frames,\n                include_paths_re=client.include_paths_re,\n                exclude_paths_re=client.exclude_paths_re,\n                locals_processor_func=lambda local_var: varmap(\n                    lambda k, val: shorten(\n                        val,\n                        list_length=client.config.local_var_list_max_length,\n                        string_length=client.config.local_var_max_length,\n                        dict_length=client.config.local_var_dict_max_length,\n                    ),\n                    local_var,\n                ),\n            )\n\n            culprit = kwargs.get(\"culprit\", None) or get_culprit(\n                frames, client.config.include_paths, client.config.exclude_paths\n            )\n\n            if hasattr(exc_type, \"__module__\"):\n                exc_module = exc_type.__module__\n                exc_type = exc_type.__name__\n            else:\n                exc_module = None\n                exc_type = exc_type.__name__\n        finally:\n            if new_exc_info:\n                try:\n                    del exc_info\n                    del exc_traceback\n                except Exception as e:\n                    logger.exception(e)\n        if \"message\" in kwargs:\n            message = kwargs[\"message\"]\n        else:\n            message = \"%s: %s\" % (exc_type, to_unicode(exc_value)) if exc_value else str(exc_type)\n\n        data = {\n            \"id\": \"%032x\" % random.getrandbits(128),\n            \"culprit\": keyword_field(culprit),\n            \"exception\": {\n                \"message\": message,\n                \"type\": keyword_field(str(exc_type)),\n                \"module\": keyword_field(str(exc_module)),\n                \"stacktrace\": frames,\n            },\n        }\n        if hasattr(exc_value, \"_elastic_apm_span_id\"):\n            data[\"parent_id\"] = exc_value._elastic_apm_span_id\n            del exc_value._elastic_apm_span_id\n        depth = kwargs.get(\"_exc_chain_depth\", 0)\n        if depth > EXCEPTION_CHAIN_MAX_DEPTH:\n            return\n        cause = exc_value.__cause__\n        chained_context = exc_value.__context__\n\n        # we follow the pattern of Python itself here and only capture the chained exception\n        # if cause is not None and __suppress_context__ is False\n        if chained_context and not (exc_value.__suppress_context__ and cause is None):\n            if cause:\n                chained_exc_type = type(cause)\n                chained_exc_value = cause\n            else:\n                chained_exc_type = type(chained_context)\n                chained_exc_value = chained_context\n            chained_exc_info = chained_exc_type, chained_exc_value, chained_context.__traceback__\n\n            chained_cause = Exception.capture(\n                client, exc_info=chained_exc_info, culprit=\"None\", _exc_chain_depth=depth + 1\n            )\n            if chained_cause:\n                data[\"exception\"][\"cause\"] = [chained_cause[\"exception\"]]\n        return data\n\n\nclass Message(BaseEvent):\n    \"\"\"\n    Messages store the following metadata:\n\n    - message: 'My message from %s about %s'\n    - params: ('foo', 'bar')\n    \"\"\"\n\n    @staticmethod\n    def to_string(client, data):\n        return data[\"log\"][\"message\"]\n\n    @staticmethod\n    def get_hash(data):\n        msg = data[\"param_message\"]\n        return [msg[\"message\"]]\n\n    @staticmethod\n    def capture(client, param_message=None, message=None, level=None, logger_name=None, **kwargs):\n        if message:\n            param_message = {\"message\": message}\n        params = param_message.get(\"params\")\n        message = param_message[\"message\"] % params if params else param_message[\"message\"]\n        data = kwargs.get(\"data\", {})\n        message_data = {\n            \"id\": \"%032x\" % random.getrandbits(128),\n            \"log\": {\n                \"level\": keyword_field(level or \"error\"),\n                \"logger_name\": keyword_field(logger_name or \"__root__\"),\n                \"message\": message,\n                \"param_message\": keyword_field(param_message[\"message\"]),\n            },\n        }\n        if isinstance(data.get(\"stacktrace\"), dict):\n            message_data[\"log\"][\"stacktrace\"] = data[\"stacktrace\"][\"frames\"]\n        if kwargs.get(\"exception\"):\n            message_data[\"culprit\"] = kwargs[\"exception\"][\"culprit\"]\n            message_data[\"exception\"] = kwargs[\"exception\"][\"exception\"]\n        return message_data\n", "path": "elasticapm/events.py"}], "after_files": [{"content": "#  BSD 3-Clause License\n#\n#  Copyright (c) 2012, the Sentry Team, see AUTHORS for more details\n#  Copyright (c) 2019, Elasticsearch BV\n#  All rights reserved.\n#\n#  Redistribution and use in source and binary forms, with or without\n#  modification, are permitted provided that the following conditions are met:\n#\n#  * Redistributions of source code must retain the above copyright notice, this\n#    list of conditions and the following disclaimer.\n#\n#  * Redistributions in binary form must reproduce the above copyright notice,\n#    this list of conditions and the following disclaimer in the documentation\n#    and/or other materials provided with the distribution.\n#\n#  * Neither the name of the copyright holder nor the names of its\n#    contributors may be used to endorse or promote products derived from\n#    this software without specific prior written permission.\n#\n#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n#  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n#  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n#  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n#  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n#  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n#  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n\n\nimport random\nimport sys\n\nfrom elasticapm.conf.constants import EXCEPTION_CHAIN_MAX_DEPTH\nfrom elasticapm.utils import encoding, varmap\nfrom elasticapm.utils.encoding import keyword_field, shorten, to_unicode\nfrom elasticapm.utils.logging import get_logger\nfrom elasticapm.utils.stacks import get_culprit, get_stack_info, iter_traceback_frames\n\n__all__ = (\"BaseEvent\", \"Exception\", \"Message\")\n\nlogger = get_logger(\"elasticapm.events\")\n\n\nclass BaseEvent(object):\n    @staticmethod\n    def to_string(client, data):\n        raise NotImplementedError\n\n    @staticmethod\n    def capture(client, **kwargs):\n        return {}\n\n\nclass Exception(BaseEvent):\n    \"\"\"\n    Exceptions store the following metadata:\n\n    - value: 'My exception value'\n    - type: 'ClassName'\n    - module '__builtin__' (i.e. __builtin__.TypeError)\n    - frames: a list of serialized frames (see _get_traceback_frames)\n    \"\"\"\n\n    @staticmethod\n    def to_string(client, data):\n        exc = data[\"exception\"]\n        if exc[\"value\"]:\n            return \"%s: %s\" % (exc[\"type\"], exc[\"value\"])\n        return exc[\"type\"]\n\n    @staticmethod\n    def get_hash(data):\n        exc = data[\"exception\"]\n        output = [exc[\"type\"]]\n        for frame in data[\"stacktrace\"][\"frames\"]:\n            output.append(frame[\"module\"])\n            output.append(frame[\"function\"])\n        return output\n\n    @staticmethod\n    def capture(client, exc_info=None, **kwargs):\n        culprit = exc_value = exc_type = exc_module = frames = exc_traceback = None\n        new_exc_info = False\n        if not exc_info or exc_info is True:\n            new_exc_info = True\n            exc_info = sys.exc_info()\n\n        if exc_info == (None, None, None):\n            raise ValueError(\"No exception found: capture_exception requires an active exception.\")\n\n        try:\n            exc_type, exc_value, exc_traceback = exc_info\n\n            frames = get_stack_info(\n                iter_traceback_frames(exc_traceback, config=client.config),\n                with_locals=client.config.collect_local_variables in (\"errors\", \"all\"),\n                library_frame_context_lines=client.config.source_lines_error_library_frames,\n                in_app_frame_context_lines=client.config.source_lines_error_app_frames,\n                include_paths_re=client.include_paths_re,\n                exclude_paths_re=client.exclude_paths_re,\n                locals_processor_func=lambda local_var: varmap(\n                    lambda k, val: shorten(\n                        val,\n                        list_length=client.config.local_var_list_max_length,\n                        string_length=client.config.local_var_max_length,\n                        dict_length=client.config.local_var_dict_max_length,\n                    ),\n                    local_var,\n                ),\n            )\n\n            culprit = kwargs.get(\"culprit\", None) or get_culprit(\n                frames, client.config.include_paths, client.config.exclude_paths\n            )\n\n            if hasattr(exc_type, \"__module__\"):\n                exc_module = exc_type.__module__\n                exc_type = exc_type.__name__\n            else:\n                exc_module = None\n                exc_type = exc_type.__name__\n        finally:\n            if new_exc_info:\n                try:\n                    del exc_info\n                    del exc_traceback\n                except Exception as e:\n                    logger.exception(e)\n        if \"message\" in kwargs:\n            message = kwargs[\"message\"]\n        else:\n            message = \"%s: %s\" % (exc_type, to_unicode(exc_value)) if exc_value else str(exc_type)\n\n        message = encoding.long_field(message)\n\n        data = {\n            \"id\": \"%032x\" % random.getrandbits(128),\n            \"culprit\": keyword_field(culprit),\n            \"exception\": {\n                \"message\": message,\n                \"type\": keyword_field(str(exc_type)),\n                \"module\": keyword_field(str(exc_module)),\n                \"stacktrace\": frames,\n            },\n        }\n        if hasattr(exc_value, \"_elastic_apm_span_id\"):\n            data[\"parent_id\"] = exc_value._elastic_apm_span_id\n            del exc_value._elastic_apm_span_id\n        depth = kwargs.get(\"_exc_chain_depth\", 0)\n        if depth > EXCEPTION_CHAIN_MAX_DEPTH:\n            return\n        cause = exc_value.__cause__\n        chained_context = exc_value.__context__\n\n        # we follow the pattern of Python itself here and only capture the chained exception\n        # if cause is not None and __suppress_context__ is False\n        if chained_context and not (exc_value.__suppress_context__ and cause is None):\n            if cause:\n                chained_exc_type = type(cause)\n                chained_exc_value = cause\n            else:\n                chained_exc_type = type(chained_context)\n                chained_exc_value = chained_context\n            chained_exc_info = chained_exc_type, chained_exc_value, chained_context.__traceback__\n\n            chained_cause = Exception.capture(\n                client, exc_info=chained_exc_info, culprit=\"None\", _exc_chain_depth=depth + 1\n            )\n            if chained_cause:\n                data[\"exception\"][\"cause\"] = [chained_cause[\"exception\"]]\n        return data\n\n\nclass Message(BaseEvent):\n    \"\"\"\n    Messages store the following metadata:\n\n    - message: 'My message from %s about %s'\n    - params: ('foo', 'bar')\n    \"\"\"\n\n    @staticmethod\n    def to_string(client, data):\n        return data[\"log\"][\"message\"]\n\n    @staticmethod\n    def get_hash(data):\n        msg = data[\"param_message\"]\n        return [msg[\"message\"]]\n\n    @staticmethod\n    def capture(client, param_message=None, message=None, level=None, logger_name=None, **kwargs):\n        if message:\n            param_message = {\"message\": message}\n        params = param_message.get(\"params\")\n        message = param_message[\"message\"] % params if params else param_message[\"message\"]\n        data = kwargs.get(\"data\", {})\n        message_data = {\n            \"id\": \"%032x\" % random.getrandbits(128),\n            \"log\": {\n                \"level\": keyword_field(level or \"error\"),\n                \"logger_name\": keyword_field(logger_name or \"__root__\"),\n                \"message\": message,\n                \"param_message\": keyword_field(param_message[\"message\"]),\n            },\n        }\n        if isinstance(data.get(\"stacktrace\"), dict):\n            message_data[\"log\"][\"stacktrace\"] = data[\"stacktrace\"][\"frames\"]\n        if kwargs.get(\"exception\"):\n            message_data[\"culprit\"] = kwargs[\"exception\"][\"culprit\"]\n            message_data[\"exception\"] = kwargs[\"exception\"][\"exception\"]\n        return message_data\n", "path": "elasticapm/events.py"}]}