We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Config to capture query string, not just path
When a request comes in, we capture `request.path`, which doesn't appear to capture any query string.

We should match the Ruby agent:
* Default to capturing the entire path and query string
* Config to not capture query string https://docs.scoutapm.com/#uri_reporting

--- 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 scout_apm.compat import urlencode
3 
4 # Originally derived from:
5 # 1. Rails:
6 #   https://github.com/rails/rails/blob/0196551e6039ca864d1eee1e01819fcae12c1dc9/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt
7 # 2. Sentry server side scrubbing:
8 #   https://docs.sentry.io/data-management/sensitive-data/#server-side-scrubbing
9 FILTER_PARAMETERS = frozenset(
10     [
11         "access",
12         "access_token",
13         "api_key",
14         "apikey",
15         "auth",
16         "auth_token",
17         "card[number]",
18         "certificate",
19         "credentials",
20         "crypt",
21         "key",
22         "mysql_pwd",
23         "otp",
24         "passwd",
25         "password",
26         "private",
27         "protected",
28         "salt",
29         "secret",
30         "ssn",
31         "stripetoken",
32         "token",
33     ]
34 )
35 
36 
37 def create_filtered_path(path, query_params):
38     filtered_params = sorted(
39         (
40             (k, "[FILTERED]" if k.lower() in FILTER_PARAMETERS else v)
41             for k, v in query_params
42         )
43     )
44     if not filtered_params:
45         return path
46     return path + "?" + urlencode(filtered_params)
47
```

Path: `src/scout_apm/core/config.py`
Content:
```
1 # coding=utf-8
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import logging
5 import os
6 
7 from scout_apm.compat import string_type
8 from scout_apm.core import platform_detection
9 from scout_apm.core.util import octal
10 
11 logger = logging.getLogger(__name__)
12 
13 
14 class ScoutConfig(object):
15     """
16     Configuration object for the ScoutApm agent.
17 
18     Contains a list of configuration "layers". When a configuration key is
19     looked up, each layer is asked in turn if it knows the value. The first one
20     to answer affirmatively returns the value.
21     """
22 
23     def __init__(self):
24         self.layers = [
25             ScoutConfigEnv(),
26             ScoutConfigPython(),
27             ScoutConfigDerived(self),
28             ScoutConfigDefaults(),
29             ScoutConfigNull(),
30         ]
31 
32     def value(self, key):
33         value = self.locate_layer_for_key(key).value(key)
34         if key in CONVERSIONS:
35             return CONVERSIONS[key](value)
36         return value
37 
38     def locate_layer_for_key(self, key):
39         for layer in self.layers:
40             if layer.has_config(key):
41                 return layer
42 
43         # Should be unreachable because ScoutConfigNull returns None for all
44         # keys.
45         raise ValueError("key {!r} not found in any layer".format(key))
46 
47     def log(self):
48         logger.debug("Configuration Loaded:")
49         for key in self.known_keys():
50             layer = self.locate_layer_for_key(key)
51             logger.debug("%-9s: %s = %s", layer.name(), key, layer.value(key))
52 
53     def known_keys(self):
54         return [
55             "app_server",
56             "application_root",
57             "core_agent_dir",
58             "core_agent_download",
59             "core_agent_launch",
60             "core_agent_permissions",
61             "core_agent_version",
62             "disabled_instruments",
63             "download_url",
64             "framework",
65             "framework_version",
66             "hostname",
67             "ignore",
68             "key",
69             "log_level",
70             "monitor",
71             "name",
72             "revision_sha",
73             "scm_subdirectory",
74             "socket_path",
75         ]
76 
77     def core_agent_permissions(self):
78         try:
79             return octal(self.value("core_agent_permissions"))
80         except ValueError:
81             logger.exception(
82                 "Invalid core_agent_permissions value, using default of 0o700"
83             )
84             return 0o700
85 
86     @classmethod
87     def set(cls, **kwargs):
88         """
89         Sets a configuration value for the Scout agent. Values set here will
90         not override values set in ENV.
91         """
92         global SCOUT_PYTHON_VALUES
93         for key, value in kwargs.items():
94             SCOUT_PYTHON_VALUES[key] = value
95 
96     @classmethod
97     def unset(cls, *keys):
98         """
99         Removes a configuration value for the Scout agent.
100         """
101         global SCOUT_PYTHON_VALUES
102         for key in keys:
103             SCOUT_PYTHON_VALUES.pop(key, None)
104 
105     @classmethod
106     def reset_all(cls):
107         """
108         Remove all configuration settings set via `ScoutConfig.set(...)`.
109 
110         This is meant for use in testing.
111         """
112         global SCOUT_PYTHON_VALUES
113         SCOUT_PYTHON_VALUES.clear()
114 
115 
116 # Module-level data, the ScoutConfig.set(key="value") adds to this
117 SCOUT_PYTHON_VALUES = {}
118 
119 
120 class ScoutConfigPython(object):
121     """
122     A configuration overlay that lets other parts of python set values.
123     """
124 
125     def name(self):
126         return "Python"
127 
128     def has_config(self, key):
129         return key in SCOUT_PYTHON_VALUES
130 
131     def value(self, key):
132         return SCOUT_PYTHON_VALUES[key]
133 
134 
135 class ScoutConfigEnv(object):
136     """
137     Reads configuration from environment by prefixing the key
138     requested with "SCOUT_"
139 
140     Example: the `log_level` config looks for SCOUT_LOG_LEVEL
141     environment variable
142     """
143 
144     def name(self):
145         return "ENV"
146 
147     def has_config(self, key):
148         env_key = self.modify_key(key)
149         return env_key in os.environ
150 
151     def value(self, key):
152         env_key = self.modify_key(key)
153         return os.environ[env_key]
154 
155     def modify_key(self, key):
156         env_key = ("SCOUT_" + key).upper()
157         return env_key
158 
159 
160 class ScoutConfigDerived(object):
161     """
162     A configuration overlay that calculates from other values.
163     """
164 
165     def __init__(self, config):
166         """
167         config argument is the overall ScoutConfig var, so we can lookup the
168         components of the derived info.
169         """
170         self.config = config
171 
172     def name(self):
173         return "Derived"
174 
175     def has_config(self, key):
176         return self.lookup_func(key) is not None
177 
178     def value(self, key):
179         return self.lookup_func(key)()
180 
181     def lookup_func(self, key):
182         """
183         Returns the derive_#{key} function, or None if it isn't defined
184         """
185         func_name = "derive_" + key
186         return getattr(self, func_name, None)
187 
188     def derive_socket_path(self):
189         return "{}/{}/scout-agent.sock".format(
190             self.config.value("core_agent_dir"),
191             self.config.value("core_agent_full_name"),
192         )
193 
194     def derive_core_agent_full_name(self):
195         triple = self.config.value("core_agent_triple")
196         if not platform_detection.is_valid_triple(triple):
197             logger.warning("Invalid value for core_agent_triple: %s", triple)
198         return "{name}-{version}-{triple}".format(
199             name="scout_apm_core",
200             version=self.config.value("core_agent_version"),
201             triple=triple,
202         )
203 
204     def derive_core_agent_triple(self):
205         return platform_detection.get_triple()
206 
207 
208 class ScoutConfigDefaults(object):
209     """
210     Provides default values for important configurations
211     """
212 
213     def name(self):
214         return "Defaults"
215 
216     def __init__(self):
217         self.defaults = {
218             "app_server": "",
219             "application_root": "",
220             "core_agent_dir": "/tmp/scout_apm_core",
221             "core_agent_download": True,
222             "core_agent_launch": True,
223             "core_agent_permissions": 700,
224             "core_agent_version": "v1.2.0",  # can be an exact tag name, or 'latest'
225             "disabled_instruments": [],
226             "download_url": "https://s3-us-west-1.amazonaws.com/scout-public-downloads/apm_core_agent/release",  # noqa: E501
227             "framework": "",
228             "framework_version": "",
229             "hostname": None,
230             "key": "",
231             "log_level": "info",
232             "monitor": False,
233             "name": "",
234             "revision_sha": self._git_revision_sha(),
235             "scm_subdirectory": "",
236         }
237 
238     def _git_revision_sha(self):
239         # N.B. The environment variable SCOUT_REVISION_SHA may also be used,
240         # but that will be picked up by ScoutConfigEnv
241         return os.environ.get("HEROKU_SLUG_COMMIT", "")
242 
243     def has_config(self, key):
244         return key in self.defaults
245 
246     def value(self, key):
247         return self.defaults[key]
248 
249 
250 # Always returns None to any key
251 class ScoutConfigNull(object):
252     """
253     Always answers that a key is present, but the value is None
254 
255     Used as the last step of the layered configuration.
256     """
257 
258     def name(self):
259         return "Null"
260 
261     def has_config(self, key):
262         return True
263 
264     def value(self, key):
265         return None
266 
267 
268 def convert_to_bool(value):
269     if isinstance(value, bool):
270         return value
271     if isinstance(value, string_type):
272         return value.lower() in ("yes", "true", "t", "1")
273     # Unknown type - default to false?
274     return False
275 
276 
277 def convert_to_list(value):
278     if isinstance(value, list):
279         return value
280     if isinstance(value, tuple):
281         return list(value)
282     if isinstance(value, string_type):
283         # Split on commas
284         return [item.strip() for item in value.split(",") if item]
285     # Unknown type - default to empty?
286     return []
287 
288 
289 CONVERSIONS = {
290     "core_agent_download": convert_to_bool,
291     "core_agent_launch": convert_to_bool,
292     "monitor": convert_to_bool,
293     "disabled_instruments": convert_to_list,
294     "ignore": convert_to_list,
295 }
296
```
--- 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/config.py b/src/scout_apm/core/config.py
--- a/src/scout_apm/core/config.py
+++ b/src/scout_apm/core/config.py
@@ -233,6 +233,7 @@
             "name": "",
             "revision_sha": self._git_revision_sha(),
             "scm_subdirectory": "",
+            "uri_reporting": "filtered_params",
         }
 
     def _git_revision_sha(self):
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
@@ -1,5 +1,6 @@
 # coding=utf-8
 from scout_apm.compat import urlencode
+from scout_apm.core.context import AgentContext
 
 # Originally derived from:
 # 1. Rails:
@@ -35,6 +36,8 @@
 
 
 def create_filtered_path(path, query_params):
+    if AgentContext.instance.config.value("uri_reporting") == "path":
+        return path
     filtered_params = sorted(
         (
             (k, "[FILTERED]" if k.lower() in FILTER_PARAMETERS else v)

{"golden_diff": "diff --git a/src/scout_apm/core/config.py b/src/scout_apm/core/config.py\n--- a/src/scout_apm/core/config.py\n+++ b/src/scout_apm/core/config.py\n@@ -233,6 +233,7 @@\n             \"name\": \"\",\n             \"revision_sha\": self._git_revision_sha(),\n             \"scm_subdirectory\": \"\",\n+            \"uri_reporting\": \"filtered_params\",\n         }\n \n     def _git_revision_sha(self):\ndiff --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@@ -1,5 +1,6 @@\n # coding=utf-8\n from scout_apm.compat import urlencode\n+from scout_apm.core.context import AgentContext\n \n # Originally derived from:\n # 1. Rails:\n@@ -35,6 +36,8 @@\n \n \n def create_filtered_path(path, query_params):\n+    if AgentContext.instance.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", "issue": "Config to capture query string, not just path\nWhen a request comes in, we capture `request.path`, which doesn't appear to capture any query string.\r\n\r\nWe should match the Ruby agent:\r\n* Default to capturing the entire path and query string\r\n* Config to not capture query string https://docs.scoutapm.com/#uri_reporting\n", "before_files": [{"content": "# coding=utf-8\nfrom scout_apm.compat import urlencode\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\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    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", "path": "src/scout_apm/core/web_requests.py"}, {"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport logging\nimport os\n\nfrom scout_apm.compat import string_type\nfrom scout_apm.core import platform_detection\nfrom scout_apm.core.util import octal\n\nlogger = logging.getLogger(__name__)\n\n\nclass ScoutConfig(object):\n    \"\"\"\n    Configuration object for the ScoutApm agent.\n\n    Contains a list of configuration \"layers\". When a configuration key is\n    looked up, each layer is asked in turn if it knows the value. The first one\n    to answer affirmatively returns the value.\n    \"\"\"\n\n    def __init__(self):\n        self.layers = [\n            ScoutConfigEnv(),\n            ScoutConfigPython(),\n            ScoutConfigDerived(self),\n            ScoutConfigDefaults(),\n            ScoutConfigNull(),\n        ]\n\n    def value(self, key):\n        value = self.locate_layer_for_key(key).value(key)\n        if key in CONVERSIONS:\n            return CONVERSIONS[key](value)\n        return value\n\n    def locate_layer_for_key(self, key):\n        for layer in self.layers:\n            if layer.has_config(key):\n                return layer\n\n        # Should be unreachable because ScoutConfigNull returns None for all\n        # keys.\n        raise ValueError(\"key {!r} not found in any layer\".format(key))\n\n    def log(self):\n        logger.debug(\"Configuration Loaded:\")\n        for key in self.known_keys():\n            layer = self.locate_layer_for_key(key)\n            logger.debug(\"%-9s: %s = %s\", layer.name(), key, layer.value(key))\n\n    def known_keys(self):\n        return [\n            \"app_server\",\n            \"application_root\",\n            \"core_agent_dir\",\n            \"core_agent_download\",\n            \"core_agent_launch\",\n            \"core_agent_permissions\",\n            \"core_agent_version\",\n            \"disabled_instruments\",\n            \"download_url\",\n            \"framework\",\n            \"framework_version\",\n            \"hostname\",\n            \"ignore\",\n            \"key\",\n            \"log_level\",\n            \"monitor\",\n            \"name\",\n            \"revision_sha\",\n            \"scm_subdirectory\",\n            \"socket_path\",\n        ]\n\n    def core_agent_permissions(self):\n        try:\n            return octal(self.value(\"core_agent_permissions\"))\n        except ValueError:\n            logger.exception(\n                \"Invalid core_agent_permissions value, using default of 0o700\"\n            )\n            return 0o700\n\n    @classmethod\n    def set(cls, **kwargs):\n        \"\"\"\n        Sets a configuration value for the Scout agent. Values set here will\n        not override values set in ENV.\n        \"\"\"\n        global SCOUT_PYTHON_VALUES\n        for key, value in kwargs.items():\n            SCOUT_PYTHON_VALUES[key] = value\n\n    @classmethod\n    def unset(cls, *keys):\n        \"\"\"\n        Removes a configuration value for the Scout agent.\n        \"\"\"\n        global SCOUT_PYTHON_VALUES\n        for key in keys:\n            SCOUT_PYTHON_VALUES.pop(key, None)\n\n    @classmethod\n    def reset_all(cls):\n        \"\"\"\n        Remove all configuration settings set via `ScoutConfig.set(...)`.\n\n        This is meant for use in testing.\n        \"\"\"\n        global SCOUT_PYTHON_VALUES\n        SCOUT_PYTHON_VALUES.clear()\n\n\n# Module-level data, the ScoutConfig.set(key=\"value\") adds to this\nSCOUT_PYTHON_VALUES = {}\n\n\nclass ScoutConfigPython(object):\n    \"\"\"\n    A configuration overlay that lets other parts of python set values.\n    \"\"\"\n\n    def name(self):\n        return \"Python\"\n\n    def has_config(self, key):\n        return key in SCOUT_PYTHON_VALUES\n\n    def value(self, key):\n        return SCOUT_PYTHON_VALUES[key]\n\n\nclass ScoutConfigEnv(object):\n    \"\"\"\n    Reads configuration from environment by prefixing the key\n    requested with \"SCOUT_\"\n\n    Example: the `log_level` config looks for SCOUT_LOG_LEVEL\n    environment variable\n    \"\"\"\n\n    def name(self):\n        return \"ENV\"\n\n    def has_config(self, key):\n        env_key = self.modify_key(key)\n        return env_key in os.environ\n\n    def value(self, key):\n        env_key = self.modify_key(key)\n        return os.environ[env_key]\n\n    def modify_key(self, key):\n        env_key = (\"SCOUT_\" + key).upper()\n        return env_key\n\n\nclass ScoutConfigDerived(object):\n    \"\"\"\n    A configuration overlay that calculates from other values.\n    \"\"\"\n\n    def __init__(self, config):\n        \"\"\"\n        config argument is the overall ScoutConfig var, so we can lookup the\n        components of the derived info.\n        \"\"\"\n        self.config = config\n\n    def name(self):\n        return \"Derived\"\n\n    def has_config(self, key):\n        return self.lookup_func(key) is not None\n\n    def value(self, key):\n        return self.lookup_func(key)()\n\n    def lookup_func(self, key):\n        \"\"\"\n        Returns the derive_#{key} function, or None if it isn't defined\n        \"\"\"\n        func_name = \"derive_\" + key\n        return getattr(self, func_name, None)\n\n    def derive_socket_path(self):\n        return \"{}/{}/scout-agent.sock\".format(\n            self.config.value(\"core_agent_dir\"),\n            self.config.value(\"core_agent_full_name\"),\n        )\n\n    def derive_core_agent_full_name(self):\n        triple = self.config.value(\"core_agent_triple\")\n        if not platform_detection.is_valid_triple(triple):\n            logger.warning(\"Invalid value for core_agent_triple: %s\", triple)\n        return \"{name}-{version}-{triple}\".format(\n            name=\"scout_apm_core\",\n            version=self.config.value(\"core_agent_version\"),\n            triple=triple,\n        )\n\n    def derive_core_agent_triple(self):\n        return platform_detection.get_triple()\n\n\nclass ScoutConfigDefaults(object):\n    \"\"\"\n    Provides default values for important configurations\n    \"\"\"\n\n    def name(self):\n        return \"Defaults\"\n\n    def __init__(self):\n        self.defaults = {\n            \"app_server\": \"\",\n            \"application_root\": \"\",\n            \"core_agent_dir\": \"/tmp/scout_apm_core\",\n            \"core_agent_download\": True,\n            \"core_agent_launch\": True,\n            \"core_agent_permissions\": 700,\n            \"core_agent_version\": \"v1.2.0\",  # can be an exact tag name, or 'latest'\n            \"disabled_instruments\": [],\n            \"download_url\": \"https://s3-us-west-1.amazonaws.com/scout-public-downloads/apm_core_agent/release\",  # noqa: E501\n            \"framework\": \"\",\n            \"framework_version\": \"\",\n            \"hostname\": None,\n            \"key\": \"\",\n            \"log_level\": \"info\",\n            \"monitor\": False,\n            \"name\": \"\",\n            \"revision_sha\": self._git_revision_sha(),\n            \"scm_subdirectory\": \"\",\n        }\n\n    def _git_revision_sha(self):\n        # N.B. The environment variable SCOUT_REVISION_SHA may also be used,\n        # but that will be picked up by ScoutConfigEnv\n        return os.environ.get(\"HEROKU_SLUG_COMMIT\", \"\")\n\n    def has_config(self, key):\n        return key in self.defaults\n\n    def value(self, key):\n        return self.defaults[key]\n\n\n# Always returns None to any key\nclass ScoutConfigNull(object):\n    \"\"\"\n    Always answers that a key is present, but the value is None\n\n    Used as the last step of the layered configuration.\n    \"\"\"\n\n    def name(self):\n        return \"Null\"\n\n    def has_config(self, key):\n        return True\n\n    def value(self, key):\n        return None\n\n\ndef convert_to_bool(value):\n    if isinstance(value, bool):\n        return value\n    if isinstance(value, string_type):\n        return value.lower() in (\"yes\", \"true\", \"t\", \"1\")\n    # Unknown type - default to false?\n    return False\n\n\ndef convert_to_list(value):\n    if isinstance(value, list):\n        return value\n    if isinstance(value, tuple):\n        return list(value)\n    if isinstance(value, string_type):\n        # Split on commas\n        return [item.strip() for item in value.split(\",\") if item]\n    # Unknown type - default to empty?\n    return []\n\n\nCONVERSIONS = {\n    \"core_agent_download\": convert_to_bool,\n    \"core_agent_launch\": convert_to_bool,\n    \"monitor\": convert_to_bool,\n    \"disabled_instruments\": convert_to_list,\n    \"ignore\": convert_to_list,\n}\n", "path": "src/scout_apm/core/config.py"}], "after_files": [{"content": "# coding=utf-8\nfrom scout_apm.compat import urlencode\nfrom scout_apm.core.context import AgentContext\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\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 AgentContext.instance.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", "path": "src/scout_apm/core/web_requests.py"}, {"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport logging\nimport os\n\nfrom scout_apm.compat import string_type\nfrom scout_apm.core import platform_detection\nfrom scout_apm.core.util import octal\n\nlogger = logging.getLogger(__name__)\n\n\nclass ScoutConfig(object):\n    \"\"\"\n    Configuration object for the ScoutApm agent.\n\n    Contains a list of configuration \"layers\". When a configuration key is\n    looked up, each layer is asked in turn if it knows the value. The first one\n    to answer affirmatively returns the value.\n    \"\"\"\n\n    def __init__(self):\n        self.layers = [\n            ScoutConfigEnv(),\n            ScoutConfigPython(),\n            ScoutConfigDerived(self),\n            ScoutConfigDefaults(),\n            ScoutConfigNull(),\n        ]\n\n    def value(self, key):\n        value = self.locate_layer_for_key(key).value(key)\n        if key in CONVERSIONS:\n            return CONVERSIONS[key](value)\n        return value\n\n    def locate_layer_for_key(self, key):\n        for layer in self.layers:\n            if layer.has_config(key):\n                return layer\n\n        # Should be unreachable because ScoutConfigNull returns None for all\n        # keys.\n        raise ValueError(\"key {!r} not found in any layer\".format(key))\n\n    def log(self):\n        logger.debug(\"Configuration Loaded:\")\n        for key in self.known_keys():\n            layer = self.locate_layer_for_key(key)\n            logger.debug(\"%-9s: %s = %s\", layer.name(), key, layer.value(key))\n\n    def known_keys(self):\n        return [\n            \"app_server\",\n            \"application_root\",\n            \"core_agent_dir\",\n            \"core_agent_download\",\n            \"core_agent_launch\",\n            \"core_agent_permissions\",\n            \"core_agent_version\",\n            \"disabled_instruments\",\n            \"download_url\",\n            \"framework\",\n            \"framework_version\",\n            \"hostname\",\n            \"ignore\",\n            \"key\",\n            \"log_level\",\n            \"monitor\",\n            \"name\",\n            \"revision_sha\",\n            \"scm_subdirectory\",\n            \"socket_path\",\n        ]\n\n    def core_agent_permissions(self):\n        try:\n            return octal(self.value(\"core_agent_permissions\"))\n        except ValueError:\n            logger.exception(\n                \"Invalid core_agent_permissions value, using default of 0o700\"\n            )\n            return 0o700\n\n    @classmethod\n    def set(cls, **kwargs):\n        \"\"\"\n        Sets a configuration value for the Scout agent. Values set here will\n        not override values set in ENV.\n        \"\"\"\n        global SCOUT_PYTHON_VALUES\n        for key, value in kwargs.items():\n            SCOUT_PYTHON_VALUES[key] = value\n\n    @classmethod\n    def unset(cls, *keys):\n        \"\"\"\n        Removes a configuration value for the Scout agent.\n        \"\"\"\n        global SCOUT_PYTHON_VALUES\n        for key in keys:\n            SCOUT_PYTHON_VALUES.pop(key, None)\n\n    @classmethod\n    def reset_all(cls):\n        \"\"\"\n        Remove all configuration settings set via `ScoutConfig.set(...)`.\n\n        This is meant for use in testing.\n        \"\"\"\n        global SCOUT_PYTHON_VALUES\n        SCOUT_PYTHON_VALUES.clear()\n\n\n# Module-level data, the ScoutConfig.set(key=\"value\") adds to this\nSCOUT_PYTHON_VALUES = {}\n\n\nclass ScoutConfigPython(object):\n    \"\"\"\n    A configuration overlay that lets other parts of python set values.\n    \"\"\"\n\n    def name(self):\n        return \"Python\"\n\n    def has_config(self, key):\n        return key in SCOUT_PYTHON_VALUES\n\n    def value(self, key):\n        return SCOUT_PYTHON_VALUES[key]\n\n\nclass ScoutConfigEnv(object):\n    \"\"\"\n    Reads configuration from environment by prefixing the key\n    requested with \"SCOUT_\"\n\n    Example: the `log_level` config looks for SCOUT_LOG_LEVEL\n    environment variable\n    \"\"\"\n\n    def name(self):\n        return \"ENV\"\n\n    def has_config(self, key):\n        env_key = self.modify_key(key)\n        return env_key in os.environ\n\n    def value(self, key):\n        env_key = self.modify_key(key)\n        return os.environ[env_key]\n\n    def modify_key(self, key):\n        env_key = (\"SCOUT_\" + key).upper()\n        return env_key\n\n\nclass ScoutConfigDerived(object):\n    \"\"\"\n    A configuration overlay that calculates from other values.\n    \"\"\"\n\n    def __init__(self, config):\n        \"\"\"\n        config argument is the overall ScoutConfig var, so we can lookup the\n        components of the derived info.\n        \"\"\"\n        self.config = config\n\n    def name(self):\n        return \"Derived\"\n\n    def has_config(self, key):\n        return self.lookup_func(key) is not None\n\n    def value(self, key):\n        return self.lookup_func(key)()\n\n    def lookup_func(self, key):\n        \"\"\"\n        Returns the derive_#{key} function, or None if it isn't defined\n        \"\"\"\n        func_name = \"derive_\" + key\n        return getattr(self, func_name, None)\n\n    def derive_socket_path(self):\n        return \"{}/{}/scout-agent.sock\".format(\n            self.config.value(\"core_agent_dir\"),\n            self.config.value(\"core_agent_full_name\"),\n        )\n\n    def derive_core_agent_full_name(self):\n        triple = self.config.value(\"core_agent_triple\")\n        if not platform_detection.is_valid_triple(triple):\n            logger.warning(\"Invalid value for core_agent_triple: %s\", triple)\n        return \"{name}-{version}-{triple}\".format(\n            name=\"scout_apm_core\",\n            version=self.config.value(\"core_agent_version\"),\n            triple=triple,\n        )\n\n    def derive_core_agent_triple(self):\n        return platform_detection.get_triple()\n\n\nclass ScoutConfigDefaults(object):\n    \"\"\"\n    Provides default values for important configurations\n    \"\"\"\n\n    def name(self):\n        return \"Defaults\"\n\n    def __init__(self):\n        self.defaults = {\n            \"app_server\": \"\",\n            \"application_root\": \"\",\n            \"core_agent_dir\": \"/tmp/scout_apm_core\",\n            \"core_agent_download\": True,\n            \"core_agent_launch\": True,\n            \"core_agent_permissions\": 700,\n            \"core_agent_version\": \"v1.2.0\",  # can be an exact tag name, or 'latest'\n            \"disabled_instruments\": [],\n            \"download_url\": \"https://s3-us-west-1.amazonaws.com/scout-public-downloads/apm_core_agent/release\",  # noqa: E501\n            \"framework\": \"\",\n            \"framework_version\": \"\",\n            \"hostname\": None,\n            \"key\": \"\",\n            \"log_level\": \"info\",\n            \"monitor\": False,\n            \"name\": \"\",\n            \"revision_sha\": self._git_revision_sha(),\n            \"scm_subdirectory\": \"\",\n            \"uri_reporting\": \"filtered_params\",\n        }\n\n    def _git_revision_sha(self):\n        # N.B. The environment variable SCOUT_REVISION_SHA may also be used,\n        # but that will be picked up by ScoutConfigEnv\n        return os.environ.get(\"HEROKU_SLUG_COMMIT\", \"\")\n\n    def has_config(self, key):\n        return key in self.defaults\n\n    def value(self, key):\n        return self.defaults[key]\n\n\n# Always returns None to any key\nclass ScoutConfigNull(object):\n    \"\"\"\n    Always answers that a key is present, but the value is None\n\n    Used as the last step of the layered configuration.\n    \"\"\"\n\n    def name(self):\n        return \"Null\"\n\n    def has_config(self, key):\n        return True\n\n    def value(self, key):\n        return None\n\n\ndef convert_to_bool(value):\n    if isinstance(value, bool):\n        return value\n    if isinstance(value, string_type):\n        return value.lower() in (\"yes\", \"true\", \"t\", \"1\")\n    # Unknown type - default to false?\n    return False\n\n\ndef convert_to_list(value):\n    if isinstance(value, list):\n        return value\n    if isinstance(value, tuple):\n        return list(value)\n    if isinstance(value, string_type):\n        # Split on commas\n        return [item.strip() for item in value.split(\",\") if item]\n    # Unknown type - default to empty?\n    return []\n\n\nCONVERSIONS = {\n    \"core_agent_download\": convert_to_bool,\n    \"core_agent_launch\": convert_to_bool,\n    \"monitor\": convert_to_bool,\n    \"disabled_instruments\": convert_to_list,\n    \"ignore\": convert_to_list,\n}\n", "path": "src/scout_apm/core/config.py"}]}