We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
capture_exception raises exception in UUID __repr__
**Describe the bug**: When capturing exceptions, if the exception is because of a raise in UUID `__init__`, apm agent itself raises with exception:

```
self = <[AttributeError('int') raised in repr()] UUID object at 0x12508cd00>

    def __str__(self):
>       hex = '%032x' % self.int
E       AttributeError: int
```

**To Reproduce**

1. Add this test to `exception_tests.py`:
```python
def test_fail_on_uuid_raise(elasticapm_client):
    def generate_uuid():
        from uuid import UUID
        return UUID("INVALID")

    try:
        generate_uuid()
    except Exception:
        elasticapm_client.capture_exception()
```

**Environment (please complete the following information)**
- OS: MacOS / Linux
- Python version: 3.8.11
- Framework and version [e.g. Django 2.1]: Custom / Starlette
- APM Server version: N/A
- Agent version: 6.3.3


**Additional context**

Add any other context about the problem here.

`stacktrace`:
```
../../elasticapm/base.py:272: in capture_exception
    return self.capture("Exception", exc_info=exc_info, handled=handled, **kwargs)
../../elasticapm/base.py:241: in capture
    data = self._build_msg_for_logging(
../../elasticapm/base.py:469: in _build_msg_for_logging
    result = handler.capture(self, **kwargs)
../../elasticapm/events.py:96: in capture
    frames = get_stack_info(
../../elasticapm/utils/stacks.py:325: in get_stack_info
    result = get_frame_info(
../../elasticapm/utils/stacks.py:294: in get_frame_info
    f_locals = {varname: locals_processor_func(var) for varname, var in compat.iteritems(f_locals)}
../../elasticapm/utils/stacks.py:294: in <dictcomp>
    f_locals = {varname: locals_processor_func(var) for varname, var in compat.iteritems(f_locals)}
../../elasticapm/events.py:103: in <lambda>
    locals_processor_func=lambda local_var: varmap(
../../elasticapm/utils/__init__.py:71: in varmap
    ret = func(name, var, **kwargs)
../../elasticapm/events.py:104: in <lambda>
    lambda k, val: shorten(
../../elasticapm/utils/encoding.py:205: in shorten
    var = transform(var)
../../elasticapm/utils/encoding.py:132: in transform
    ret = repr(value)
/usr/local/Cellar/python@3.8/3.8.11/Frameworks/Python.framework/Versions/3.8/lib/python3.8/uuid.py:268: in __repr__
    return '%s(%r)' % (self.__class__.__name__, str(self))
```


