Repository: elastic/apm-agent-python

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



Discussion:
apmmachine: 
## :grey_exclamation: Build Aborted
  
  > There is a new build on-going so the previous on-going builds have been aborted.
  


<!-- BUILD BADGES-->
> _the below badges are clickable and redirect to their specific view in the CI or DOCS_
[![Pipeline View](https://img.shields.io/badge/pipeline-pipeline%20-green)](https://apm-ci.elastic.co/blue/organizations/jenkins/apm-agent-python%2Fapm-agent-python-mbp%2FPR-1619/detail/PR-1619/1//pipeline) [![Test View](https://img.shields.io/badge/test-test-green)](https://apm-ci.elastic.co/blue/organizations/jenkins/apm-agent-python%2Fapm-agent-python-mbp%2FPR-1619/detail/PR-1619/1//tests) [![Changes](https://img.shields.io/badge/changes-changes-green)](https://apm-ci.elastic.co/blue/organizations/jenkins/apm-agent-python%2Fapm-agent-python-mbp%2FPR-1619/detail/PR-1619/1//changes) [![Artifacts](https://img.shields.io/badge/artifacts-artifacts-yellow)](https://apm-ci.elastic.co/blue/organizations/jenkins/apm-agent-python%2Fapm-agent-python-mbp%2FPR-1619/detail/PR-1619/1//artifacts)   [![preview](https://img.shields.io/badge/elastic-observability-blue)](https://ci-stats.elastic.co/app/apm/services/apm-ci/transactions/view?rangeFrom=2022-08-18T22:29:12.830Z&rangeTo=2022-08-18T22:49:12.830Z&transactionName=BUILD+apm-agent-python%2Fapm-agent-python-mbp%2FPR-%7Bnumber%7D&transactionType=job&latencyAggregationType=avg&traceId=c9470f8986ada157c06548ec8b1ceadc&transactionId=ad235ee890e73dc6)

<!-- BUILD SUMMARY-->
<details><summary>Expand to view the summary</summary>
<p>

#### Build stats




* Start Time: 2022-08-18T22:39:12.830+0000


* Duration: 15 min 13 sec




#### Test stats :test_tube:

| Test         | Results                         |
| ------------ | :-----------------------------: |
| Failed       | 0  |
| Passed       | 1433  |
| Skipped      | 1939 |
| Total        | 3372   |


</p>
</details>

<!-- TEST RESULTS IF ANY-->


<!-- STEPS ERRORS IF ANY -->














### Steps errors [![37](https://img.shields.io/badge/37%20-red)](https://apm-ci.elastic.co/blue/organizations/jenkins/apm-agent-python%2Fapm-agent-python-mbp%2FPR-1619/detail/PR-1619/1//pipeline)
  <details><summary>Expand to view the steps failures</summary>
  <p>


> Show only the first 10 steps failures

  
  
  
  
##### `Restore files previously stashed`
<ul>
<li>Took 0 min 0 sec . View more details <a href="https://apm-ci.elastic.co//blue/rest/organizations/jenkins/pipelines/apm-agent-python/pipelines/apm-agent-python-mbp/pipelines/PR-1619/runs/1/steps/2363/log/?start=0">here</a></li>
<li>Description: <code>coverage-python-3.6-graphene-2</code></l1>
</ul>
  
  
  
  
##### `Restore files previously stashed`
<ul>
<li>Took 0 min 0 sec . View more details <a href="https://apm-ci.elastic.co//blue/rest/organizations/jenkins/pipelines/apm-agent-python/pipelines/apm-agent-python-mbp/pipelines/PR-1619/runs/1/steps/2368/log/?start=0">here</a></li>
<li>Description: <code>coverage-python-3.6-httpx-newest</code></l1>
</ul>
  
  
  
  
##### `Restore files previously stashed`
<ul>
<li>Took 0 min 0 sec . View more details <a href="https://apm-ci.elastic.co//blue/rest/organizations/jenkins/pipelines/apm-agent-python/pipelines/apm-agent-python-mbp/pipelines/PR-1619/runs/1/steps/2373/log/?start=0">here</a></li>
<li>Description: <code>coverage-python-3.6-httplib2-newest</code></l1>
</ul>
  
  
  
  
##### `Restore files previously stashed`
<ul>
<li>Took 0 min 0 sec . View more details <a href="https://apm-ci.elastic.co//blue/rest/organizations/jenkins/pipelines/apm-agent-python/pipelines/apm-agent-python-mbp/pipelines/PR-1619/runs/1/steps/2378/log/?start=0">here</a></li>
<li>Description: <code>coverage-python-3.6-prometheus_client-newest</code></l1>
</ul>
  
  
  
  
##### `Restore files previously stashed`
<ul>
<li>Took 0 min 0 sec . View more details <a href="https://apm-ci.elastic.co//blue/rest/organizations/jenkins/pipelines/apm-agent-python/pipelines/apm-agent-python-mbp/pipelines/PR-1619/runs/1/steps/2383/log/?start=0">here</a></li>
<li>Description: <code>coverage-python-3.6-kafka-python-newest</code></l1>
</ul>
  
  
  
  
##### `Restore files previously stashed`
<ul>
<li>Took 0 min 0 sec . View more details <a href="https://apm-ci.elastic.co//blue/rest/organizations/jenkins/pipelines/apm-agent-python/pipelines/apm-agent-python-mbp/pipelines/PR-1619/runs/1/steps/2599/log/?start=0">here</a></li>
<li>Description: <code>coverage-3.7-none</code></l1>
</ul>
  
  
  
  
##### `Restore files previously stashed`
<ul>
<li>Took 0 min 0 sec . View more details <a href="https://apm-ci.elastic.co//blue/rest/organizations/jenkins/pipelines/apm-agent-python/pipelines/apm-agent-python-mbp/pipelines/PR-1619/runs/1/steps/2604/log/?start=0">here</a></li>
<li>Description: <code>coverage-3.8-none</code></l1>
</ul>
  
  
  
  
##### `Restore files previously stashed`
<ul>
<li>Took 0 min 0 sec . View more details <a href="https://apm-ci.elastic.co//blue/rest/organizations/jenkins/pipelines/apm-agent-python/pipelines/apm-agent-python-mbp/pipelines/PR-1619/runs/1/steps/2609/log/?start=0">here</a></li>
<li>Description: <code>coverage-3.9-none</code></l1>
</ul>
  
  
  
  
##### `Archive the artifacts`
<ul>
<li>Took 0 min 0 sec . View more details <a href="https://apm-ci.elastic.co//blue/rest/organizations/jenkins/pipelines/apm-agent-python/pipelines/apm-agent-python-mbp/pipelines/PR-1619/runs/1/steps/2624/log/?start=0">here</a></li>
<li>Description: <code>[2022-08-18T22:54:23.560Z] Archiving artifacts
Python-python-3.6-django-3.1 tests failed : org.jenk</code></l1>
</ul>
  
  
  
  
##### `Error signal`
<ul>
<li>Took 0 min 0 sec . View more details <a href="https://apm-ci.elastic.co//blue/rest/organizations/jenkins/pipelines/apm-agent-python/pipelines/apm-agent-python-mbp/pipelines/PR-1619/runs/1/steps/229/log/?start=0">here</a></li>
<li>Description: <code>Python-python-3.6-none tests failed : org.jenkinsci.plugins.workflow.steps.FlowInterruptedException</code></l1>
</ul>
  
  </p>
  </details>



## :robot: GitHub comments

To re-run your PR in the CI, just comment with:



- `/test` : Re-trigger the build.


- `/test linters` : Run the Python linters only.


- `/test full` : Run the full matrix of tests.


- `/test benchmark` : Run the APM Agent Python benchmarks tests.


- `run` `elasticsearch-ci/docs` : Re-trigger the docs validation. (use unformatted text in the comment!)




<!--COMMENT_GENERATED_WITH_ID_comment.id-->
apmmachine: ### :globe_with_meridians: Coverage report

Name | Metrics % (`covered/total`) | Diff
 --- | --- | ---
Packages | 100.0% (`68/68`) | :green_heart: 
Files | 100.0% (`227/227`) | :green_heart: 
Classes | 100.0% (`227/227`) | :green_heart: 
Lines | 89.755% (`17452/19444`) | :+1: 0.004
Conditionals | 77.18% (`3186/4128`) | :+1: 

<!--COMMENT_GENERATED_WITH_ID_coverage-->

Patch:
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index e5210f3a..07b09fb6 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -41,6 +41,7 @@ endif::[]
 
 * Differentiate Lambda URLs from API Gateway in AWS Lambda integration {pull}#1609[#1609]
 * Restrict the size of Django request bodies to prevent APM Server rejection {pull}#1610[#1610]
+* Restrict length of `exception.message` for exceptions captured by the agent {pull}#1619[#1619]
 
 
 
diff --git a/elasticapm/events.py b/elasticapm/events.py
index 3733b506..d6fe5baf 100644
--- a/elasticapm/events.py
+++ b/elasticapm/events.py
@@ -33,7 +33,7 @@ import random
 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 @@ class Exception(BaseEvent):
         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),


Test patch:
diff --git a/tests/client/exception_tests.py b/tests/client/exception_tests.py
index c4410ff3..f5964683 100644
--- a/tests/client/exception_tests.py
+++ b/tests/client/exception_tests.py
@@ -397,3 +397,13 @@ def test_fail_on_uuid_raise(elasticapm_client):
         generate_uuid()
     except Exception:
         elasticapm_client.capture_exception()
+
+
+def test_long_message(elasticapm_client):
+    try:
+        raise Exception("t" * 1000000)
+    except:
+        elasticapm_client.capture_exception()
+
+    exception = elasticapm_client.events[ERROR][0]
+    assert exception["exception"]["message"] == f'Exception: {"t"*9988}{"…"}'