We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Cookie value from request header is not getting sanitized
Hi, 

The APM python agent is silently throwing error when trying to sanitize the cookie value from request header. On debugging, I found that we are reading the cookie value incorrectly from context. 
 
Instead of  reading the cookie value using `event["context"]["request"]["headers"]["Cookie"]`, we read it as `event["context"]["request"]["headers"]["cookie"]`. 

https://github.com/elastic/apm-agent-python/blob/31c858595db709c766226e5cddb826d27e377d9d/elasticapm/processors.py#L116

--- 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/processors.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 warnings
33 from collections import defaultdict
34 
35 from elasticapm.conf.constants import BASE_SANITIZE_FIELD_NAMES, ERROR, MASK, SPAN, TRANSACTION
36 from elasticapm.utils import compat, varmap
37 from elasticapm.utils.encoding import force_text
38 from elasticapm.utils.stacks import get_lines_from_file
39 
40 
41 def for_events(*events):
42     """
43     :param events: list of event types
44     Only calls wrapped function if given event_type is in list of events
45     """
46     events = set(events)
47 
48     def wrap(func):
49         func.event_types = events
50         return func
51 
52     return wrap
53 
54 
55 @for_events(ERROR, TRANSACTION)
56 def remove_http_request_body(client, event):
57     """
58     Removes request.body from context
59     :param client: an ElasticAPM client
60     :param event: a transaction or error event
61     :return: The modified event
62     """
63     if "context" in event and "request" in event["context"]:
64         event["context"]["request"].pop("body", None)
65     return event
66 
67 
68 @for_events(ERROR, SPAN)
69 def remove_stacktrace_locals(client, event):
70     """
71     Removes local variables from any frames.
72     :param client: an ElasticAPM client
73     :param event: a transaction or error event
74     :return: The modified event
75     """
76     func = lambda frame: frame.pop("vars", None)
77     return _process_stack_frames(event, func)
78 
79 
80 @for_events(ERROR, SPAN)
81 def sanitize_stacktrace_locals(client, event):
82     """
83     Sanitizes local variables in all frames
84     :param client: an ElasticAPM client
85     :param event: a transaction or error event
86     :return: The modified event
87     """
88 
89     def func(frame):
90         if "vars" in frame:
91             frame["vars"] = varmap(_sanitize, frame["vars"], sanitize_field_names=client.config.sanitize_field_names)
92 
93     return _process_stack_frames(event, func)
94 
95 
96 @for_events(ERROR, TRANSACTION)
97 def sanitize_http_request_cookies(client, event):
98     """
99     Sanitizes http request cookies
100     :param client: an ElasticAPM client
101     :param event: a transaction or error event
102     :return: The modified event
103     """
104 
105     # sanitize request.cookies dict
106     try:
107         cookies = event["context"]["request"]["cookies"]
108         event["context"]["request"]["cookies"] = varmap(
109             _sanitize, cookies, sanitize_field_names=client.config.sanitize_field_names
110         )
111     except (KeyError, TypeError):
112         pass
113 
114     # sanitize request.header.cookie string
115     try:
116         cookie_string = force_text(event["context"]["request"]["headers"]["cookie"], errors="replace")
117         event["context"]["request"]["headers"]["cookie"] = _sanitize_string(
118             cookie_string, "; ", "=", sanitize_field_names=client.config.sanitize_field_names
119         )
120     except (KeyError, TypeError):
121         pass
122     return event
123 
124 
125 @for_events(ERROR, TRANSACTION)
126 def sanitize_http_response_cookies(client, event):
127     """
128     Sanitizes the set-cookie header of the response
129     :param client: an ElasticAPM client
130     :param event: a transaction or error event
131     :return: The modified event
132     """
133     try:
134         cookie_string = force_text(event["context"]["response"]["headers"]["set-cookie"], errors="replace")
135         event["context"]["response"]["headers"]["set-cookie"] = _sanitize_string(
136             cookie_string, ";", "=", sanitize_field_names=client.config.sanitize_field_names
137         )
138     except (KeyError, TypeError):
139         pass
140     return event
141 
142 
143 @for_events(ERROR, TRANSACTION)
144 def sanitize_http_headers(client, event):
145     """
146     Sanitizes http request/response headers
147     :param client: an ElasticAPM client
148     :param event: a transaction or error event
149     :return: The modified event
150     """
151     # request headers
152     try:
153         headers = event["context"]["request"]["headers"]
154         event["context"]["request"]["headers"] = varmap(
155             _sanitize, headers, sanitize_field_names=client.config.sanitize_field_names
156         )
157     except (KeyError, TypeError):
158         pass
159 
160     # response headers
161     try:
162         headers = event["context"]["response"]["headers"]
163         event["context"]["response"]["headers"] = varmap(
164             _sanitize, headers, sanitize_field_names=client.config.sanitize_field_names
165         )
166     except (KeyError, TypeError):
167         pass
168 
169     return event
170 
171 
172 @for_events(ERROR, TRANSACTION)
173 def sanitize_http_wsgi_env(client, event):
174     """
175     Sanitizes WSGI environment variables
176     :param client: an ElasticAPM client
177     :param event: a transaction or error event
178     :return: The modified event
179     """
180     try:
181         env = event["context"]["request"]["env"]
182         event["context"]["request"]["env"] = varmap(
183             _sanitize, env, sanitize_field_names=client.config.sanitize_field_names
184         )
185     except (KeyError, TypeError):
186         pass
187     return event
188 
189 
190 @for_events(ERROR, TRANSACTION)
191 def sanitize_http_request_body(client, event):
192     """
193     Sanitizes http request body. This only works if the request body
194     is a query-encoded string. Other types (e.g. JSON) are not handled by
195     this sanitizer.
196     :param client: an ElasticAPM client
197     :param event: a transaction or error event
198     :return: The modified event
199     """
200     try:
201         body = force_text(event["context"]["request"]["body"], errors="replace")
202     except (KeyError, TypeError):
203         return event
204     if "=" in body:
205         sanitized_query_string = _sanitize_string(
206             body, "&", "=", sanitize_field_names=client.config.sanitize_field_names
207         )
208         event["context"]["request"]["body"] = sanitized_query_string
209     return event
210 
211 
212 @for_events(ERROR, SPAN)
213 def add_context_lines_to_frames(client, event):
214     # divide frames up into source files before reading from disk. This should help
215     # with utilizing the disk cache better
216     #
217     # TODO: further optimize by only opening each file once and reading all needed source
218     # TODO: blocks at once.
219     per_file = defaultdict(list)
220     _process_stack_frames(
221         event,
222         lambda frame: per_file[frame["context_metadata"][0]].append(frame) if "context_metadata" in frame else None,
223     )
224     for filename, frames in compat.iteritems(per_file):
225         for frame in frames:
226             # context_metadata key has been set in elasticapm.utils.stacks.get_frame_info for
227             # all frames for which we should gather source code context lines
228             fname, lineno, context_lines, loader, module_name = frame.pop("context_metadata")
229             pre_context, context_line, post_context = get_lines_from_file(
230                 fname, lineno, context_lines, loader, module_name
231             )
232             if context_line:
233                 frame["pre_context"] = pre_context
234                 frame["context_line"] = context_line
235                 frame["post_context"] = post_context
236     return event
237 
238 
239 @for_events(ERROR, SPAN)
240 def mark_in_app_frames(client, event):
241     warnings.warn(
242         "The mark_in_app_frames processor is deprecated and can be removed from your PROCESSORS setting",
243         DeprecationWarning,
244     )
245     return event
246 
247 
248 def _sanitize(key, value, **kwargs):
249     if "sanitize_field_names" in kwargs:
250         sanitize_field_names = kwargs["sanitize_field_names"]
251     else:
252         sanitize_field_names = BASE_SANITIZE_FIELD_NAMES
253 
254     if value is None:
255         return
256 
257     if isinstance(value, dict):
258         # varmap will call _sanitize on each k:v pair of the dict, so we don't
259         # have to do anything with dicts here
260         return value
261 
262     if not key:  # key can be a NoneType
263         return value
264 
265     key = key.lower()
266     for field in sanitize_field_names:
267         if field.match(key.strip()):
268             # store mask as a fixed length for security
269             return MASK
270     return value
271 
272 
273 def _sanitize_string(unsanitized, itemsep, kvsep, sanitize_field_names=BASE_SANITIZE_FIELD_NAMES):
274     """
275     sanitizes a string that contains multiple key/value items
276     :param unsanitized: the unsanitized string
277     :param itemsep: string that separates items
278     :param kvsep: string that separates key from value
279     :param sanitize_field_names: field names to pass to _sanitize
280     :return: a sanitized string
281     """
282     sanitized = []
283     kvs = unsanitized.split(itemsep)
284     for kv in kvs:
285         kv = kv.split(kvsep)
286         if len(kv) == 2:
287             sanitized.append((kv[0], _sanitize(kv[0], kv[1], sanitize_field_names=sanitize_field_names)))
288         else:
289             sanitized.append(kv)
290     return itemsep.join(kvsep.join(kv) for kv in sanitized)
291 
292 
293 def _process_stack_frames(event, func):
294     if "stacktrace" in event:
295         for frame in event["stacktrace"]:
296             func(frame)
297     # an error can have two stacktraces, one in "exception", one in "log"
298     if "exception" in event and "stacktrace" in event["exception"]:
299         for frame in event["exception"]["stacktrace"]:
300             func(frame)
301         # check for chained exceptions
302         cause = event["exception"].get("cause", None)
303         while cause:
304             if "stacktrace" in cause[0]:
305                 for frame in cause[0]["stacktrace"]:
306                     func(frame)
307             cause = cause[0].get("cause", None)
308     if "log" in event and "stacktrace" in event["log"]:
309         for frame in event["log"]["stacktrace"]:
310             func(frame)
311     return event
312
```
--- 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/processors.py b/elasticapm/processors.py
--- a/elasticapm/processors.py
+++ b/elasticapm/processors.py
@@ -118,7 +118,14 @@
             cookie_string, "; ", "=", sanitize_field_names=client.config.sanitize_field_names
         )
     except (KeyError, TypeError):
-        pass
+        try:
+            # Sometimes it's Cookie, not cookie
+            cookie_string = force_text(event["context"]["request"]["headers"]["Cookie"], errors="replace")
+            event["context"]["request"]["headers"]["Cookie"] = _sanitize_string(
+                cookie_string, "; ", "=", sanitize_field_names=client.config.sanitize_field_names
+            )
+        except (KeyError, TypeError):
+            pass
     return event

{"golden_diff": "diff --git a/elasticapm/processors.py b/elasticapm/processors.py\n--- a/elasticapm/processors.py\n+++ b/elasticapm/processors.py\n@@ -118,7 +118,14 @@\n             cookie_string, \"; \", \"=\", sanitize_field_names=client.config.sanitize_field_names\n         )\n     except (KeyError, TypeError):\n-        pass\n+        try:\n+            # Sometimes it's Cookie, not cookie\n+            cookie_string = force_text(event[\"context\"][\"request\"][\"headers\"][\"Cookie\"], errors=\"replace\")\n+            event[\"context\"][\"request\"][\"headers\"][\"Cookie\"] = _sanitize_string(\n+                cookie_string, \"; \", \"=\", sanitize_field_names=client.config.sanitize_field_names\n+            )\n+        except (KeyError, TypeError):\n+            pass\n     return event\n", "issue": "Cookie value from request header is not getting sanitized\nHi, \r\n\r\nThe APM python agent is silently throwing error when trying to sanitize the cookie value from request header. On debugging, I found that we are reading the cookie value incorrectly from context. \r\n \r\nInstead of  reading the cookie value using `event[\"context\"][\"request\"][\"headers\"][\"Cookie\"]`, we read it as `event[\"context\"][\"request\"][\"headers\"][\"cookie\"]`. \r\n\r\nhttps://github.com/elastic/apm-agent-python/blob/31c858595db709c766226e5cddb826d27e377d9d/elasticapm/processors.py#L116\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 warnings\nfrom collections import defaultdict\n\nfrom elasticapm.conf.constants import BASE_SANITIZE_FIELD_NAMES, ERROR, MASK, SPAN, TRANSACTION\nfrom elasticapm.utils import compat, varmap\nfrom elasticapm.utils.encoding import force_text\nfrom elasticapm.utils.stacks import get_lines_from_file\n\n\ndef for_events(*events):\n    \"\"\"\n    :param events: list of event types\n    Only calls wrapped function if given event_type is in list of events\n    \"\"\"\n    events = set(events)\n\n    def wrap(func):\n        func.event_types = events\n        return func\n\n    return wrap\n\n\n@for_events(ERROR, TRANSACTION)\ndef remove_http_request_body(client, event):\n    \"\"\"\n    Removes request.body from context\n    :param client: an ElasticAPM client\n    :param event: a transaction or error event\n    :return: The modified event\n    \"\"\"\n    if \"context\" in event and \"request\" in event[\"context\"]:\n        event[\"context\"][\"request\"].pop(\"body\", None)\n    return event\n\n\n@for_events(ERROR, SPAN)\ndef remove_stacktrace_locals(client, event):\n    \"\"\"\n    Removes local variables from any frames.\n    :param client: an ElasticAPM client\n    :param event: a transaction or error event\n    :return: The modified event\n    \"\"\"\n    func = lambda frame: frame.pop(\"vars\", None)\n    return _process_stack_frames(event, func)\n\n\n@for_events(ERROR, SPAN)\ndef sanitize_stacktrace_locals(client, event):\n    \"\"\"\n    Sanitizes local variables in all frames\n    :param client: an ElasticAPM client\n    :param event: a transaction or error event\n    :return: The modified event\n    \"\"\"\n\n    def func(frame):\n        if \"vars\" in frame:\n            frame[\"vars\"] = varmap(_sanitize, frame[\"vars\"], sanitize_field_names=client.config.sanitize_field_names)\n\n    return _process_stack_frames(event, func)\n\n\n@for_events(ERROR, TRANSACTION)\ndef sanitize_http_request_cookies(client, event):\n    \"\"\"\n    Sanitizes http request cookies\n    :param client: an ElasticAPM client\n    :param event: a transaction or error event\n    :return: The modified event\n    \"\"\"\n\n    # sanitize request.cookies dict\n    try:\n        cookies = event[\"context\"][\"request\"][\"cookies\"]\n        event[\"context\"][\"request\"][\"cookies\"] = varmap(\n            _sanitize, cookies, sanitize_field_names=client.config.sanitize_field_names\n        )\n    except (KeyError, TypeError):\n        pass\n\n    # sanitize request.header.cookie string\n    try:\n        cookie_string = force_text(event[\"context\"][\"request\"][\"headers\"][\"cookie\"], errors=\"replace\")\n        event[\"context\"][\"request\"][\"headers\"][\"cookie\"] = _sanitize_string(\n            cookie_string, \"; \", \"=\", sanitize_field_names=client.config.sanitize_field_names\n        )\n    except (KeyError, TypeError):\n        pass\n    return event\n\n\n@for_events(ERROR, TRANSACTION)\ndef sanitize_http_response_cookies(client, event):\n    \"\"\"\n    Sanitizes the set-cookie header of the response\n    :param client: an ElasticAPM client\n    :param event: a transaction or error event\n    :return: The modified event\n    \"\"\"\n    try:\n        cookie_string = force_text(event[\"context\"][\"response\"][\"headers\"][\"set-cookie\"], errors=\"replace\")\n        event[\"context\"][\"response\"][\"headers\"][\"set-cookie\"] = _sanitize_string(\n            cookie_string, \";\", \"=\", sanitize_field_names=client.config.sanitize_field_names\n        )\n    except (KeyError, TypeError):\n        pass\n    return event\n\n\n@for_events(ERROR, TRANSACTION)\ndef sanitize_http_headers(client, event):\n    \"\"\"\n    Sanitizes http request/response headers\n    :param client: an ElasticAPM client\n    :param event: a transaction or error event\n    :return: The modified event\n    \"\"\"\n    # request headers\n    try:\n        headers = event[\"context\"][\"request\"][\"headers\"]\n        event[\"context\"][\"request\"][\"headers\"] = varmap(\n            _sanitize, headers, sanitize_field_names=client.config.sanitize_field_names\n        )\n    except (KeyError, TypeError):\n        pass\n\n    # response headers\n    try:\n        headers = event[\"context\"][\"response\"][\"headers\"]\n        event[\"context\"][\"response\"][\"headers\"] = varmap(\n            _sanitize, headers, sanitize_field_names=client.config.sanitize_field_names\n        )\n    except (KeyError, TypeError):\n        pass\n\n    return event\n\n\n@for_events(ERROR, TRANSACTION)\ndef sanitize_http_wsgi_env(client, event):\n    \"\"\"\n    Sanitizes WSGI environment variables\n    :param client: an ElasticAPM client\n    :param event: a transaction or error event\n    :return: The modified event\n    \"\"\"\n    try:\n        env = event[\"context\"][\"request\"][\"env\"]\n        event[\"context\"][\"request\"][\"env\"] = varmap(\n            _sanitize, env, sanitize_field_names=client.config.sanitize_field_names\n        )\n    except (KeyError, TypeError):\n        pass\n    return event\n\n\n@for_events(ERROR, TRANSACTION)\ndef sanitize_http_request_body(client, event):\n    \"\"\"\n    Sanitizes http request body. This only works if the request body\n    is a query-encoded string. Other types (e.g. JSON) are not handled by\n    this sanitizer.\n    :param client: an ElasticAPM client\n    :param event: a transaction or error event\n    :return: The modified event\n    \"\"\"\n    try:\n        body = force_text(event[\"context\"][\"request\"][\"body\"], errors=\"replace\")\n    except (KeyError, TypeError):\n        return event\n    if \"=\" in body:\n        sanitized_query_string = _sanitize_string(\n            body, \"&\", \"=\", sanitize_field_names=client.config.sanitize_field_names\n        )\n        event[\"context\"][\"request\"][\"body\"] = sanitized_query_string\n    return event\n\n\n@for_events(ERROR, SPAN)\ndef add_context_lines_to_frames(client, event):\n    # divide frames up into source files before reading from disk. This should help\n    # with utilizing the disk cache better\n    #\n    # TODO: further optimize by only opening each file once and reading all needed source\n    # TODO: blocks at once.\n    per_file = defaultdict(list)\n    _process_stack_frames(\n        event,\n        lambda frame: per_file[frame[\"context_metadata\"][0]].append(frame) if \"context_metadata\" in frame else None,\n    )\n    for filename, frames in compat.iteritems(per_file):\n        for frame in frames:\n            # context_metadata key has been set in elasticapm.utils.stacks.get_frame_info for\n            # all frames for which we should gather source code context lines\n            fname, lineno, context_lines, loader, module_name = frame.pop(\"context_metadata\")\n            pre_context, context_line, post_context = get_lines_from_file(\n                fname, lineno, context_lines, loader, module_name\n            )\n            if context_line:\n                frame[\"pre_context\"] = pre_context\n                frame[\"context_line\"] = context_line\n                frame[\"post_context\"] = post_context\n    return event\n\n\n@for_events(ERROR, SPAN)\ndef mark_in_app_frames(client, event):\n    warnings.warn(\n        \"The mark_in_app_frames processor is deprecated and can be removed from your PROCESSORS setting\",\n        DeprecationWarning,\n    )\n    return event\n\n\ndef _sanitize(key, value, **kwargs):\n    if \"sanitize_field_names\" in kwargs:\n        sanitize_field_names = kwargs[\"sanitize_field_names\"]\n    else:\n        sanitize_field_names = BASE_SANITIZE_FIELD_NAMES\n\n    if value is None:\n        return\n\n    if isinstance(value, dict):\n        # varmap will call _sanitize on each k:v pair of the dict, so we don't\n        # have to do anything with dicts here\n        return value\n\n    if not key:  # key can be a NoneType\n        return value\n\n    key = key.lower()\n    for field in sanitize_field_names:\n        if field.match(key.strip()):\n            # store mask as a fixed length for security\n            return MASK\n    return value\n\n\ndef _sanitize_string(unsanitized, itemsep, kvsep, sanitize_field_names=BASE_SANITIZE_FIELD_NAMES):\n    \"\"\"\n    sanitizes a string that contains multiple key/value items\n    :param unsanitized: the unsanitized string\n    :param itemsep: string that separates items\n    :param kvsep: string that separates key from value\n    :param sanitize_field_names: field names to pass to _sanitize\n    :return: a sanitized string\n    \"\"\"\n    sanitized = []\n    kvs = unsanitized.split(itemsep)\n    for kv in kvs:\n        kv = kv.split(kvsep)\n        if len(kv) == 2:\n            sanitized.append((kv[0], _sanitize(kv[0], kv[1], sanitize_field_names=sanitize_field_names)))\n        else:\n            sanitized.append(kv)\n    return itemsep.join(kvsep.join(kv) for kv in sanitized)\n\n\ndef _process_stack_frames(event, func):\n    if \"stacktrace\" in event:\n        for frame in event[\"stacktrace\"]:\n            func(frame)\n    # an error can have two stacktraces, one in \"exception\", one in \"log\"\n    if \"exception\" in event and \"stacktrace\" in event[\"exception\"]:\n        for frame in event[\"exception\"][\"stacktrace\"]:\n            func(frame)\n        # check for chained exceptions\n        cause = event[\"exception\"].get(\"cause\", None)\n        while cause:\n            if \"stacktrace\" in cause[0]:\n                for frame in cause[0][\"stacktrace\"]:\n                    func(frame)\n            cause = cause[0].get(\"cause\", None)\n    if \"log\" in event and \"stacktrace\" in event[\"log\"]:\n        for frame in event[\"log\"][\"stacktrace\"]:\n            func(frame)\n    return event\n", "path": "elasticapm/processors.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 warnings\nfrom collections import defaultdict\n\nfrom elasticapm.conf.constants import BASE_SANITIZE_FIELD_NAMES, ERROR, MASK, SPAN, TRANSACTION\nfrom elasticapm.utils import compat, varmap\nfrom elasticapm.utils.encoding import force_text\nfrom elasticapm.utils.stacks import get_lines_from_file\n\n\ndef for_events(*events):\n    \"\"\"\n    :param events: list of event types\n    Only calls wrapped function if given event_type is in list of events\n    \"\"\"\n    events = set(events)\n\n    def wrap(func):\n        func.event_types = events\n        return func\n\n    return wrap\n\n\n@for_events(ERROR, TRANSACTION)\ndef remove_http_request_body(client, event):\n    \"\"\"\n    Removes request.body from context\n    :param client: an ElasticAPM client\n    :param event: a transaction or error event\n    :return: The modified event\n    \"\"\"\n    if \"context\" in event and \"request\" in event[\"context\"]:\n        event[\"context\"][\"request\"].pop(\"body\", None)\n    return event\n\n\n@for_events(ERROR, SPAN)\ndef remove_stacktrace_locals(client, event):\n    \"\"\"\n    Removes local variables from any frames.\n    :param client: an ElasticAPM client\n    :param event: a transaction or error event\n    :return: The modified event\n    \"\"\"\n    func = lambda frame: frame.pop(\"vars\", None)\n    return _process_stack_frames(event, func)\n\n\n@for_events(ERROR, SPAN)\ndef sanitize_stacktrace_locals(client, event):\n    \"\"\"\n    Sanitizes local variables in all frames\n    :param client: an ElasticAPM client\n    :param event: a transaction or error event\n    :return: The modified event\n    \"\"\"\n\n    def func(frame):\n        if \"vars\" in frame:\n            frame[\"vars\"] = varmap(_sanitize, frame[\"vars\"], sanitize_field_names=client.config.sanitize_field_names)\n\n    return _process_stack_frames(event, func)\n\n\n@for_events(ERROR, TRANSACTION)\ndef sanitize_http_request_cookies(client, event):\n    \"\"\"\n    Sanitizes http request cookies\n    :param client: an ElasticAPM client\n    :param event: a transaction or error event\n    :return: The modified event\n    \"\"\"\n\n    # sanitize request.cookies dict\n    try:\n        cookies = event[\"context\"][\"request\"][\"cookies\"]\n        event[\"context\"][\"request\"][\"cookies\"] = varmap(\n            _sanitize, cookies, sanitize_field_names=client.config.sanitize_field_names\n        )\n    except (KeyError, TypeError):\n        pass\n\n    # sanitize request.header.cookie string\n    try:\n        cookie_string = force_text(event[\"context\"][\"request\"][\"headers\"][\"cookie\"], errors=\"replace\")\n        event[\"context\"][\"request\"][\"headers\"][\"cookie\"] = _sanitize_string(\n            cookie_string, \"; \", \"=\", sanitize_field_names=client.config.sanitize_field_names\n        )\n    except (KeyError, TypeError):\n        try:\n            # Sometimes it's Cookie, not cookie\n            cookie_string = force_text(event[\"context\"][\"request\"][\"headers\"][\"Cookie\"], errors=\"replace\")\n            event[\"context\"][\"request\"][\"headers\"][\"Cookie\"] = _sanitize_string(\n                cookie_string, \"; \", \"=\", sanitize_field_names=client.config.sanitize_field_names\n            )\n        except (KeyError, TypeError):\n            pass\n    return event\n\n\n@for_events(ERROR, TRANSACTION)\ndef sanitize_http_response_cookies(client, event):\n    \"\"\"\n    Sanitizes the set-cookie header of the response\n    :param client: an ElasticAPM client\n    :param event: a transaction or error event\n    :return: The modified event\n    \"\"\"\n    try:\n        cookie_string = force_text(event[\"context\"][\"response\"][\"headers\"][\"set-cookie\"], errors=\"replace\")\n        event[\"context\"][\"response\"][\"headers\"][\"set-cookie\"] = _sanitize_string(\n            cookie_string, \";\", \"=\", sanitize_field_names=client.config.sanitize_field_names\n        )\n    except (KeyError, TypeError):\n        pass\n    return event\n\n\n@for_events(ERROR, TRANSACTION)\ndef sanitize_http_headers(client, event):\n    \"\"\"\n    Sanitizes http request/response headers\n    :param client: an ElasticAPM client\n    :param event: a transaction or error event\n    :return: The modified event\n    \"\"\"\n    # request headers\n    try:\n        headers = event[\"context\"][\"request\"][\"headers\"]\n        event[\"context\"][\"request\"][\"headers\"] = varmap(\n            _sanitize, headers, sanitize_field_names=client.config.sanitize_field_names\n        )\n    except (KeyError, TypeError):\n        pass\n\n    # response headers\n    try:\n        headers = event[\"context\"][\"response\"][\"headers\"]\n        event[\"context\"][\"response\"][\"headers\"] = varmap(\n            _sanitize, headers, sanitize_field_names=client.config.sanitize_field_names\n        )\n    except (KeyError, TypeError):\n        pass\n\n    return event\n\n\n@for_events(ERROR, TRANSACTION)\ndef sanitize_http_wsgi_env(client, event):\n    \"\"\"\n    Sanitizes WSGI environment variables\n    :param client: an ElasticAPM client\n    :param event: a transaction or error event\n    :return: The modified event\n    \"\"\"\n    try:\n        env = event[\"context\"][\"request\"][\"env\"]\n        event[\"context\"][\"request\"][\"env\"] = varmap(\n            _sanitize, env, sanitize_field_names=client.config.sanitize_field_names\n        )\n    except (KeyError, TypeError):\n        pass\n    return event\n\n\n@for_events(ERROR, TRANSACTION)\ndef sanitize_http_request_body(client, event):\n    \"\"\"\n    Sanitizes http request body. This only works if the request body\n    is a query-encoded string. Other types (e.g. JSON) are not handled by\n    this sanitizer.\n    :param client: an ElasticAPM client\n    :param event: a transaction or error event\n    :return: The modified event\n    \"\"\"\n    try:\n        body = force_text(event[\"context\"][\"request\"][\"body\"], errors=\"replace\")\n    except (KeyError, TypeError):\n        return event\n    if \"=\" in body:\n        sanitized_query_string = _sanitize_string(\n            body, \"&\", \"=\", sanitize_field_names=client.config.sanitize_field_names\n        )\n        event[\"context\"][\"request\"][\"body\"] = sanitized_query_string\n    return event\n\n\n@for_events(ERROR, SPAN)\ndef add_context_lines_to_frames(client, event):\n    # divide frames up into source files before reading from disk. This should help\n    # with utilizing the disk cache better\n    #\n    # TODO: further optimize by only opening each file once and reading all needed source\n    # TODO: blocks at once.\n    per_file = defaultdict(list)\n    _process_stack_frames(\n        event,\n        lambda frame: per_file[frame[\"context_metadata\"][0]].append(frame) if \"context_metadata\" in frame else None,\n    )\n    for filename, frames in compat.iteritems(per_file):\n        for frame in frames:\n            # context_metadata key has been set in elasticapm.utils.stacks.get_frame_info for\n            # all frames for which we should gather source code context lines\n            fname, lineno, context_lines, loader, module_name = frame.pop(\"context_metadata\")\n            pre_context, context_line, post_context = get_lines_from_file(\n                fname, lineno, context_lines, loader, module_name\n            )\n            if context_line:\n                frame[\"pre_context\"] = pre_context\n                frame[\"context_line\"] = context_line\n                frame[\"post_context\"] = post_context\n    return event\n\n\n@for_events(ERROR, SPAN)\ndef mark_in_app_frames(client, event):\n    warnings.warn(\n        \"The mark_in_app_frames processor is deprecated and can be removed from your PROCESSORS setting\",\n        DeprecationWarning,\n    )\n    return event\n\n\ndef _sanitize(key, value, **kwargs):\n    if \"sanitize_field_names\" in kwargs:\n        sanitize_field_names = kwargs[\"sanitize_field_names\"]\n    else:\n        sanitize_field_names = BASE_SANITIZE_FIELD_NAMES\n\n    if value is None:\n        return\n\n    if isinstance(value, dict):\n        # varmap will call _sanitize on each k:v pair of the dict, so we don't\n        # have to do anything with dicts here\n        return value\n\n    if not key:  # key can be a NoneType\n        return value\n\n    key = key.lower()\n    for field in sanitize_field_names:\n        if field.match(key.strip()):\n            # store mask as a fixed length for security\n            return MASK\n    return value\n\n\ndef _sanitize_string(unsanitized, itemsep, kvsep, sanitize_field_names=BASE_SANITIZE_FIELD_NAMES):\n    \"\"\"\n    sanitizes a string that contains multiple key/value items\n    :param unsanitized: the unsanitized string\n    :param itemsep: string that separates items\n    :param kvsep: string that separates key from value\n    :param sanitize_field_names: field names to pass to _sanitize\n    :return: a sanitized string\n    \"\"\"\n    sanitized = []\n    kvs = unsanitized.split(itemsep)\n    for kv in kvs:\n        kv = kv.split(kvsep)\n        if len(kv) == 2:\n            sanitized.append((kv[0], _sanitize(kv[0], kv[1], sanitize_field_names=sanitize_field_names)))\n        else:\n            sanitized.append(kv)\n    return itemsep.join(kvsep.join(kv) for kv in sanitized)\n\n\ndef _process_stack_frames(event, func):\n    if \"stacktrace\" in event:\n        for frame in event[\"stacktrace\"]:\n            func(frame)\n    # an error can have two stacktraces, one in \"exception\", one in \"log\"\n    if \"exception\" in event and \"stacktrace\" in event[\"exception\"]:\n        for frame in event[\"exception\"][\"stacktrace\"]:\n            func(frame)\n        # check for chained exceptions\n        cause = event[\"exception\"].get(\"cause\", None)\n        while cause:\n            if \"stacktrace\" in cause[0]:\n                for frame in cause[0][\"stacktrace\"]:\n                    func(frame)\n            cause = cause[0].get(\"cause\", None)\n    if \"log\" in event and \"stacktrace\" in event[\"log\"]:\n        for frame in event[\"log\"][\"stacktrace\"]:\n            func(frame)\n    return event\n", "path": "elasticapm/processors.py"}]}