--- 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/utils/encoding.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 #
3 #  BSD 3-Clause License
4 #
5 #  Copyright (c) 2012, the Sentry Team, see AUTHORS for more details
6 #  Copyright (c) 2019, Elasticsearch BV
7 #  All rights reserved.
8 #
9 #  Redistribution and use in source and binary forms, with or without
10 #  modification, are permitted provided that the following conditions are met:
11 #
12 #  * Redistributions of source code must retain the above copyright notice, this
13 #    list of conditions and the following disclaimer.
14 #
15 #  * Redistributions in binary form must reproduce the above copyright notice,
16 #    this list of conditions and the following disclaimer in the documentation
17 #    and/or other materials provided with the distribution.
18 #
19 #  * Neither the name of the copyright holder nor the names of its
20 #    contributors may be used to endorse or promote products derived from
21 #    this software without specific prior written permission.
22 #
23 #  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24 #  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 #  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 #  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
27 #  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 #  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29 #  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30 #  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31 #  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32 
33 
34 import datetime
35 import itertools
36 import uuid
37 from decimal import Decimal
38 
39 from elasticapm.conf.constants import KEYWORD_MAX_LENGTH, LABEL_RE, LABEL_TYPES
40 from elasticapm.utils import compat
41 
42 PROTECTED_TYPES = compat.integer_types + (type(None), float, Decimal, datetime.datetime, datetime.date, datetime.time)
43 
44 
45 def is_protected_type(obj):
46     """Determine if the object instance is of a protected type.
47 
48     Objects of protected types are preserved as-is when passed to
49     force_text(strings_only=True).
50     """
51     return isinstance(obj, PROTECTED_TYPES)
52 
53 
54 def force_text(s, encoding="utf-8", strings_only=False, errors="strict"):
55     """
56     Similar to smart_text, except that lazy instances are resolved to
57     strings, rather than kept as lazy objects.
58 
59     If strings_only is True, don't convert (some) non-string-like objects.
60     """
61     # Handle the common case first, saves 30-40% when s is an instance of
62     # compat.text_type. This function gets called often in that setting.
63     #
64     # Adapted from Django
65     if isinstance(s, compat.text_type):
66         return s
67     if strings_only and is_protected_type(s):
68         return s
69     try:
70         if not isinstance(s, compat.string_types):
71             if hasattr(s, "__unicode__"):
72                 s = s.__unicode__()
73             else:
74                 if compat.PY3:
75                     if isinstance(s, bytes):
76                         s = compat.text_type(s, encoding, errors)
77                     else:
78                         s = compat.text_type(s)
79                 else:
80                     s = compat.text_type(bytes(s), encoding, errors)
81         else:
82             # Note: We use .decode() here, instead of compat.text_type(s, encoding,
83             # errors), so that if s is a SafeBytes, it ends up being a
84             # SafeText at the end.
85             s = s.decode(encoding, errors)
86     except UnicodeDecodeError as e:
87         if not isinstance(s, Exception):
88             raise UnicodeDecodeError(*e.args)
89         else:
90             # If we get to here, the caller has passed in an Exception
91             # subclass populated with non-ASCII bytestring data without a
92             # working unicode method. Try to handle this without raising a
93             # further exception by individually forcing the exception args
94             # to unicode.
95             s = " ".join([force_text(arg, encoding, strings_only, errors) for arg in s])
96     return s
97 
98 
99 def _has_elasticapm_metadata(value):
100     try:
101         return callable(value.__getattribute__("__elasticapm__"))
102     except Exception:
103         return False
104 
105 
106 def transform(value, stack=None, context=None):
107     # TODO: make this extendable
108     if context is None:
109         context = {}
110     if stack is None:
111         stack = []
112 
113     objid = id(value)
114     if objid in context:
115         return "<...>"
116 
117     context[objid] = 1
118     transform_rec = lambda o: transform(o, stack + [value], context)
119 
120     if any(value is s for s in stack):
121         ret = "cycle"
122     elif isinstance(value, (tuple, list, set, frozenset)):
123         try:
124             ret = type(value)(transform_rec(o) for o in value)
125         except Exception:
126             # We may be dealing with a namedtuple
127             class value_type(list):
128                 __name__ = type(value).__name__
129 
130             ret = value_type(transform_rec(o) for o in value)
131     elif isinstance(value, uuid.UUID):
132         ret = repr(value)
133     elif isinstance(value, dict):
134         ret = dict((to_unicode(k), transform_rec(v)) for k, v in compat.iteritems(value))
135     elif isinstance(value, compat.text_type):
136         ret = to_unicode(value)
137     elif isinstance(value, compat.binary_type):
138         ret = to_string(value)
139     elif not isinstance(value, compat.class_types) and _has_elasticapm_metadata(value):
140         ret = transform_rec(value.__elasticapm__())
141     elif isinstance(value, bool):
142         ret = bool(value)
143     elif isinstance(value, float):
144         ret = float(value)
145     elif isinstance(value, int):
146         ret = int(value)
147     elif compat.PY2 and isinstance(value, long):  # noqa F821
148         ret = long(value)  # noqa F821
149     elif value is not None:
150         try:
151             ret = transform(repr(value))
152         except Exception:
153             # It's common case that a model's __unicode__ definition may try to query the database
154             # which if it was not cleaned up correctly, would hit a transaction aborted exception
155             ret = u"<BadRepr: %s>" % type(value)
156     else:
157         ret = None
158     del context[objid]
159     return ret
160 
161 
162 def to_unicode(value):
163     try:
164         value = compat.text_type(force_text(value))
165     except (UnicodeEncodeError, UnicodeDecodeError):
166         value = "(Error decoding value)"
167     except Exception:  # in some cases we get a different exception
168         try:
169             value = compat.binary_type(repr(type(value)))
170         except Exception:
171             value = "(Error decoding value)"
172     return value
173 
174 
175 def to_string(value):
176     try:
177         return compat.binary_type(value.decode("utf-8").encode("utf-8"))
178     except Exception:
179         return to_unicode(value).encode("utf-8")
180 
181 
182 def shorten(var, list_length=50, string_length=200, dict_length=50, **kwargs):
183     """
184     Shorten a given variable based on configurable maximum lengths, leaving
185     breadcrumbs in the object to show that it was shortened.
186 
187     For strings, truncate the string to the max length, and append "..." so
188     the user knows data was lost.
189 
190     For lists, truncate the list to the max length, and append two new strings
191     to the list: "..." and "(<x> more elements)" where <x> is the number of
192     elements removed.
193 
194     For dicts, truncate the dict to the max length (based on number of key/value
195     pairs) and add a new (key, value) pair to the dict:
196     ("...", "(<x> more elements)") where <x> is the number of key/value pairs
197     removed.
198 
199     :param var: Variable to be shortened
200     :param list_length: Max length (in items) of lists
201     :param string_length: Max length (in characters) of strings
202     :param dict_length: Max length (in key/value pairs) of dicts
203     :return: Shortened variable
204     """
205     var = transform(var)
206     if isinstance(var, compat.string_types) and len(var) > string_length:
207         var = var[: string_length - 3] + "..."
208     elif isinstance(var, (list, tuple, set, frozenset)) and len(var) > list_length:
209         # TODO: we should write a real API for storing some metadata with vars when
210         # we get around to doing ref storage
211         var = list(var)[:list_length] + ["...", "(%d more elements)" % (len(var) - list_length,)]
212     elif isinstance(var, dict) and len(var) > dict_length:
213         trimmed_tuples = [(k, v) for (k, v) in itertools.islice(compat.iteritems(var), dict_length)]
214         if "<truncated>" not in var:
215             trimmed_tuples += [("<truncated>", "(%d more elements)" % (len(var) - dict_length))]
216         var = dict(trimmed_tuples)
217     return var
218 
219 
220 def keyword_field(string):
221     """
222     If the given string is longer than KEYWORD_MAX_LENGTH, truncate it to
223     KEYWORD_MAX_LENGTH-1, adding the "…" character at the end.
224     """
225     if not isinstance(string, compat.string_types) or len(string) <= KEYWORD_MAX_LENGTH:
226         return string
227     return string[: KEYWORD_MAX_LENGTH - 1] + u"…"
228 
229 
230 def enforce_label_format(labels):
231     """
232     Enforces label format:
233       * dots, double quotes or stars in keys are replaced by underscores
234       * string values are limited to a length of 1024 characters
235       * values can only be of a limited set of types
236 
237     :param labels: a dictionary of labels
238     :return: a new dictionary with sanitized keys/values
239     """
240     new = {}
241     for key, value in compat.iteritems(labels):
242         if not isinstance(value, LABEL_TYPES):
243             value = keyword_field(compat.text_type(value))
244         new[LABEL_RE.sub("_", compat.text_type(key))] = value
245     return new
246
```
--- 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/utils/encoding.py b/elasticapm/utils/encoding.py
--- a/elasticapm/utils/encoding.py
+++ b/elasticapm/utils/encoding.py
@@ -129,7 +129,10 @@
 
             ret = value_type(transform_rec(o) for o in value)
     elif isinstance(value, uuid.UUID):
-        ret = repr(value)
+        try:
+            ret = repr(value)
+        except AttributeError:
+            ret = None
     elif isinstance(value, dict):
         ret = dict((to_unicode(k), transform_rec(v)) for k, v in compat.iteritems(value))
     elif isinstance(value, compat.text_type):

{"golden_diff": "diff --git a/elasticapm/utils/encoding.py b/elasticapm/utils/encoding.py\n--- a/elasticapm/utils/encoding.py\n+++ b/elasticapm/utils/encoding.py\n@@ -129,7 +129,10 @@\n \n             ret = value_type(transform_rec(o) for o in value)\n     elif isinstance(value, uuid.UUID):\n-        ret = repr(value)\n+        try:\n+            ret = repr(value)\n+        except AttributeError:\n+            ret = None\n     elif isinstance(value, dict):\n         ret = dict((to_unicode(k), transform_rec(v)) for k, v in compat.iteritems(value))\n     elif isinstance(value, compat.text_type):\n", "issue": "capture_exception raises exception in UUID __repr__\n**Describe the bug**: When capturing exceptions, if the exception is because of a raise in UUID `__init__`, apm agent itself raises with exception:\r\n\r\n```\r\nself = <[AttributeError('int') raised in repr()] UUID object at 0x12508cd00>\r\n\r\n    def __str__(self):\r\n>       hex = '%032x' % self.int\r\nE       AttributeError: int\r\n```\r\n\r\n**To Reproduce**\r\n\r\n1. Add this test to `exception_tests.py`:\r\n```python\r\ndef test_fail_on_uuid_raise(elasticapm_client):\r\n    def generate_uuid():\r\n        from uuid import UUID\r\n        return UUID(\"INVALID\")\r\n\r\n    try:\r\n        generate_uuid()\r\n    except Exception:\r\n        elasticapm_client.capture_exception()\r\n```\r\n\r\n**Environment (please complete the following information)**\r\n- OS: MacOS / Linux\r\n- Python version: 3.8.11\r\n- Framework and version [e.g. Django 2.1]: Custom / Starlette\r\n- APM Server version: N/A\r\n- Agent version: 6.3.3\r\n\r\n\r\n**Additional context**\r\n\r\nAdd any other context about the problem here.\r\n\r\n`stacktrace`:\r\n```\r\n../../elasticapm/base.py:272: in capture_exception\r\n    return self.capture(\"Exception\", exc_info=exc_info, handled=handled, **kwargs)\r\n../../elasticapm/base.py:241: in capture\r\n    data = self._build_msg_for_logging(\r\n../../elasticapm/base.py:469: in _build_msg_for_logging\r\n    result = handler.capture(self, **kwargs)\r\n../../elasticapm/events.py:96: in capture\r\n    frames = get_stack_info(\r\n../../elasticapm/utils/stacks.py:325: in get_stack_info\r\n    result = get_frame_info(\r\n../../elasticapm/utils/stacks.py:294: in get_frame_info\r\n    f_locals = {varname: locals_processor_func(var) for varname, var in compat.iteritems(f_locals)}\r\n../../elasticapm/utils/stacks.py:294: in <dictcomp>\r\n    f_locals = {varname: locals_processor_func(var) for varname, var in compat.iteritems(f_locals)}\r\n../../elasticapm/events.py:103: in <lambda>\r\n    locals_processor_func=lambda local_var: varmap(\r\n../../elasticapm/utils/__init__.py:71: in varmap\r\n    ret = func(name, var, **kwargs)\r\n../../elasticapm/events.py:104: in <lambda>\r\n    lambda k, val: shorten(\r\n../../elasticapm/utils/encoding.py:205: in shorten\r\n    var = transform(var)\r\n../../elasticapm/utils/encoding.py:132: in transform\r\n    ret = repr(value)\r\n/usr/local/Cellar/python@3.8/3.8.11/Frameworks/Python.framework/Versions/3.8/lib/python3.8/uuid.py:268: in __repr__\r\n    return '%s(%r)' % (self.__class__.__name__, str(self))\r\n```\r\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n#\n#  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 datetime\nimport itertools\nimport uuid\nfrom decimal import Decimal\n\nfrom elasticapm.conf.constants import KEYWORD_MAX_LENGTH, LABEL_RE, LABEL_TYPES\nfrom elasticapm.utils import compat\n\nPROTECTED_TYPES = compat.integer_types + (type(None), float, Decimal, datetime.datetime, datetime.date, datetime.time)\n\n\ndef is_protected_type(obj):\n    \"\"\"Determine if the object instance is of a protected type.\n\n    Objects of protected types are preserved as-is when passed to\n    force_text(strings_only=True).\n    \"\"\"\n    return isinstance(obj, PROTECTED_TYPES)\n\n\ndef force_text(s, encoding=\"utf-8\", strings_only=False, errors=\"strict\"):\n    \"\"\"\n    Similar to smart_text, except that lazy instances are resolved to\n    strings, rather than kept as lazy objects.\n\n    If strings_only is True, don't convert (some) non-string-like objects.\n    \"\"\"\n    # Handle the common case first, saves 30-40% when s is an instance of\n    # compat.text_type. This function gets called often in that setting.\n    #\n    # Adapted from Django\n    if isinstance(s, compat.text_type):\n        return s\n    if strings_only and is_protected_type(s):\n        return s\n    try:\n        if not isinstance(s, compat.string_types):\n            if hasattr(s, \"__unicode__\"):\n                s = s.__unicode__()\n            else:\n                if compat.PY3:\n                    if isinstance(s, bytes):\n                        s = compat.text_type(s, encoding, errors)\n                    else:\n                        s = compat.text_type(s)\n                else:\n                    s = compat.text_type(bytes(s), encoding, errors)\n        else:\n            # Note: We use .decode() here, instead of compat.text_type(s, encoding,\n            # errors), so that if s is a SafeBytes, it ends up being a\n            # SafeText at the end.\n            s = s.decode(encoding, errors)\n    except UnicodeDecodeError as e:\n        if not isinstance(s, Exception):\n            raise UnicodeDecodeError(*e.args)\n        else:\n            # If we get to here, the caller has passed in an Exception\n            # subclass populated with non-ASCII bytestring data without a\n            # working unicode method. Try to handle this without raising a\n            # further exception by individually forcing the exception args\n            # to unicode.\n            s = \" \".join([force_text(arg, encoding, strings_only, errors) for arg in s])\n    return s\n\n\ndef _has_elasticapm_metadata(value):\n    try:\n        return callable(value.__getattribute__(\"__elasticapm__\"))\n    except Exception:\n        return False\n\n\ndef transform(value, stack=None, context=None):\n    # TODO: make this extendable\n    if context is None:\n        context = {}\n    if stack is None:\n        stack = []\n\n    objid = id(value)\n    if objid in context:\n        return \"<...>\"\n\n    context[objid] = 1\n    transform_rec = lambda o: transform(o, stack + [value], context)\n\n    if any(value is s for s in stack):\n        ret = \"cycle\"\n    elif isinstance(value, (tuple, list, set, frozenset)):\n        try:\n            ret = type(value)(transform_rec(o) for o in value)\n        except Exception:\n            # We may be dealing with a namedtuple\n            class value_type(list):\n                __name__ = type(value).__name__\n\n            ret = value_type(transform_rec(o) for o in value)\n    elif isinstance(value, uuid.UUID):\n        ret = repr(value)\n    elif isinstance(value, dict):\n        ret = dict((to_unicode(k), transform_rec(v)) for k, v in compat.iteritems(value))\n    elif isinstance(value, compat.text_type):\n        ret = to_unicode(value)\n    elif isinstance(value, compat.binary_type):\n        ret = to_string(value)\n    elif not isinstance(value, compat.class_types) and _has_elasticapm_metadata(value):\n        ret = transform_rec(value.__elasticapm__())\n    elif isinstance(value, bool):\n        ret = bool(value)\n    elif isinstance(value, float):\n        ret = float(value)\n    elif isinstance(value, int):\n        ret = int(value)\n    elif compat.PY2 and isinstance(value, long):  # noqa F821\n        ret = long(value)  # noqa F821\n    elif value is not None:\n        try:\n            ret = transform(repr(value))\n        except Exception:\n            # It's common case that a model's __unicode__ definition may try to query the database\n            # which if it was not cleaned up correctly, would hit a transaction aborted exception\n            ret = u\"<BadRepr: %s>\" % type(value)\n    else:\n        ret = None\n    del context[objid]\n    return ret\n\n\ndef to_unicode(value):\n    try:\n        value = compat.text_type(force_text(value))\n    except (UnicodeEncodeError, UnicodeDecodeError):\n        value = \"(Error decoding value)\"\n    except Exception:  # in some cases we get a different exception\n        try:\n            value = compat.binary_type(repr(type(value)))\n        except Exception:\n            value = \"(Error decoding value)\"\n    return value\n\n\ndef to_string(value):\n    try:\n        return compat.binary_type(value.decode(\"utf-8\").encode(\"utf-8\"))\n    except Exception:\n        return to_unicode(value).encode(\"utf-8\")\n\n\ndef shorten(var, list_length=50, string_length=200, dict_length=50, **kwargs):\n    \"\"\"\n    Shorten a given variable based on configurable maximum lengths, leaving\n    breadcrumbs in the object to show that it was shortened.\n\n    For strings, truncate the string to the max length, and append \"...\" so\n    the user knows data was lost.\n\n    For lists, truncate the list to the max length, and append two new strings\n    to the list: \"...\" and \"(<x> more elements)\" where <x> is the number of\n    elements removed.\n\n    For dicts, truncate the dict to the max length (based on number of key/value\n    pairs) and add a new (key, value) pair to the dict:\n    (\"...\", \"(<x> more elements)\") where <x> is the number of key/value pairs\n    removed.\n\n    :param var: Variable to be shortened\n    :param list_length: Max length (in items) of lists\n    :param string_length: Max length (in characters) of strings\n    :param dict_length: Max length (in key/value pairs) of dicts\n    :return: Shortened variable\n    \"\"\"\n    var = transform(var)\n    if isinstance(var, compat.string_types) and len(var) > string_length:\n        var = var[: string_length - 3] + \"...\"\n    elif isinstance(var, (list, tuple, set, frozenset)) and len(var) > list_length:\n        # TODO: we should write a real API for storing some metadata with vars when\n        # we get around to doing ref storage\n        var = list(var)[:list_length] + [\"...\", \"(%d more elements)\" % (len(var) - list_length,)]\n    elif isinstance(var, dict) and len(var) > dict_length:\n        trimmed_tuples = [(k, v) for (k, v) in itertools.islice(compat.iteritems(var), dict_length)]\n        if \"<truncated>\" not in var:\n            trimmed_tuples += [(\"<truncated>\", \"(%d more elements)\" % (len(var) - dict_length))]\n        var = dict(trimmed_tuples)\n    return var\n\n\ndef keyword_field(string):\n    \"\"\"\n    If the given string is longer than KEYWORD_MAX_LENGTH, truncate it to\n    KEYWORD_MAX_LENGTH-1, adding the \"\u2026\" character at the end.\n    \"\"\"\n    if not isinstance(string, compat.string_types) or len(string) <= KEYWORD_MAX_LENGTH:\n        return string\n    return string[: KEYWORD_MAX_LENGTH - 1] + u\"\u2026\"\n\n\ndef enforce_label_format(labels):\n    \"\"\"\n    Enforces label format:\n      * dots, double quotes or stars in keys are replaced by underscores\n      * string values are limited to a length of 1024 characters\n      * values can only be of a limited set of types\n\n    :param labels: a dictionary of labels\n    :return: a new dictionary with sanitized keys/values\n    \"\"\"\n    new = {}\n    for key, value in compat.iteritems(labels):\n        if not isinstance(value, LABEL_TYPES):\n            value = keyword_field(compat.text_type(value))\n        new[LABEL_RE.sub(\"_\", compat.text_type(key))] = value\n    return new\n", "path": "elasticapm/utils/encoding.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n#\n#  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 datetime\nimport itertools\nimport uuid\nfrom decimal import Decimal\n\nfrom elasticapm.conf.constants import KEYWORD_MAX_LENGTH, LABEL_RE, LABEL_TYPES\nfrom elasticapm.utils import compat\n\nPROTECTED_TYPES = compat.integer_types + (type(None), float, Decimal, datetime.datetime, datetime.date, datetime.time)\n\n\ndef is_protected_type(obj):\n    \"\"\"Determine if the object instance is of a protected type.\n\n    Objects of protected types are preserved as-is when passed to\n    force_text(strings_only=True).\n    \"\"\"\n    return isinstance(obj, PROTECTED_TYPES)\n\n\ndef force_text(s, encoding=\"utf-8\", strings_only=False, errors=\"strict\"):\n    \"\"\"\n    Similar to smart_text, except that lazy instances are resolved to\n    strings, rather than kept as lazy objects.\n\n    If strings_only is True, don't convert (some) non-string-like objects.\n    \"\"\"\n    # Handle the common case first, saves 30-40% when s is an instance of\n    # compat.text_type. This function gets called often in that setting.\n    #\n    # Adapted from Django\n    if isinstance(s, compat.text_type):\n        return s\n    if strings_only and is_protected_type(s):\n        return s\n    try:\n        if not isinstance(s, compat.string_types):\n            if hasattr(s, \"__unicode__\"):\n                s = s.__unicode__()\n            else:\n                if compat.PY3:\n                    if isinstance(s, bytes):\n                        s = compat.text_type(s, encoding, errors)\n                    else:\n                        s = compat.text_type(s)\n                else:\n                    s = compat.text_type(bytes(s), encoding, errors)\n        else:\n            # Note: We use .decode() here, instead of compat.text_type(s, encoding,\n            # errors), so that if s is a SafeBytes, it ends up being a\n            # SafeText at the end.\n            s = s.decode(encoding, errors)\n    except UnicodeDecodeError as e:\n        if not isinstance(s, Exception):\n            raise UnicodeDecodeError(*e.args)\n        else:\n            # If we get to here, the caller has passed in an Exception\n            # subclass populated with non-ASCII bytestring data without a\n            # working unicode method. Try to handle this without raising a\n            # further exception by individually forcing the exception args\n            # to unicode.\n            s = \" \".join([force_text(arg, encoding, strings_only, errors) for arg in s])\n    return s\n\n\ndef _has_elasticapm_metadata(value):\n    try:\n        return callable(value.__getattribute__(\"__elasticapm__\"))\n    except Exception:\n        return False\n\n\ndef transform(value, stack=None, context=None):\n    # TODO: make this extendable\n    if context is None:\n        context = {}\n    if stack is None:\n        stack = []\n\n    objid = id(value)\n    if objid in context:\n        return \"<...>\"\n\n    context[objid] = 1\n    transform_rec = lambda o: transform(o, stack + [value], context)\n\n    if any(value is s for s in stack):\n        ret = \"cycle\"\n    elif isinstance(value, (tuple, list, set, frozenset)):\n        try:\n            ret = type(value)(transform_rec(o) for o in value)\n        except Exception:\n            # We may be dealing with a namedtuple\n            class value_type(list):\n                __name__ = type(value).__name__\n\n            ret = value_type(transform_rec(o) for o in value)\n    elif isinstance(value, uuid.UUID):\n        try:\n            ret = repr(value)\n        except AttributeError:\n            ret = None\n    elif isinstance(value, dict):\n        ret = dict((to_unicode(k), transform_rec(v)) for k, v in compat.iteritems(value))\n    elif isinstance(value, compat.text_type):\n        ret = to_unicode(value)\n    elif isinstance(value, compat.binary_type):\n        ret = to_string(value)\n    elif not isinstance(value, compat.class_types) and _has_elasticapm_metadata(value):\n        ret = transform_rec(value.__elasticapm__())\n    elif isinstance(value, bool):\n        ret = bool(value)\n    elif isinstance(value, float):\n        ret = float(value)\n    elif isinstance(value, int):\n        ret = int(value)\n    elif compat.PY2 and isinstance(value, long):  # noqa F821\n        ret = long(value)  # noqa F821\n    elif value is not None:\n        try:\n            ret = transform(repr(value))\n        except Exception:\n            # It's common case that a model's __unicode__ definition may try to query the database\n            # which if it was not cleaned up correctly, would hit a transaction aborted exception\n            ret = u\"<BadRepr: %s>\" % type(value)\n    else:\n        ret = None\n    del context[objid]\n    return ret\n\n\ndef to_unicode(value):\n    try:\n        value = compat.text_type(force_text(value))\n    except (UnicodeEncodeError, UnicodeDecodeError):\n        value = \"(Error decoding value)\"\n    except Exception:  # in some cases we get a different exception\n        try:\n            value = compat.binary_type(repr(type(value)))\n        except Exception:\n            value = \"(Error decoding value)\"\n    return value\n\n\ndef to_string(value):\n    try:\n        return compat.binary_type(value.decode(\"utf-8\").encode(\"utf-8\"))\n    except Exception:\n        return to_unicode(value).encode(\"utf-8\")\n\n\ndef shorten(var, list_length=50, string_length=200, dict_length=50, **kwargs):\n    \"\"\"\n    Shorten a given variable based on configurable maximum lengths, leaving\n    breadcrumbs in the object to show that it was shortened.\n\n    For strings, truncate the string to the max length, and append \"...\" so\n    the user knows data was lost.\n\n    For lists, truncate the list to the max length, and append two new strings\n    to the list: \"...\" and \"(<x> more elements)\" where <x> is the number of\n    elements removed.\n\n    For dicts, truncate the dict to the max length (based on number of key/value\n    pairs) and add a new (key, value) pair to the dict:\n    (\"...\", \"(<x> more elements)\") where <x> is the number of key/value pairs\n    removed.\n\n    :param var: Variable to be shortened\n    :param list_length: Max length (in items) of lists\n    :param string_length: Max length (in characters) of strings\n    :param dict_length: Max length (in key/value pairs) of dicts\n    :return: Shortened variable\n    \"\"\"\n    var = transform(var)\n    if isinstance(var, compat.string_types) and len(var) > string_length:\n        var = var[: string_length - 3] + \"...\"\n    elif isinstance(var, (list, tuple, set, frozenset)) and len(var) > list_length:\n        # TODO: we should write a real API for storing some metadata with vars when\n        # we get around to doing ref storage\n        var = list(var)[:list_length] + [\"...\", \"(%d more elements)\" % (len(var) - list_length,)]\n    elif isinstance(var, dict) and len(var) > dict_length:\n        trimmed_tuples = [(k, v) for (k, v) in itertools.islice(compat.iteritems(var), dict_length)]\n        if \"<truncated>\" not in var:\n            trimmed_tuples += [(\"<truncated>\", \"(%d more elements)\" % (len(var) - dict_length))]\n        var = dict(trimmed_tuples)\n    return var\n\n\ndef keyword_field(string):\n    \"\"\"\n    If the given string is longer than KEYWORD_MAX_LENGTH, truncate it to\n    KEYWORD_MAX_LENGTH-1, adding the \"\u2026\" character at the end.\n    \"\"\"\n    if not isinstance(string, compat.string_types) or len(string) <= KEYWORD_MAX_LENGTH:\n        return string\n    return string[: KEYWORD_MAX_LENGTH - 1] + u\"\u2026\"\n\n\ndef enforce_label_format(labels):\n    \"\"\"\n    Enforces label format:\n      * dots, double quotes or stars in keys are replaced by underscores\n      * string values are limited to a length of 1024 characters\n      * values can only be of a limited set of types\n\n    :param labels: a dictionary of labels\n    :return: a new dictionary with sanitized keys/values\n    \"\"\"\n    new = {}\n    for key, value in compat.iteritems(labels):\n        if not isinstance(value, LABEL_TYPES):\n            value = keyword_field(compat.text_type(value))\n        new[LABEL_RE.sub(\"_\", compat.text_type(key))] = value\n    return new\n", "path": "elasticapm/utils/encoding.py"}]}