prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  The api key in the st2api log is not obfuscated
  ##### SUMMARY
  The user found in clean API key in query request (for the load balancer health check)
  ```GET /api/v1/?st2-api-key=foo HTTP/1.1```
  
  ##### ISSUE TYPE
   - Bug Report
   
  ##### STACKSTORM VERSION
  st2 2.10.3, on Python 2.7.12
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `st2common/st2common/middleware/logging.py`
  Content:
  ```
  1 # Licensed to the StackStorm, Inc ('StackStorm') under one or more
  2 # contributor license agreements.  See the NOTICE file distributed with
  3 # this work for additional information regarding copyright ownership.
  4 # The ASF licenses this file to You under the Apache License, Version 2.0
  5 # (the "License"); you may not use this file except in compliance with
  6 # the License.  You may obtain a copy of the License at
  7 #
  8 #     http://www.apache.org/licenses/LICENSE-2.0
  9 #
  10 # Unless required by applicable law or agreed to in writing, software
  11 # distributed under the License is distributed on an "AS IS" BASIS,
  12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 # See the License for the specific language governing permissions and
  14 # limitations under the License.
  15 
  16 from __future__ import absolute_import
  17 import time
  18 import types
  19 import itertools
  20 
  21 from st2common.constants.api import REQUEST_ID_HEADER
  22 from st2common import log as logging
  23 from st2common.router import Request, NotFoundException
  24 
  25 LOG = logging.getLogger(__name__)
  26 
  27 try:
  28     clock = time.perf_counter
  29 except AttributeError:
  30     clock = time.time
  31 
  32 
  33 class LoggingMiddleware(object):
  34     """
  35     Logs all incoming requests and outgoing responses
  36     """
  37 
  38     def __init__(self, app, router):
  39         self.app = app
  40         self.router = router
  41 
  42     def __call__(self, environ, start_response):
  43         start_time = clock()
  44         status_code = []
  45         content_length = []
  46 
  47         request = Request(environ)
  48 
  49         # Log the incoming request
  50         values = {
  51             'method': request.method,
  52             'path': request.path,
  53             'remote_addr': request.remote_addr,
  54             'query': request.GET.dict_of_lists(),
  55             'request_id': request.headers.get(REQUEST_ID_HEADER, None)
  56         }
  57 
  58         LOG.info('%(request_id)s - %(method)s %(path)s with query=%(query)s' %
  59                  values, extra=values)
  60 
  61         def custom_start_response(status, headers, exc_info=None):
  62             status_code.append(int(status.split(' ')[0]))
  63 
  64             for name, value in headers:
  65                 if name.lower() == 'content-length':
  66                     content_length.append(int(value))
  67                     break
  68 
  69             return start_response(status, headers, exc_info)
  70 
  71         retval = self.app(environ, custom_start_response)
  72 
  73         try:
  74             endpoint, path_vars = self.router.match(request)
  75         except NotFoundException:
  76             endpoint = {}
  77 
  78         log_result = endpoint.get('x-log-result', True)
  79 
  80         if isinstance(retval, (types.GeneratorType, itertools.chain)):
  81             # Note: We don't log the result when return value is a generator, because this would
  82             # result in calling str() on the generator and as such, exhausting it
  83             content_length = [float('inf')]
  84             log_result = False
  85 
  86         # Log the response
  87         values = {
  88             'method': request.method,
  89             'path': request.path,
  90             'remote_addr': request.remote_addr,
  91             'status': status_code[0],
  92             'runtime': float("{0:.3f}".format((clock() - start_time) * 10**3)),
  93             'content_length': content_length[0] if content_length else len(b''.join(retval)),
  94             'request_id': request.headers.get(REQUEST_ID_HEADER, None)
  95         }
  96 
  97         log_msg = '%(request_id)s - %(status)s %(content_length)s %(runtime)sms' % (values)
  98         LOG.info(log_msg, extra=values)
  99 
  100         if log_result:
  101             values['result'] = retval[0]
  102             log_msg = ('%(request_id)s - %(status)s %(content_length)s %(runtime)sms\n%(result)s' %
  103                       (values))
  104             LOG.debug(log_msg, extra=values)
  105 
  106         return retval
  107
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/st2common/st2common/middleware/logging.py b/st2common/st2common/middleware/logging.py
  --- a/st2common/st2common/middleware/logging.py
  +++ b/st2common/st2common/middleware/logging.py
  @@ -14,16 +14,28 @@
   # limitations under the License.
   
   from __future__ import absolute_import
  +
   import time
   import types
   import itertools
   
  +from oslo_config import cfg
  +
   from st2common.constants.api import REQUEST_ID_HEADER
  +from st2common.constants.auth import QUERY_PARAM_ATTRIBUTE_NAME
  +from st2common.constants.auth import QUERY_PARAM_API_KEY_ATTRIBUTE_NAME
  +from st2common.constants.secrets import MASKED_ATTRIBUTE_VALUE
  +from st2common.constants.secrets import MASKED_ATTRIBUTES_BLACKLIST
   from st2common import log as logging
   from st2common.router import Request, NotFoundException
   
   LOG = logging.getLogger(__name__)
   
  +SECRET_QUERY_PARAMS = [
  +    QUERY_PARAM_ATTRIBUTE_NAME,
  +    QUERY_PARAM_API_KEY_ATTRIBUTE_NAME
  +] + MASKED_ATTRIBUTES_BLACKLIST
  +
   try:
       clock = time.perf_counter
   except AttributeError:
  @@ -46,12 +58,20 @@
   
           request = Request(environ)
   
  +        query_params = request.GET.dict_of_lists()
  +
  +        # Mask secret / sensitive query params
  +        secret_query_params = SECRET_QUERY_PARAMS + cfg.CONF.log.mask_secrets_blacklist
  +        for param_name in secret_query_params:
  +            if param_name in query_params:
  +                query_params[param_name] = MASKED_ATTRIBUTE_VALUE
  +
           # Log the incoming request
           values = {
               'method': request.method,
               'path': request.path,
               'remote_addr': request.remote_addr,
  -            'query': request.GET.dict_of_lists(),
  +            'query': query_params,
               'request_id': request.headers.get(REQUEST_ID_HEADER, None)
           }
  
verification_info: {"golden_diff": "diff --git a/st2common/st2common/middleware/logging.py b/st2common/st2common/middleware/logging.py\n--- a/st2common/st2common/middleware/logging.py\n+++ b/st2common/st2common/middleware/logging.py\n@@ -14,16 +14,28 @@\n # limitations under the License.\n \n from __future__ import absolute_import\n+\n import time\n import types\n import itertools\n \n+from oslo_config import cfg\n+\n from st2common.constants.api import REQUEST_ID_HEADER\n+from st2common.constants.auth import QUERY_PARAM_ATTRIBUTE_NAME\n+from st2common.constants.auth import QUERY_PARAM_API_KEY_ATTRIBUTE_NAME\n+from st2common.constants.secrets import MASKED_ATTRIBUTE_VALUE\n+from st2common.constants.secrets import MASKED_ATTRIBUTES_BLACKLIST\n from st2common import log as logging\n from st2common.router import Request, NotFoundException\n \n LOG = logging.getLogger(__name__)\n \n+SECRET_QUERY_PARAMS = [\n+    QUERY_PARAM_ATTRIBUTE_NAME,\n+    QUERY_PARAM_API_KEY_ATTRIBUTE_NAME\n+] + MASKED_ATTRIBUTES_BLACKLIST\n+\n try:\n     clock = time.perf_counter\n except AttributeError:\n@@ -46,12 +58,20 @@\n \n         request = Request(environ)\n \n+        query_params = request.GET.dict_of_lists()\n+\n+        # Mask secret / sensitive query params\n+        secret_query_params = SECRET_QUERY_PARAMS + cfg.CONF.log.mask_secrets_blacklist\n+        for param_name in secret_query_params:\n+            if param_name in query_params:\n+                query_params[param_name] = MASKED_ATTRIBUTE_VALUE\n+\n         # Log the incoming request\n         values = {\n             'method': request.method,\n             'path': request.path,\n             'remote_addr': request.remote_addr,\n-            'query': request.GET.dict_of_lists(),\n+            'query': query_params,\n             'request_id': request.headers.get(REQUEST_ID_HEADER, None)\n         }\n", "issue": "The api key in the st2api log is not obfuscated\n##### SUMMARY\r\nThe user found in clean API key in query request (for the load balancer health check)\r\n```GET /api/v1/?st2-api-key=foo HTTP/1.1```\r\n\r\n##### ISSUE TYPE\r\n - Bug Report\r\n \r\n##### STACKSTORM VERSION\r\nst2 2.10.3, on Python 2.7.12\n", "before_files": [{"content": "# Licensed to the StackStorm, Inc ('StackStorm') under one or more\n# contributor license agreements.  See the NOTICE file distributed with\n# this work for additional information regarding copyright ownership.\n# The ASF licenses this file to You under the Apache License, Version 2.0\n# (the \"License\"); you may not use this file except in compliance with\n# the License.  You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom __future__ import absolute_import\nimport time\nimport types\nimport itertools\n\nfrom st2common.constants.api import REQUEST_ID_HEADER\nfrom st2common import log as logging\nfrom st2common.router import Request, NotFoundException\n\nLOG = logging.getLogger(__name__)\n\ntry:\n    clock = time.perf_counter\nexcept AttributeError:\n    clock = time.time\n\n\nclass LoggingMiddleware(object):\n    \"\"\"\n    Logs all incoming requests and outgoing responses\n    \"\"\"\n\n    def __init__(self, app, router):\n        self.app = app\n        self.router = router\n\n    def __call__(self, environ, start_response):\n        start_time = clock()\n        status_code = []\n        content_length = []\n\n        request = Request(environ)\n\n        # Log the incoming request\n        values = {\n            'method': request.method,\n            'path': request.path,\n            'remote_addr': request.remote_addr,\n            'query': request.GET.dict_of_lists(),\n            'request_id': request.headers.get(REQUEST_ID_HEADER, None)\n        }\n\n        LOG.info('%(request_id)s - %(method)s %(path)s with query=%(query)s' %\n                 values, extra=values)\n\n        def custom_start_response(status, headers, exc_info=None):\n            status_code.append(int(status.split(' ')[0]))\n\n            for name, value in headers:\n                if name.lower() == 'content-length':\n                    content_length.append(int(value))\n                    break\n\n            return start_response(status, headers, exc_info)\n\n        retval = self.app(environ, custom_start_response)\n\n        try:\n            endpoint, path_vars = self.router.match(request)\n        except NotFoundException:\n            endpoint = {}\n\n        log_result = endpoint.get('x-log-result', True)\n\n        if isinstance(retval, (types.GeneratorType, itertools.chain)):\n            # Note: We don't log the result when return value is a generator, because this would\n            # result in calling str() on the generator and as such, exhausting it\n            content_length = [float('inf')]\n            log_result = False\n\n        # Log the response\n        values = {\n            'method': request.method,\n            'path': request.path,\n            'remote_addr': request.remote_addr,\n            'status': status_code[0],\n            'runtime': float(\"{0:.3f}\".format((clock() - start_time) * 10**3)),\n            'content_length': content_length[0] if content_length else len(b''.join(retval)),\n            'request_id': request.headers.get(REQUEST_ID_HEADER, None)\n        }\n\n        log_msg = '%(request_id)s - %(status)s %(content_length)s %(runtime)sms' % (values)\n        LOG.info(log_msg, extra=values)\n\n        if log_result:\n            values['result'] = retval[0]\n            log_msg = ('%(request_id)s - %(status)s %(content_length)s %(runtime)sms\\n%(result)s' %\n                      (values))\n            LOG.debug(log_msg, extra=values)\n\n        return retval\n", "path": "st2common/st2common/middleware/logging.py"}], "after_files": [{"content": "# Licensed to the StackStorm, Inc ('StackStorm') under one or more\n# contributor license agreements.  See the NOTICE file distributed with\n# this work for additional information regarding copyright ownership.\n# The ASF licenses this file to You under the Apache License, Version 2.0\n# (the \"License\"); you may not use this file except in compliance with\n# the License.  You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom __future__ import absolute_import\n\nimport time\nimport types\nimport itertools\n\nfrom oslo_config import cfg\n\nfrom st2common.constants.api import REQUEST_ID_HEADER\nfrom st2common.constants.auth import QUERY_PARAM_ATTRIBUTE_NAME\nfrom st2common.constants.auth import QUERY_PARAM_API_KEY_ATTRIBUTE_NAME\nfrom st2common.constants.secrets import MASKED_ATTRIBUTE_VALUE\nfrom st2common.constants.secrets import MASKED_ATTRIBUTES_BLACKLIST\nfrom st2common import log as logging\nfrom st2common.router import Request, NotFoundException\n\nLOG = logging.getLogger(__name__)\n\nSECRET_QUERY_PARAMS = [\n    QUERY_PARAM_ATTRIBUTE_NAME,\n    QUERY_PARAM_API_KEY_ATTRIBUTE_NAME\n] + MASKED_ATTRIBUTES_BLACKLIST\n\ntry:\n    clock = time.perf_counter\nexcept AttributeError:\n    clock = time.time\n\n\nclass LoggingMiddleware(object):\n    \"\"\"\n    Logs all incoming requests and outgoing responses\n    \"\"\"\n\n    def __init__(self, app, router):\n        self.app = app\n        self.router = router\n\n    def __call__(self, environ, start_response):\n        start_time = clock()\n        status_code = []\n        content_length = []\n\n        request = Request(environ)\n\n        query_params = request.GET.dict_of_lists()\n\n        # Mask secret / sensitive query params\n        secret_query_params = SECRET_QUERY_PARAMS + cfg.CONF.log.mask_secrets_blacklist\n        for param_name in secret_query_params:\n            if param_name in query_params:\n                query_params[param_name] = MASKED_ATTRIBUTE_VALUE\n\n        # Log the incoming request\n        values = {\n            'method': request.method,\n            'path': request.path,\n            'remote_addr': request.remote_addr,\n            'query': query_params,\n            'request_id': request.headers.get(REQUEST_ID_HEADER, None)\n        }\n\n        LOG.info('%(request_id)s - %(method)s %(path)s with query=%(query)s' %\n                 values, extra=values)\n\n        def custom_start_response(status, headers, exc_info=None):\n            status_code.append(int(status.split(' ')[0]))\n\n            for name, value in headers:\n                if name.lower() == 'content-length':\n                    content_length.append(int(value))\n                    break\n\n            return start_response(status, headers, exc_info)\n\n        retval = self.app(environ, custom_start_response)\n\n        try:\n            endpoint, path_vars = self.router.match(request)\n        except NotFoundException:\n            endpoint = {}\n\n        log_result = endpoint.get('x-log-result', True)\n\n        if isinstance(retval, (types.GeneratorType, itertools.chain)):\n            # Note: We don't log the result when return value is a generator, because this would\n            # result in calling str() on the generator and as such, exhausting it\n            content_length = [float('inf')]\n            log_result = False\n\n        # Log the response\n        values = {\n            'method': request.method,\n            'path': request.path,\n            'remote_addr': request.remote_addr,\n            'status': status_code[0],\n            'runtime': float(\"{0:.3f}\".format((clock() - start_time) * 10**3)),\n            'content_length': content_length[0] if content_length else len(b''.join(retval)),\n            'request_id': request.headers.get(REQUEST_ID_HEADER, None)\n        }\n\n        log_msg = '%(request_id)s - %(status)s %(content_length)s %(runtime)sms' % (values)\n        LOG.info(log_msg, extra=values)\n\n        if log_result:\n            values['result'] = retval[0]\n            log_msg = ('%(request_id)s - %(status)s %(content_length)s %(runtime)sms\\n%(result)s' %\n                      (values))\n            LOG.debug(log_msg, extra=values)\n\n        return retval\n", "path": "st2common/st2common/middleware/logging.py"}]}