We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
ST2 logs writing "(unknown file)" in log prefix, instead of module name
## SUMMARY

In ST2 v3.2, the st2sensorcontainer.log, and in v3.3 all st2 logs have the `module` name replaced with `(unknown file)`


### STACKSTORM VERSION

v3.2, v3.3, EL8 and U18.04
Python 3.6

### OS, environment, install method

One Line and Ansible so far. 

## Steps to reproduce the problem

Show how to reproduce the problem, using a minimal test-case. Make sure to include any content
(pack content - workflows, actions, etc.) which are needed to reproduce the problem.

## Expected Results

No `(unknown file)`, but the python module name. 

## Actual Results
```
2020-10-13 17:55:13,787 140460262501416 INFO (unknown file) [-] Sensor linux.FileWatchSensor started
2020-10-13 17:55:15,444 139725927337456 INFO (unknown file) [-] No config found for sensor "FileWatchSensor"
2020-10-13 17:55:15,446 139725927337456 INFO (unknown file) [-] Watcher started
2020-10-13 17:55:15,446 139725927337456 INFO (unknown file) [-] Running sensor initialization code
2020-10-13 17:55:15,454 139725927337456 INFO (unknown file) [-] Running sensor in passive mode
```



--- 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/log.py`
Content:
```
1 # Copyright 2020 The StackStorm Authors.
2 # Copyright 2019 Extreme Networks, Inc.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # 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 
18 import os
19 import sys
20 import logging
21 import logging.config
22 import logging.handlers
23 import traceback
24 from functools import wraps
25 
26 import six
27 
28 from st2common.logging.filters import LoggerNameExclusionFilter
29 
30 # Those are here for backward compatibility reasons
31 from st2common.logging.handlers import FormatNamedFileHandler
32 from st2common.logging.handlers import ConfigurableSyslogHandler
33 from st2common.util.misc import prefix_dict_keys
34 from st2common.util.misc import get_normalized_file_path
35 
36 __all__ = [
37     'getLogger',
38     'setup',
39 
40     'FormatNamedFileHandler',
41     'ConfigurableSyslogHandler',
42 
43     'LoggingStream',
44 
45     'ignore_lib2to3_log_messages',
46     'ignore_statsd_log_messages'
47 ]
48 
49 # NOTE: We set AUDIT to the highest log level which means AUDIT log messages will always be
50 # included (e.g. also if log level is set to INFO). To avoid that, we need to explicitly filter
51 # out AUDIT log level in service setup code.
52 logging.AUDIT = logging.CRITICAL + 10
53 logging.addLevelName(logging.AUDIT, 'AUDIT')
54 
55 LOGGER_KEYS = [
56     'debug',
57     'info',
58     'warning',
59     'error',
60     'critical',
61     'exception',
62     'log',
63 
64     'audit'
65 ]
66 
67 # Note: This attribute is used by "find_caller" so it can correctly exclude this file when looking
68 # for the logger method caller frame.
69 _srcfile = get_normalized_file_path(__file__)
70 
71 
72 def find_caller(*args, **kwargs):
73     """
74     Find the stack frame of the caller so that we can note the source file name, line number and
75     function name.
76 
77     Note: This is based on logging/__init__.py:findCaller and modified so it takes into account
78     this file - https://hg.python.org/cpython/file/2.7/Lib/logging/__init__.py#l1233
79     """
80     rv = '(unknown file)', 0, '(unknown function)'
81 
82     try:
83         f = logging.currentframe().f_back
84         while hasattr(f, 'f_code'):
85             co = f.f_code
86             filename = os.path.normcase(co.co_filename)
87             if filename in (_srcfile, logging._srcfile):  # This line is modified.
88                 f = f.f_back
89                 continue
90             rv = (filename, f.f_lineno, co.co_name)
91             break
92     except Exception:
93         pass
94 
95     return rv
96 
97 
98 def decorate_log_method(func):
99     @wraps(func)
100     def func_wrapper(*args, **kwargs):
101         # Prefix extra keys with underscore
102         if 'extra' in kwargs:
103             kwargs['extra'] = prefix_dict_keys(dictionary=kwargs['extra'], prefix='_')
104 
105         try:
106             return func(*args, **kwargs)
107         except TypeError as e:
108             # In some version of Python 2.7, logger.exception doesn't take any kwargs so we need
109             # this hack :/
110             # See:
111             # - https://docs.python.org/release/2.7.3/library/logging.html#logging.Logger.exception
112             # - https://docs.python.org/release/2.7.7/library/logging.html#logging.Logger.exception
113             if 'got an unexpected keyword argument \'extra\'' in six.text_type(e):
114                 kwargs.pop('extra', None)
115                 return func(*args, **kwargs)
116             raise e
117     return func_wrapper
118 
119 
120 def decorate_logger_methods(logger):
121     """
122     Decorate all the logger methods so all the keys in the extra dictionary are
123     automatically prefixed with an underscore to avoid clashes with standard log
124     record attributes.
125     """
126 
127     # Note: We override findCaller with our custom implementation which takes into account this
128     # module.
129     # This way filename, module, funcName and lineno LogRecord attributes contain correct values
130     # instead of all pointing to decorate_log_method.
131     logger.findCaller = find_caller
132     for key in LOGGER_KEYS:
133         log_method = getattr(logger, key)
134         log_method = decorate_log_method(log_method)
135         setattr(logger, key, log_method)
136 
137     return logger
138 
139 
140 def getLogger(name):
141     # make sure that prefix isn't appended multiple times to preserve logging name hierarchy
142     prefix = 'st2.'
143     if name.startswith(prefix):
144         logger = logging.getLogger(name)
145     else:
146         logger_name = '{}{}'.format(prefix, name)
147         logger = logging.getLogger(logger_name)
148 
149     logger = decorate_logger_methods(logger=logger)
150     return logger
151 
152 
153 class LoggingStream(object):
154 
155     def __init__(self, name, level=logging.ERROR):
156         self._logger = getLogger(name)
157         self._level = level
158 
159     def write(self, message):
160         self._logger._log(self._level, message, None)
161 
162     def flush(self):
163         pass
164 
165 
166 def _audit(logger, msg, *args, **kwargs):
167     if logger.isEnabledFor(logging.AUDIT):
168         logger._log(logging.AUDIT, msg, args, **kwargs)
169 
170 
171 logging.Logger.audit = _audit
172 
173 
174 def _add_exclusion_filters(handlers, excludes=None):
175     if excludes:
176         for h in handlers:
177             h.addFilter(LoggerNameExclusionFilter(excludes))
178 
179 
180 def _redirect_stderr():
181     # It is ok to redirect stderr as none of the st2 handlers write to stderr.
182     sys.stderr = LoggingStream('STDERR')
183 
184 
185 def setup(config_file, redirect_stderr=True, excludes=None, disable_existing_loggers=False,
186           st2_conf_path=None):
187     """
188     Configure logging from file.
189 
190     :param st2_conf_path: Optional path to st2.conf file. If provided and "config_file" path is
191                           relative to st2.conf path, the config_file path will get resolved to full
192                           absolute path relative to st2.conf.
193     :type st2_conf_path: ``str``
194     """
195     if st2_conf_path and config_file[:2] == './' and not os.path.isfile(config_file):
196         # Logging config path is relative to st2.conf, resolve it to full absolute path
197         directory = os.path.dirname(st2_conf_path)
198         config_file_name = os.path.basename(config_file)
199         config_file = os.path.join(directory, config_file_name)
200 
201     try:
202         logging.config.fileConfig(config_file,
203                                   defaults=None,
204                                   disable_existing_loggers=disable_existing_loggers)
205         handlers = logging.getLoggerClass().manager.root.handlers
206         _add_exclusion_filters(handlers=handlers, excludes=excludes)
207         if redirect_stderr:
208             _redirect_stderr()
209     except Exception as exc:
210         exc_cls = type(exc)
211         tb_msg = traceback.format_exc()
212 
213         msg = str(exc)
214         msg += '\n\n' + tb_msg
215 
216         # revert stderr redirection since there is no logger in place.
217         sys.stderr = sys.__stderr__
218 
219         # No logger yet therefore write to stderr
220         sys.stderr.write('ERROR: %s' % (msg))
221 
222         raise exc_cls(six.text_type(msg))
223 
224 
225 def ignore_lib2to3_log_messages():
226     """
227     Work around to ignore "Generating grammar tables from" log messages which are logged under
228     INFO by default by libraries such as networkx which use 2to3.
229     """
230     import lib2to3.pgen2.driver
231 
232     class MockLoggingModule(object):
233         def getLogger(self, *args, **kwargs):
234             return logging.getLogger('lib2to3')
235 
236     lib2to3.pgen2.driver.logging = MockLoggingModule()
237     logging.getLogger('lib2to3').setLevel(logging.ERROR)
238 
239 
240 def ignore_statsd_log_messages():
241     """
242     By default statsd client logs all the operations under INFO and that causes a lot of noise.
243 
244     This pull request silences all the statsd INFO log messages.
245     """
246     import statsd.connection
247     import statsd.client
248 
249     class MockLoggingModule(object):
250         def getLogger(self, *args, **kwargs):
251             return logging.getLogger('statsd')
252 
253     statsd.connection.logging = MockLoggingModule()
254     statsd.client.logging = MockLoggingModule()
255     logging.getLogger('statsd').setLevel(logging.ERROR)
256
```
--- 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/st2common/st2common/log.py b/st2common/st2common/log.py
--- a/st2common/st2common/log.py
+++ b/st2common/st2common/log.py
@@ -15,6 +15,7 @@
 
 from __future__ import absolute_import
 
+import io
 import os
 import sys
 import logging
@@ -69,25 +70,64 @@
 _srcfile = get_normalized_file_path(__file__)
 
 
-def find_caller(*args, **kwargs):
+def find_caller(stack_info=False, stacklevel=1):
     """
     Find the stack frame of the caller so that we can note the source file name, line number and
     function name.
 
     Note: This is based on logging/__init__.py:findCaller and modified so it takes into account
-    this file - https://hg.python.org/cpython/file/2.7/Lib/logging/__init__.py#l1233
+    this file:
+    https://github.com/python/cpython/blob/2.7/Lib/logging/__init__.py#L1240-L1259
+
+    The Python 3.x implementation adds in a new argument `stack_info` and `stacklevel`
+    and expects a 4-element tuple to be returned, rather than a 3-element tuple in
+    the python 2 implementation.
+    We derived our implementation from the Python 3.9 source code here:
+    https://github.com/python/cpython/blob/3.9/Lib/logging/__init__.py#L1502-L1536
+
+    We've made the appropriate changes so that we're python 2 and python 3 compatible depending
+    on what runtine we're working in.
     """
-    rv = '(unknown file)', 0, '(unknown function)'
+    if six.PY2:
+        rv = '(unknown file)', 0, '(unknown function)'
+    else:
+        # python 3, has extra tuple element at the end for stack information
+        rv = '(unknown file)', 0, '(unknown function)', None
 
     try:
-        f = logging.currentframe().f_back
+        f = logging.currentframe()
+        # On some versions of IronPython, currentframe() returns None if
+        # IronPython isn't run with -X:Frames.
+        if f is not None:
+            f = f.f_back
+        orig_f = f
+        while f and stacklevel > 1:
+            f = f.f_back
+            stacklevel -= 1
+        if not f:
+            f = orig_f
+
         while hasattr(f, 'f_code'):
             co = f.f_code
             filename = os.path.normcase(co.co_filename)
             if filename in (_srcfile, logging._srcfile):  # This line is modified.
                 f = f.f_back
                 continue
-            rv = (filename, f.f_lineno, co.co_name)
+
+            if six.PY2:
+                rv = (filename, f.f_lineno, co.co_name)
+            else:
+                # python 3, new stack_info processing and extra tuple return value
+                sinfo = None
+                if stack_info:
+                    sio = io.StringIO()
+                    sio.write('Stack (most recent call last):\n')
+                    traceback.print_stack(f, file=sio)
+                    sinfo = sio.getvalue()
+                    if sinfo[-1] == '\n':
+                        sinfo = sinfo[:-1]
+                    sio.close()
+                rv = (filename, f.f_lineno, co.co_name, sinfo)
             break
     except Exception:
         pass

{"golden_diff": "diff --git a/st2common/st2common/log.py b/st2common/st2common/log.py\n--- a/st2common/st2common/log.py\n+++ b/st2common/st2common/log.py\n@@ -15,6 +15,7 @@\n \n from __future__ import absolute_import\n \n+import io\n import os\n import sys\n import logging\n@@ -69,25 +70,64 @@\n _srcfile = get_normalized_file_path(__file__)\n \n \n-def find_caller(*args, **kwargs):\n+def find_caller(stack_info=False, stacklevel=1):\n     \"\"\"\n     Find the stack frame of the caller so that we can note the source file name, line number and\n     function name.\n \n     Note: This is based on logging/__init__.py:findCaller and modified so it takes into account\n-    this file - https://hg.python.org/cpython/file/2.7/Lib/logging/__init__.py#l1233\n+    this file:\n+    https://github.com/python/cpython/blob/2.7/Lib/logging/__init__.py#L1240-L1259\n+\n+    The Python 3.x implementation adds in a new argument `stack_info` and `stacklevel`\n+    and expects a 4-element tuple to be returned, rather than a 3-element tuple in\n+    the python 2 implementation.\n+    We derived our implementation from the Python 3.9 source code here:\n+    https://github.com/python/cpython/blob/3.9/Lib/logging/__init__.py#L1502-L1536\n+\n+    We've made the appropriate changes so that we're python 2 and python 3 compatible depending\n+    on what runtine we're working in.\n     \"\"\"\n-    rv = '(unknown file)', 0, '(unknown function)'\n+    if six.PY2:\n+        rv = '(unknown file)', 0, '(unknown function)'\n+    else:\n+        # python 3, has extra tuple element at the end for stack information\n+        rv = '(unknown file)', 0, '(unknown function)', None\n \n     try:\n-        f = logging.currentframe().f_back\n+        f = logging.currentframe()\n+        # On some versions of IronPython, currentframe() returns None if\n+        # IronPython isn't run with -X:Frames.\n+        if f is not None:\n+            f = f.f_back\n+        orig_f = f\n+        while f and stacklevel > 1:\n+            f = f.f_back\n+            stacklevel -= 1\n+        if not f:\n+            f = orig_f\n+\n         while hasattr(f, 'f_code'):\n             co = f.f_code\n             filename = os.path.normcase(co.co_filename)\n             if filename in (_srcfile, logging._srcfile):  # This line is modified.\n                 f = f.f_back\n                 continue\n-            rv = (filename, f.f_lineno, co.co_name)\n+\n+            if six.PY2:\n+                rv = (filename, f.f_lineno, co.co_name)\n+            else:\n+                # python 3, new stack_info processing and extra tuple return value\n+                sinfo = None\n+                if stack_info:\n+                    sio = io.StringIO()\n+                    sio.write('Stack (most recent call last):\\n')\n+                    traceback.print_stack(f, file=sio)\n+                    sinfo = sio.getvalue()\n+                    if sinfo[-1] == '\\n':\n+                        sinfo = sinfo[:-1]\n+                    sio.close()\n+                rv = (filename, f.f_lineno, co.co_name, sinfo)\n             break\n     except Exception:\n         pass\n", "issue": "ST2 logs writing \"(unknown file)\" in log prefix, instead of module name\n## SUMMARY\r\n\r\nIn ST2 v3.2, the st2sensorcontainer.log, and in v3.3 all st2 logs have the `module` name replaced with `(unknown file)`\r\n\r\n\r\n### STACKSTORM VERSION\r\n\r\nv3.2, v3.3, EL8 and U18.04\r\nPython 3.6\r\n\r\n### OS, environment, install method\r\n\r\nOne Line and Ansible so far. \r\n\r\n## Steps to reproduce the problem\r\n\r\nShow how to reproduce the problem, using a minimal test-case. Make sure to include any content\r\n(pack content - workflows, actions, etc.) which are needed to reproduce the problem.\r\n\r\n## Expected Results\r\n\r\nNo `(unknown file)`, but the python module name. \r\n\r\n## Actual Results\r\n```\r\n2020-10-13 17:55:13,787 140460262501416 INFO (unknown file) [-] Sensor linux.FileWatchSensor started\r\n2020-10-13 17:55:15,444 139725927337456 INFO (unknown file) [-] No config found for sensor \"FileWatchSensor\"\r\n2020-10-13 17:55:15,446 139725927337456 INFO (unknown file) [-] Watcher started\r\n2020-10-13 17:55:15,446 139725927337456 INFO (unknown file) [-] Running sensor initialization code\r\n2020-10-13 17:55:15,454 139725927337456 INFO (unknown file) [-] Running sensor in passive mode\r\n```\r\n\r\n\n", "before_files": [{"content": "# Copyright 2020 The StackStorm Authors.\n# Copyright 2019 Extreme Networks, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# 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 os\nimport sys\nimport logging\nimport logging.config\nimport logging.handlers\nimport traceback\nfrom functools import wraps\n\nimport six\n\nfrom st2common.logging.filters import LoggerNameExclusionFilter\n\n# Those are here for backward compatibility reasons\nfrom st2common.logging.handlers import FormatNamedFileHandler\nfrom st2common.logging.handlers import ConfigurableSyslogHandler\nfrom st2common.util.misc import prefix_dict_keys\nfrom st2common.util.misc import get_normalized_file_path\n\n__all__ = [\n    'getLogger',\n    'setup',\n\n    'FormatNamedFileHandler',\n    'ConfigurableSyslogHandler',\n\n    'LoggingStream',\n\n    'ignore_lib2to3_log_messages',\n    'ignore_statsd_log_messages'\n]\n\n# NOTE: We set AUDIT to the highest log level which means AUDIT log messages will always be\n# included (e.g. also if log level is set to INFO). To avoid that, we need to explicitly filter\n# out AUDIT log level in service setup code.\nlogging.AUDIT = logging.CRITICAL + 10\nlogging.addLevelName(logging.AUDIT, 'AUDIT')\n\nLOGGER_KEYS = [\n    'debug',\n    'info',\n    'warning',\n    'error',\n    'critical',\n    'exception',\n    'log',\n\n    'audit'\n]\n\n# Note: This attribute is used by \"find_caller\" so it can correctly exclude this file when looking\n# for the logger method caller frame.\n_srcfile = get_normalized_file_path(__file__)\n\n\ndef find_caller(*args, **kwargs):\n    \"\"\"\n    Find the stack frame of the caller so that we can note the source file name, line number and\n    function name.\n\n    Note: This is based on logging/__init__.py:findCaller and modified so it takes into account\n    this file - https://hg.python.org/cpython/file/2.7/Lib/logging/__init__.py#l1233\n    \"\"\"\n    rv = '(unknown file)', 0, '(unknown function)'\n\n    try:\n        f = logging.currentframe().f_back\n        while hasattr(f, 'f_code'):\n            co = f.f_code\n            filename = os.path.normcase(co.co_filename)\n            if filename in (_srcfile, logging._srcfile):  # This line is modified.\n                f = f.f_back\n                continue\n            rv = (filename, f.f_lineno, co.co_name)\n            break\n    except Exception:\n        pass\n\n    return rv\n\n\ndef decorate_log_method(func):\n    @wraps(func)\n    def func_wrapper(*args, **kwargs):\n        # Prefix extra keys with underscore\n        if 'extra' in kwargs:\n            kwargs['extra'] = prefix_dict_keys(dictionary=kwargs['extra'], prefix='_')\n\n        try:\n            return func(*args, **kwargs)\n        except TypeError as e:\n            # In some version of Python 2.7, logger.exception doesn't take any kwargs so we need\n            # this hack :/\n            # See:\n            # - https://docs.python.org/release/2.7.3/library/logging.html#logging.Logger.exception\n            # - https://docs.python.org/release/2.7.7/library/logging.html#logging.Logger.exception\n            if 'got an unexpected keyword argument \\'extra\\'' in six.text_type(e):\n                kwargs.pop('extra', None)\n                return func(*args, **kwargs)\n            raise e\n    return func_wrapper\n\n\ndef decorate_logger_methods(logger):\n    \"\"\"\n    Decorate all the logger methods so all the keys in the extra dictionary are\n    automatically prefixed with an underscore to avoid clashes with standard log\n    record attributes.\n    \"\"\"\n\n    # Note: We override findCaller with our custom implementation which takes into account this\n    # module.\n    # This way filename, module, funcName and lineno LogRecord attributes contain correct values\n    # instead of all pointing to decorate_log_method.\n    logger.findCaller = find_caller\n    for key in LOGGER_KEYS:\n        log_method = getattr(logger, key)\n        log_method = decorate_log_method(log_method)\n        setattr(logger, key, log_method)\n\n    return logger\n\n\ndef getLogger(name):\n    # make sure that prefix isn't appended multiple times to preserve logging name hierarchy\n    prefix = 'st2.'\n    if name.startswith(prefix):\n        logger = logging.getLogger(name)\n    else:\n        logger_name = '{}{}'.format(prefix, name)\n        logger = logging.getLogger(logger_name)\n\n    logger = decorate_logger_methods(logger=logger)\n    return logger\n\n\nclass LoggingStream(object):\n\n    def __init__(self, name, level=logging.ERROR):\n        self._logger = getLogger(name)\n        self._level = level\n\n    def write(self, message):\n        self._logger._log(self._level, message, None)\n\n    def flush(self):\n        pass\n\n\ndef _audit(logger, msg, *args, **kwargs):\n    if logger.isEnabledFor(logging.AUDIT):\n        logger._log(logging.AUDIT, msg, args, **kwargs)\n\n\nlogging.Logger.audit = _audit\n\n\ndef _add_exclusion_filters(handlers, excludes=None):\n    if excludes:\n        for h in handlers:\n            h.addFilter(LoggerNameExclusionFilter(excludes))\n\n\ndef _redirect_stderr():\n    # It is ok to redirect stderr as none of the st2 handlers write to stderr.\n    sys.stderr = LoggingStream('STDERR')\n\n\ndef setup(config_file, redirect_stderr=True, excludes=None, disable_existing_loggers=False,\n          st2_conf_path=None):\n    \"\"\"\n    Configure logging from file.\n\n    :param st2_conf_path: Optional path to st2.conf file. If provided and \"config_file\" path is\n                          relative to st2.conf path, the config_file path will get resolved to full\n                          absolute path relative to st2.conf.\n    :type st2_conf_path: ``str``\n    \"\"\"\n    if st2_conf_path and config_file[:2] == './' and not os.path.isfile(config_file):\n        # Logging config path is relative to st2.conf, resolve it to full absolute path\n        directory = os.path.dirname(st2_conf_path)\n        config_file_name = os.path.basename(config_file)\n        config_file = os.path.join(directory, config_file_name)\n\n    try:\n        logging.config.fileConfig(config_file,\n                                  defaults=None,\n                                  disable_existing_loggers=disable_existing_loggers)\n        handlers = logging.getLoggerClass().manager.root.handlers\n        _add_exclusion_filters(handlers=handlers, excludes=excludes)\n        if redirect_stderr:\n            _redirect_stderr()\n    except Exception as exc:\n        exc_cls = type(exc)\n        tb_msg = traceback.format_exc()\n\n        msg = str(exc)\n        msg += '\\n\\n' + tb_msg\n\n        # revert stderr redirection since there is no logger in place.\n        sys.stderr = sys.__stderr__\n\n        # No logger yet therefore write to stderr\n        sys.stderr.write('ERROR: %s' % (msg))\n\n        raise exc_cls(six.text_type(msg))\n\n\ndef ignore_lib2to3_log_messages():\n    \"\"\"\n    Work around to ignore \"Generating grammar tables from\" log messages which are logged under\n    INFO by default by libraries such as networkx which use 2to3.\n    \"\"\"\n    import lib2to3.pgen2.driver\n\n    class MockLoggingModule(object):\n        def getLogger(self, *args, **kwargs):\n            return logging.getLogger('lib2to3')\n\n    lib2to3.pgen2.driver.logging = MockLoggingModule()\n    logging.getLogger('lib2to3').setLevel(logging.ERROR)\n\n\ndef ignore_statsd_log_messages():\n    \"\"\"\n    By default statsd client logs all the operations under INFO and that causes a lot of noise.\n\n    This pull request silences all the statsd INFO log messages.\n    \"\"\"\n    import statsd.connection\n    import statsd.client\n\n    class MockLoggingModule(object):\n        def getLogger(self, *args, **kwargs):\n            return logging.getLogger('statsd')\n\n    statsd.connection.logging = MockLoggingModule()\n    statsd.client.logging = MockLoggingModule()\n    logging.getLogger('statsd').setLevel(logging.ERROR)\n", "path": "st2common/st2common/log.py"}], "after_files": [{"content": "# Copyright 2020 The StackStorm Authors.\n# Copyright 2019 Extreme Networks, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# 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 io\nimport os\nimport sys\nimport logging\nimport logging.config\nimport logging.handlers\nimport traceback\nfrom functools import wraps\n\nimport six\n\nfrom st2common.logging.filters import LoggerNameExclusionFilter\n\n# Those are here for backward compatibility reasons\nfrom st2common.logging.handlers import FormatNamedFileHandler\nfrom st2common.logging.handlers import ConfigurableSyslogHandler\nfrom st2common.util.misc import prefix_dict_keys\nfrom st2common.util.misc import get_normalized_file_path\n\n__all__ = [\n    'getLogger',\n    'setup',\n\n    'FormatNamedFileHandler',\n    'ConfigurableSyslogHandler',\n\n    'LoggingStream',\n\n    'ignore_lib2to3_log_messages',\n    'ignore_statsd_log_messages'\n]\n\n# NOTE: We set AUDIT to the highest log level which means AUDIT log messages will always be\n# included (e.g. also if log level is set to INFO). To avoid that, we need to explicitly filter\n# out AUDIT log level in service setup code.\nlogging.AUDIT = logging.CRITICAL + 10\nlogging.addLevelName(logging.AUDIT, 'AUDIT')\n\nLOGGER_KEYS = [\n    'debug',\n    'info',\n    'warning',\n    'error',\n    'critical',\n    'exception',\n    'log',\n\n    'audit'\n]\n\n# Note: This attribute is used by \"find_caller\" so it can correctly exclude this file when looking\n# for the logger method caller frame.\n_srcfile = get_normalized_file_path(__file__)\n\n\ndef find_caller(stack_info=False, stacklevel=1):\n    \"\"\"\n    Find the stack frame of the caller so that we can note the source file name, line number and\n    function name.\n\n    Note: This is based on logging/__init__.py:findCaller and modified so it takes into account\n    this file:\n    https://github.com/python/cpython/blob/2.7/Lib/logging/__init__.py#L1240-L1259\n\n    The Python 3.x implementation adds in a new argument `stack_info` and `stacklevel`\n    and expects a 4-element tuple to be returned, rather than a 3-element tuple in\n    the python 2 implementation.\n    We derived our implementation from the Python 3.9 source code here:\n    https://github.com/python/cpython/blob/3.9/Lib/logging/__init__.py#L1502-L1536\n\n    We've made the appropriate changes so that we're python 2 and python 3 compatible depending\n    on what runtine we're working in.\n    \"\"\"\n    if six.PY2:\n        rv = '(unknown file)', 0, '(unknown function)'\n    else:\n        # python 3, has extra tuple element at the end for stack information\n        rv = '(unknown file)', 0, '(unknown function)', None\n\n    try:\n        f = logging.currentframe()\n        # On some versions of IronPython, currentframe() returns None if\n        # IronPython isn't run with -X:Frames.\n        if f is not None:\n            f = f.f_back\n        orig_f = f\n        while f and stacklevel > 1:\n            f = f.f_back\n            stacklevel -= 1\n        if not f:\n            f = orig_f\n\n        while hasattr(f, 'f_code'):\n            co = f.f_code\n            filename = os.path.normcase(co.co_filename)\n            if filename in (_srcfile, logging._srcfile):  # This line is modified.\n                f = f.f_back\n                continue\n\n            if six.PY2:\n                rv = (filename, f.f_lineno, co.co_name)\n            else:\n                # python 3, new stack_info processing and extra tuple return value\n                sinfo = None\n                if stack_info:\n                    sio = io.StringIO()\n                    sio.write('Stack (most recent call last):\\n')\n                    traceback.print_stack(f, file=sio)\n                    sinfo = sio.getvalue()\n                    if sinfo[-1] == '\\n':\n                        sinfo = sinfo[:-1]\n                    sio.close()\n                rv = (filename, f.f_lineno, co.co_name, sinfo)\n            break\n    except Exception:\n        pass\n\n    return rv\n\n\ndef decorate_log_method(func):\n    @wraps(func)\n    def func_wrapper(*args, **kwargs):\n        # Prefix extra keys with underscore\n        if 'extra' in kwargs:\n            kwargs['extra'] = prefix_dict_keys(dictionary=kwargs['extra'], prefix='_')\n\n        try:\n            return func(*args, **kwargs)\n        except TypeError as e:\n            # In some version of Python 2.7, logger.exception doesn't take any kwargs so we need\n            # this hack :/\n            # See:\n            # - https://docs.python.org/release/2.7.3/library/logging.html#logging.Logger.exception\n            # - https://docs.python.org/release/2.7.7/library/logging.html#logging.Logger.exception\n            if 'got an unexpected keyword argument \\'extra\\'' in six.text_type(e):\n                kwargs.pop('extra', None)\n                return func(*args, **kwargs)\n            raise e\n    return func_wrapper\n\n\ndef decorate_logger_methods(logger):\n    \"\"\"\n    Decorate all the logger methods so all the keys in the extra dictionary are\n    automatically prefixed with an underscore to avoid clashes with standard log\n    record attributes.\n    \"\"\"\n\n    # Note: We override findCaller with our custom implementation which takes into account this\n    # module.\n    # This way filename, module, funcName and lineno LogRecord attributes contain correct values\n    # instead of all pointing to decorate_log_method.\n    logger.findCaller = find_caller\n    for key in LOGGER_KEYS:\n        log_method = getattr(logger, key)\n        log_method = decorate_log_method(log_method)\n        setattr(logger, key, log_method)\n\n    return logger\n\n\ndef getLogger(name):\n    # make sure that prefix isn't appended multiple times to preserve logging name hierarchy\n    prefix = 'st2.'\n    if name.startswith(prefix):\n        logger = logging.getLogger(name)\n    else:\n        logger_name = '{}{}'.format(prefix, name)\n        logger = logging.getLogger(logger_name)\n\n    logger = decorate_logger_methods(logger=logger)\n    return logger\n\n\nclass LoggingStream(object):\n\n    def __init__(self, name, level=logging.ERROR):\n        self._logger = getLogger(name)\n        self._level = level\n\n    def write(self, message):\n        self._logger._log(self._level, message, None)\n\n    def flush(self):\n        pass\n\n\ndef _audit(logger, msg, *args, **kwargs):\n    if logger.isEnabledFor(logging.AUDIT):\n        logger._log(logging.AUDIT, msg, args, **kwargs)\n\n\nlogging.Logger.audit = _audit\n\n\ndef _add_exclusion_filters(handlers, excludes=None):\n    if excludes:\n        for h in handlers:\n            h.addFilter(LoggerNameExclusionFilter(excludes))\n\n\ndef _redirect_stderr():\n    # It is ok to redirect stderr as none of the st2 handlers write to stderr.\n    sys.stderr = LoggingStream('STDERR')\n\n\ndef setup(config_file, redirect_stderr=True, excludes=None, disable_existing_loggers=False,\n          st2_conf_path=None):\n    \"\"\"\n    Configure logging from file.\n\n    :param st2_conf_path: Optional path to st2.conf file. If provided and \"config_file\" path is\n                          relative to st2.conf path, the config_file path will get resolved to full\n                          absolute path relative to st2.conf.\n    :type st2_conf_path: ``str``\n    \"\"\"\n    if st2_conf_path and config_file[:2] == './' and not os.path.isfile(config_file):\n        # Logging config path is relative to st2.conf, resolve it to full absolute path\n        directory = os.path.dirname(st2_conf_path)\n        config_file_name = os.path.basename(config_file)\n        config_file = os.path.join(directory, config_file_name)\n\n    try:\n        logging.config.fileConfig(config_file,\n                                  defaults=None,\n                                  disable_existing_loggers=disable_existing_loggers)\n        handlers = logging.getLoggerClass().manager.root.handlers\n        _add_exclusion_filters(handlers=handlers, excludes=excludes)\n        if redirect_stderr:\n            _redirect_stderr()\n    except Exception as exc:\n        exc_cls = type(exc)\n        tb_msg = traceback.format_exc()\n\n        msg = str(exc)\n        msg += '\\n\\n' + tb_msg\n\n        # revert stderr redirection since there is no logger in place.\n        sys.stderr = sys.__stderr__\n\n        # No logger yet therefore write to stderr\n        sys.stderr.write('ERROR: %s' % (msg))\n\n        raise exc_cls(six.text_type(msg))\n\n\ndef ignore_lib2to3_log_messages():\n    \"\"\"\n    Work around to ignore \"Generating grammar tables from\" log messages which are logged under\n    INFO by default by libraries such as networkx which use 2to3.\n    \"\"\"\n    import lib2to3.pgen2.driver\n\n    class MockLoggingModule(object):\n        def getLogger(self, *args, **kwargs):\n            return logging.getLogger('lib2to3')\n\n    lib2to3.pgen2.driver.logging = MockLoggingModule()\n    logging.getLogger('lib2to3').setLevel(logging.ERROR)\n\n\ndef ignore_statsd_log_messages():\n    \"\"\"\n    By default statsd client logs all the operations under INFO and that causes a lot of noise.\n\n    This pull request silences all the statsd INFO log messages.\n    \"\"\"\n    import statsd.connection\n    import statsd.client\n\n    class MockLoggingModule(object):\n        def getLogger(self, *args, **kwargs):\n            return logging.getLogger('statsd')\n\n    statsd.connection.logging = MockLoggingModule()\n    statsd.client.logging = MockLoggingModule()\n    logging.getLogger('statsd').setLevel(logging.ERROR)\n", "path": "st2common/st2common/log.py"}]}