We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[2.9.0] UnicodeEncodeError raised on create_filtered_path
Hi,

so we still use Python2, and got an UnicodeErrorException in create_filtered_path, ligne 53: 

```python
return path + "?" + urlencode(filtered_params)
```

filtered_params value:
```python
filtered_params = [[u'search', u'Clos du Chêne, Montévrain, France'],]
```
query_params has the same value

As it is the output from Sentry, I do not know whether the strings were really Unicode as implied by the "u".

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/scout_apm/core/web_requests.py`
Content:
```
1 # coding=utf-8
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import time
5 
6 from scout_apm.compat import datetime_to_timestamp, urlencode
7 from scout_apm.core.config import scout_config
8 
9 # Originally derived from:
10 # 1. Rails:
11 #   https://github.com/rails/rails/blob/0196551e6039ca864d1eee1e01819fcae12c1dc9/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt  # noqa
12 # 2. Sentry server side scrubbing:
13 #   https://docs.sentry.io/data-management/sensitive-data/#server-side-scrubbing
14 FILTER_PARAMETERS = frozenset(
15     [
16         "access",
17         "access_token",
18         "api_key",
19         "apikey",
20         "auth",
21         "auth_token",
22         "card[number]",
23         "certificate",
24         "credentials",
25         "crypt",
26         "key",
27         "mysql_pwd",
28         "otp",
29         "passwd",
30         "password",
31         "private",
32         "protected",
33         "salt",
34         "secret",
35         "ssn",
36         "stripetoken",
37         "token",
38     ]
39 )
40 
41 
42 def create_filtered_path(path, query_params):
43     if scout_config.value("uri_reporting") == "path":
44         return path
45     filtered_params = sorted(
46         (
47             (k, "[FILTERED]" if k.lower() in FILTER_PARAMETERS else v)
48             for k, v in query_params
49         )
50     )
51     if not filtered_params:
52         return path
53     return path + "?" + urlencode(filtered_params)
54 
55 
56 def ignore_path(path):
57     ignored_paths = scout_config.value("ignore")
58     for ignored in ignored_paths:
59         if path.startswith(ignored):
60             return True
61     return False
62 
63 
64 def track_request_queue_time(header_value, tracked_request):
65     if header_value.startswith("t="):
66         header_value = header_value[2:]
67 
68     try:
69         first_char = header_value[0]
70     except IndexError:
71         return False
72 
73     if not first_char.isdigit():  # filter out negatives, nan, inf, etc.
74         return False
75 
76     try:
77         ambiguous_start_timestamp = float(header_value)
78     except ValueError:
79         return False
80 
81     start_timestamp_ns = convert_ambiguous_timestamp_to_ns(ambiguous_start_timestamp)
82     if start_timestamp_ns == 0.0:
83         return False
84 
85     tr_start_timestamp_ns = datetime_to_timestamp(tracked_request.start_time) * 1e9
86 
87     # Ignore if in the future
88     if start_timestamp_ns > tr_start_timestamp_ns:
89         return False
90 
91     queue_time_ns = int(tr_start_timestamp_ns - start_timestamp_ns)
92     tracked_request.tag("scout.queue_time_ns", queue_time_ns)
93     return True
94 
95 
96 def track_amazon_request_queue_time(header_value, tracked_request):
97     items = header_value.split(";")
98     found_item = None
99     for item in items:
100         if found_item is None and item.startswith("Root="):
101             found_item = item
102         elif item.startswith("Self="):
103             found_item = item
104 
105     if found_item is None:
106         return False
107 
108     pieces = found_item.split("-")
109     if len(pieces) != 3:
110         return False
111 
112     timestamp_str = pieces[1]
113 
114     try:
115         first_char = timestamp_str[0]
116     except IndexError:
117         return False
118 
119     if not first_char.isdigit():
120         return False
121 
122     try:
123         start_timestamp_ns = int(timestamp_str) * 1000000000.0
124     except ValueError:
125         return False
126 
127     if start_timestamp_ns == 0:
128         return False
129 
130     tr_start_timestamp_ns = datetime_to_timestamp(tracked_request.start_time) * 1e9
131 
132     # Ignore if in the futuren
133     if start_timestamp_ns > tr_start_timestamp_ns:
134         return False
135 
136     queue_time_ns = int(tr_start_timestamp_ns - start_timestamp_ns)
137     tracked_request.tag("scout.queue_time_ns", queue_time_ns)
138     return True
139 
140 
141 # Cutoff epoch is used for determining ambiguous timestamp boundaries, and is
142 # just over 10 years ago at time of writing
143 CUTOFF_EPOCH_S = time.mktime((2009, 6, 1, 0, 0, 0, 0, 0, 0))
144 CUTOFF_EPOCH_MS = CUTOFF_EPOCH_S * 1000.0
145 CUTOFF_EPOCH_US = CUTOFF_EPOCH_S * 1000000.0
146 CUTOFF_EPOCH_NS = CUTOFF_EPOCH_S * 1000000000.0
147 
148 
149 def convert_ambiguous_timestamp_to_ns(timestamp):
150     """
151     Convert an ambiguous float timestamp that could be in nanoseconds,
152     microseconds, milliseconds, or seconds to nanoseconds. Return 0.0 for
153     values in the more than 10 years ago.
154     """
155     if timestamp > CUTOFF_EPOCH_NS:
156         converted_timestamp = timestamp
157     elif timestamp > CUTOFF_EPOCH_US:
158         converted_timestamp = timestamp * 1000.0
159     elif timestamp > CUTOFF_EPOCH_MS:
160         converted_timestamp = timestamp * 1000000.0
161     elif timestamp > CUTOFF_EPOCH_S:
162         converted_timestamp = timestamp * 1000000000.0
163     else:
164         return 0.0
165     return converted_timestamp
166 
167 
168 def werkzeug_track_request_data(werkzeug_request, tracked_request):
169     """
170     Several integrations use Werkzeug requests, so share the code for
171     extracting common data here.
172     """
173     path = werkzeug_request.path
174     tracked_request.tag(
175         "path", create_filtered_path(path, werkzeug_request.args.items(multi=True))
176     )
177     if ignore_path(path):
178         tracked_request.tag("ignore_transaction", True)
179 
180     # Determine a remote IP to associate with the request. The value is
181     # spoofable by the requester so this is not suitable to use in any
182     # security sensitive context.
183     user_ip = (
184         werkzeug_request.headers.get("x-forwarded-for", default="").split(",")[0]
185         or werkzeug_request.headers.get("client-ip", default="").split(",")[0]
186         or werkzeug_request.remote_addr
187     )
188     tracked_request.tag("user_ip", user_ip)
189 
190     queue_time = werkzeug_request.headers.get(
191         "x-queue-start", default=""
192     ) or werkzeug_request.headers.get("x-request-start", default="")
193     tracked_queue_time = track_request_queue_time(queue_time, tracked_request)
194     if not tracked_queue_time:
195         amazon_queue_time = werkzeug_request.headers.get("x-amzn-trace-id", default="")
196         track_amazon_request_queue_time(amazon_queue_time, tracked_request)
197
```
--- 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/src/scout_apm/core/web_requests.py b/src/scout_apm/core/web_requests.py
--- a/src/scout_apm/core/web_requests.py
+++ b/src/scout_apm/core/web_requests.py
@@ -42,10 +42,18 @@
 def create_filtered_path(path, query_params):
     if scout_config.value("uri_reporting") == "path":
         return path
+    # Python 2 unicode compatibility: force all keys and values to bytes
     filtered_params = sorted(
         (
-            (k, "[FILTERED]" if k.lower() in FILTER_PARAMETERS else v)
-            for k, v in query_params
+            (
+                key.encode("utf-8"),
+                (
+                    b"[FILTERED]"
+                    if key.lower() in FILTER_PARAMETERS
+                    else value.encode("utf-8")
+                ),
+            )
+            for key, value in query_params
         )
     )
     if not filtered_params:

{"golden_diff": "diff --git a/src/scout_apm/core/web_requests.py b/src/scout_apm/core/web_requests.py\n--- a/src/scout_apm/core/web_requests.py\n+++ b/src/scout_apm/core/web_requests.py\n@@ -42,10 +42,18 @@\n def create_filtered_path(path, query_params):\n     if scout_config.value(\"uri_reporting\") == \"path\":\n         return path\n+    # Python 2 unicode compatibility: force all keys and values to bytes\n     filtered_params = sorted(\n         (\n-            (k, \"[FILTERED]\" if k.lower() in FILTER_PARAMETERS else v)\n-            for k, v in query_params\n+            (\n+                key.encode(\"utf-8\"),\n+                (\n+                    b\"[FILTERED]\"\n+                    if key.lower() in FILTER_PARAMETERS\n+                    else value.encode(\"utf-8\")\n+                ),\n+            )\n+            for key, value in query_params\n         )\n     )\n     if not filtered_params:\n", "issue": "[2.9.0] UnicodeEncodeError raised on create_filtered_path\nHi,\r\n\r\nso we still use Python2, and got an UnicodeErrorException in create_filtered_path, ligne 53: \r\n\r\n```python\r\nreturn path + \"?\" + urlencode(filtered_params)\r\n```\r\n\r\nfiltered_params value:\r\n```python\r\nfiltered_params = [[u'search', u'Clos du Ch\u00eane, Mont\u00e9vrain, France'],]\r\n```\r\nquery_params has the same value\r\n\r\nAs it is the output from Sentry, I do not know whether the strings were really Unicode as implied by the \"u\".\n", "before_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport time\n\nfrom scout_apm.compat import datetime_to_timestamp, urlencode\nfrom scout_apm.core.config import scout_config\n\n# Originally derived from:\n# 1. Rails:\n#   https://github.com/rails/rails/blob/0196551e6039ca864d1eee1e01819fcae12c1dc9/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt  # noqa\n# 2. Sentry server side scrubbing:\n#   https://docs.sentry.io/data-management/sensitive-data/#server-side-scrubbing\nFILTER_PARAMETERS = frozenset(\n    [\n        \"access\",\n        \"access_token\",\n        \"api_key\",\n        \"apikey\",\n        \"auth\",\n        \"auth_token\",\n        \"card[number]\",\n        \"certificate\",\n        \"credentials\",\n        \"crypt\",\n        \"key\",\n        \"mysql_pwd\",\n        \"otp\",\n        \"passwd\",\n        \"password\",\n        \"private\",\n        \"protected\",\n        \"salt\",\n        \"secret\",\n        \"ssn\",\n        \"stripetoken\",\n        \"token\",\n    ]\n)\n\n\ndef create_filtered_path(path, query_params):\n    if scout_config.value(\"uri_reporting\") == \"path\":\n        return path\n    filtered_params = sorted(\n        (\n            (k, \"[FILTERED]\" if k.lower() in FILTER_PARAMETERS else v)\n            for k, v in query_params\n        )\n    )\n    if not filtered_params:\n        return path\n    return path + \"?\" + urlencode(filtered_params)\n\n\ndef ignore_path(path):\n    ignored_paths = scout_config.value(\"ignore\")\n    for ignored in ignored_paths:\n        if path.startswith(ignored):\n            return True\n    return False\n\n\ndef track_request_queue_time(header_value, tracked_request):\n    if header_value.startswith(\"t=\"):\n        header_value = header_value[2:]\n\n    try:\n        first_char = header_value[0]\n    except IndexError:\n        return False\n\n    if not first_char.isdigit():  # filter out negatives, nan, inf, etc.\n        return False\n\n    try:\n        ambiguous_start_timestamp = float(header_value)\n    except ValueError:\n        return False\n\n    start_timestamp_ns = convert_ambiguous_timestamp_to_ns(ambiguous_start_timestamp)\n    if start_timestamp_ns == 0.0:\n        return False\n\n    tr_start_timestamp_ns = datetime_to_timestamp(tracked_request.start_time) * 1e9\n\n    # Ignore if in the future\n    if start_timestamp_ns > tr_start_timestamp_ns:\n        return False\n\n    queue_time_ns = int(tr_start_timestamp_ns - start_timestamp_ns)\n    tracked_request.tag(\"scout.queue_time_ns\", queue_time_ns)\n    return True\n\n\ndef track_amazon_request_queue_time(header_value, tracked_request):\n    items = header_value.split(\";\")\n    found_item = None\n    for item in items:\n        if found_item is None and item.startswith(\"Root=\"):\n            found_item = item\n        elif item.startswith(\"Self=\"):\n            found_item = item\n\n    if found_item is None:\n        return False\n\n    pieces = found_item.split(\"-\")\n    if len(pieces) != 3:\n        return False\n\n    timestamp_str = pieces[1]\n\n    try:\n        first_char = timestamp_str[0]\n    except IndexError:\n        return False\n\n    if not first_char.isdigit():\n        return False\n\n    try:\n        start_timestamp_ns = int(timestamp_str) * 1000000000.0\n    except ValueError:\n        return False\n\n    if start_timestamp_ns == 0:\n        return False\n\n    tr_start_timestamp_ns = datetime_to_timestamp(tracked_request.start_time) * 1e9\n\n    # Ignore if in the futuren\n    if start_timestamp_ns > tr_start_timestamp_ns:\n        return False\n\n    queue_time_ns = int(tr_start_timestamp_ns - start_timestamp_ns)\n    tracked_request.tag(\"scout.queue_time_ns\", queue_time_ns)\n    return True\n\n\n# Cutoff epoch is used for determining ambiguous timestamp boundaries, and is\n# just over 10 years ago at time of writing\nCUTOFF_EPOCH_S = time.mktime((2009, 6, 1, 0, 0, 0, 0, 0, 0))\nCUTOFF_EPOCH_MS = CUTOFF_EPOCH_S * 1000.0\nCUTOFF_EPOCH_US = CUTOFF_EPOCH_S * 1000000.0\nCUTOFF_EPOCH_NS = CUTOFF_EPOCH_S * 1000000000.0\n\n\ndef convert_ambiguous_timestamp_to_ns(timestamp):\n    \"\"\"\n    Convert an ambiguous float timestamp that could be in nanoseconds,\n    microseconds, milliseconds, or seconds to nanoseconds. Return 0.0 for\n    values in the more than 10 years ago.\n    \"\"\"\n    if timestamp > CUTOFF_EPOCH_NS:\n        converted_timestamp = timestamp\n    elif timestamp > CUTOFF_EPOCH_US:\n        converted_timestamp = timestamp * 1000.0\n    elif timestamp > CUTOFF_EPOCH_MS:\n        converted_timestamp = timestamp * 1000000.0\n    elif timestamp > CUTOFF_EPOCH_S:\n        converted_timestamp = timestamp * 1000000000.0\n    else:\n        return 0.0\n    return converted_timestamp\n\n\ndef werkzeug_track_request_data(werkzeug_request, tracked_request):\n    \"\"\"\n    Several integrations use Werkzeug requests, so share the code for\n    extracting common data here.\n    \"\"\"\n    path = werkzeug_request.path\n    tracked_request.tag(\n        \"path\", create_filtered_path(path, werkzeug_request.args.items(multi=True))\n    )\n    if ignore_path(path):\n        tracked_request.tag(\"ignore_transaction\", True)\n\n    # Determine a remote IP to associate with the request. The value is\n    # spoofable by the requester so this is not suitable to use in any\n    # security sensitive context.\n    user_ip = (\n        werkzeug_request.headers.get(\"x-forwarded-for\", default=\"\").split(\",\")[0]\n        or werkzeug_request.headers.get(\"client-ip\", default=\"\").split(\",\")[0]\n        or werkzeug_request.remote_addr\n    )\n    tracked_request.tag(\"user_ip\", user_ip)\n\n    queue_time = werkzeug_request.headers.get(\n        \"x-queue-start\", default=\"\"\n    ) or werkzeug_request.headers.get(\"x-request-start\", default=\"\")\n    tracked_queue_time = track_request_queue_time(queue_time, tracked_request)\n    if not tracked_queue_time:\n        amazon_queue_time = werkzeug_request.headers.get(\"x-amzn-trace-id\", default=\"\")\n        track_amazon_request_queue_time(amazon_queue_time, tracked_request)\n", "path": "src/scout_apm/core/web_requests.py"}], "after_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport time\n\nfrom scout_apm.compat import datetime_to_timestamp, urlencode\nfrom scout_apm.core.config import scout_config\n\n# Originally derived from:\n# 1. Rails:\n#   https://github.com/rails/rails/blob/0196551e6039ca864d1eee1e01819fcae12c1dc9/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt  # noqa\n# 2. Sentry server side scrubbing:\n#   https://docs.sentry.io/data-management/sensitive-data/#server-side-scrubbing\nFILTER_PARAMETERS = frozenset(\n    [\n        \"access\",\n        \"access_token\",\n        \"api_key\",\n        \"apikey\",\n        \"auth\",\n        \"auth_token\",\n        \"card[number]\",\n        \"certificate\",\n        \"credentials\",\n        \"crypt\",\n        \"key\",\n        \"mysql_pwd\",\n        \"otp\",\n        \"passwd\",\n        \"password\",\n        \"private\",\n        \"protected\",\n        \"salt\",\n        \"secret\",\n        \"ssn\",\n        \"stripetoken\",\n        \"token\",\n    ]\n)\n\n\ndef create_filtered_path(path, query_params):\n    if scout_config.value(\"uri_reporting\") == \"path\":\n        return path\n    # Python 2 unicode compatibility: force all keys and values to bytes\n    filtered_params = sorted(\n        (\n            (\n                key.encode(\"utf-8\"),\n                (\n                    b\"[FILTERED]\"\n                    if key.lower() in FILTER_PARAMETERS\n                    else value.encode(\"utf-8\")\n                ),\n            )\n            for key, value in query_params\n        )\n    )\n    if not filtered_params:\n        return path\n    return path + \"?\" + urlencode(filtered_params)\n\n\ndef ignore_path(path):\n    ignored_paths = scout_config.value(\"ignore\")\n    for ignored in ignored_paths:\n        if path.startswith(ignored):\n            return True\n    return False\n\n\ndef track_request_queue_time(header_value, tracked_request):\n    if header_value.startswith(\"t=\"):\n        header_value = header_value[2:]\n\n    try:\n        first_char = header_value[0]\n    except IndexError:\n        return False\n\n    if not first_char.isdigit():  # filter out negatives, nan, inf, etc.\n        return False\n\n    try:\n        ambiguous_start_timestamp = float(header_value)\n    except ValueError:\n        return False\n\n    start_timestamp_ns = convert_ambiguous_timestamp_to_ns(ambiguous_start_timestamp)\n    if start_timestamp_ns == 0.0:\n        return False\n\n    tr_start_timestamp_ns = datetime_to_timestamp(tracked_request.start_time) * 1e9\n\n    # Ignore if in the future\n    if start_timestamp_ns > tr_start_timestamp_ns:\n        return False\n\n    queue_time_ns = int(tr_start_timestamp_ns - start_timestamp_ns)\n    tracked_request.tag(\"scout.queue_time_ns\", queue_time_ns)\n    return True\n\n\ndef track_amazon_request_queue_time(header_value, tracked_request):\n    items = header_value.split(\";\")\n    found_item = None\n    for item in items:\n        if found_item is None and item.startswith(\"Root=\"):\n            found_item = item\n        elif item.startswith(\"Self=\"):\n            found_item = item\n\n    if found_item is None:\n        return False\n\n    pieces = found_item.split(\"-\")\n    if len(pieces) != 3:\n        return False\n\n    timestamp_str = pieces[1]\n\n    try:\n        first_char = timestamp_str[0]\n    except IndexError:\n        return False\n\n    if not first_char.isdigit():\n        return False\n\n    try:\n        start_timestamp_ns = int(timestamp_str) * 1000000000.0\n    except ValueError:\n        return False\n\n    if start_timestamp_ns == 0:\n        return False\n\n    tr_start_timestamp_ns = datetime_to_timestamp(tracked_request.start_time) * 1e9\n\n    # Ignore if in the futuren\n    if start_timestamp_ns > tr_start_timestamp_ns:\n        return False\n\n    queue_time_ns = int(tr_start_timestamp_ns - start_timestamp_ns)\n    tracked_request.tag(\"scout.queue_time_ns\", queue_time_ns)\n    return True\n\n\n# Cutoff epoch is used for determining ambiguous timestamp boundaries, and is\n# just over 10 years ago at time of writing\nCUTOFF_EPOCH_S = time.mktime((2009, 6, 1, 0, 0, 0, 0, 0, 0))\nCUTOFF_EPOCH_MS = CUTOFF_EPOCH_S * 1000.0\nCUTOFF_EPOCH_US = CUTOFF_EPOCH_S * 1000000.0\nCUTOFF_EPOCH_NS = CUTOFF_EPOCH_S * 1000000000.0\n\n\ndef convert_ambiguous_timestamp_to_ns(timestamp):\n    \"\"\"\n    Convert an ambiguous float timestamp that could be in nanoseconds,\n    microseconds, milliseconds, or seconds to nanoseconds. Return 0.0 for\n    values in the more than 10 years ago.\n    \"\"\"\n    if timestamp > CUTOFF_EPOCH_NS:\n        converted_timestamp = timestamp\n    elif timestamp > CUTOFF_EPOCH_US:\n        converted_timestamp = timestamp * 1000.0\n    elif timestamp > CUTOFF_EPOCH_MS:\n        converted_timestamp = timestamp * 1000000.0\n    elif timestamp > CUTOFF_EPOCH_S:\n        converted_timestamp = timestamp * 1000000000.0\n    else:\n        return 0.0\n    return converted_timestamp\n\n\ndef werkzeug_track_request_data(werkzeug_request, tracked_request):\n    \"\"\"\n    Several integrations use Werkzeug requests, so share the code for\n    extracting common data here.\n    \"\"\"\n    path = werkzeug_request.path\n    tracked_request.tag(\n        \"path\", create_filtered_path(path, werkzeug_request.args.items(multi=True))\n    )\n    if ignore_path(path):\n        tracked_request.tag(\"ignore_transaction\", True)\n\n    # Determine a remote IP to associate with the request. The value is\n    # spoofable by the requester so this is not suitable to use in any\n    # security sensitive context.\n    user_ip = (\n        werkzeug_request.headers.get(\"x-forwarded-for\", default=\"\").split(\",\")[0]\n        or werkzeug_request.headers.get(\"client-ip\", default=\"\").split(\",\")[0]\n        or werkzeug_request.remote_addr\n    )\n    tracked_request.tag(\"user_ip\", user_ip)\n\n    queue_time = werkzeug_request.headers.get(\n        \"x-queue-start\", default=\"\"\n    ) or werkzeug_request.headers.get(\"x-request-start\", default=\"\")\n    tracked_queue_time = track_request_queue_time(queue_time, tracked_request)\n    if not tracked_queue_time:\n        amazon_queue_time = werkzeug_request.headers.get(\"x-amzn-trace-id\", default=\"\")\n        track_amazon_request_queue_time(amazon_queue_time, tracked_request)\n", "path": "src/scout_apm/core/web_requests.py"}]}