We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Logging non-string messages leads to AttributeError
Test code:

```
dut._log.info(dut.empty.value)
# dut.empty is a signal in the DUT, .value gets its BinaryValue object
```

This worked fine in older versions of cocotb, haven't attempted to find exact regression range yet. Now I get this trace:

```
Traceback (most recent call last):
  File "/usr/lib64/python3.7/logging/__init__.py", line 1034, in emit
    msg = self.format(record)
  File "/usr/lib64/python3.7/logging/__init__.py", line 880, in format
    return fmt.format(record)
  File "/home/philipp/.local/lib/python3.7/site-packages/cocotb/log.py", line 201, in format
    msg = '\n'.join([SimColourLogFormatter.loglevel2colour[record.levelno] % line for line in msg.split('\n')])
AttributeError: 'BinaryValue' object has no attribute 'split'
Call stack:
  File "/home/philipp/.local/lib/python3.7/site-packages/cocotb/scheduler.py", line 355, in react
    self._event_loop(trigger)
  File "/home/philipp/.local/lib/python3.7/site-packages/cocotb/scheduler.py", line 448, in _event_loop
    self.schedule(coro, trigger=trigger)
  File "/home/philipp/.local/lib/python3.7/site-packages/cocotb/scheduler.py", line 758, in schedule
    result = coroutine._advance(send_outcome)
  File "/home/philipp/.local/lib/python3.7/site-packages/cocotb/decorators.py", line 264, in _advance
    return super(RunningTest, self)._advance(outcome)
  File "/home/philipp/.local/lib/python3.7/site-packages/cocotb/decorators.py", line 146, in _advance
    return outcome.send(self._coro)
  File "/home/philipp/.local/lib/python3.7/site-packages/cocotb/outcomes.py", line 38, in send
    return gen.send(self.value)
  File "path/to/test_fifo.py", line 45, in test_fifo_manual
    dut._log.info(dut.empty.value)
```

I'll try to come up with a test and a fix soon-ish.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `cocotb/log.py`
Content:
```
1 # Copyright (c) 2013, 2018 Potential Ventures Ltd
2 # Copyright (c) 2013 SolarFlare Communications Inc
3 # All rights reserved.
4 #
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are met:
7 #     * Redistributions of source code must retain the above copyright
8 #       notice, this list of conditions and the following disclaimer.
9 #     * Redistributions in binary form must reproduce the above copyright
10 #       notice, this list of conditions and the following disclaimer in the
11 #       documentation and/or other materials provided with the distribution.
12 #     * Neither the name of Potential Ventures Ltd,
13 #       SolarFlare Communications Inc nor the
14 #       names of its contributors may be used to endorse or promote products
15 #       derived from this software without specific prior written permission.
16 #
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 # DISCLAIMED. IN NO EVENT SHALL POTENTIAL VENTURES LTD BE LIABLE FOR ANY
21 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 
28 """
29 Everything related to logging
30 """
31 
32 import os
33 import sys
34 import logging
35 import warnings
36 
37 from cocotb.utils import get_sim_time, want_color_output
38 
39 import cocotb.ANSI as ANSI
40 
41 if "COCOTB_REDUCED_LOG_FMT" in os.environ:
42     _suppress = True
43 else:
44     _suppress = False
45 
46 # Column alignment
47 _LEVEL_CHARS    = len("CRITICAL")  # noqa
48 _RECORD_CHARS   = 35  # noqa
49 _FILENAME_CHARS = 20  # noqa
50 _LINENO_CHARS   = 4  # noqa
51 _FUNCNAME_CHARS = 31  # noqa
52 
53 
54 def default_config():
55     """ Apply the default cocotb log formatting to the root logger.
56 
57     This hooks up the logger to write to stdout, using either
58     :class:`SimColourLogFormatter` or :class:`SimLogFormatter` depending
59     on whether colored output is requested.
60 
61     The logging level for cocotb logs is set based on the
62     :envvar:`COCOTB_LOG_LEVEL` environment variable, which defaults to ``INFO``.
63 
64     If desired, this logging configuration can be overwritten by calling
65     ``logging.basicConfig(..., force=True)`` (in Python 3.8 onwards), or by
66     manually resetting the root logger instance, for which examples can be
67     found online.
68     """
69     # construct an appropriate handler
70     hdlr = logging.StreamHandler(sys.stdout)
71     if want_color_output():
72         hdlr.setFormatter(SimColourLogFormatter())
73     else:
74         hdlr.setFormatter(SimLogFormatter())
75 
76     logging.setLoggerClass(SimBaseLog)  # For backwards compatibility
77     logging.basicConfig()
78     logging.getLogger().handlers = [hdlr]  # overwrite default handlers
79 
80     # apply level settings for cocotb
81     log = logging.getLogger('cocotb')
82     level = os.getenv("COCOTB_LOG_LEVEL", "INFO")
83     try:
84         _default_log = getattr(logging, level)
85     except AttributeError:
86         log.error("Unable to set logging level to %r" % level)
87         _default_log = logging.INFO
88     log.setLevel(_default_log)
89 
90     # Notify GPI of log level, which it uses as an optimization to avoid
91     # calling into Python.
92     if "COCOTB_SIM" in os.environ:
93         import simulator
94         simulator.log_level(_default_log)
95 
96 
97 class SimBaseLog(logging.getLoggerClass()):
98     """ This class only exists for backwards compatibility """
99 
100     @property
101     def logger(self):
102         warnings.warn(
103             "the .logger attribute should not be used now that `SimLog` "
104             "returns a native logger instance directly.",
105             DeprecationWarning, stacklevel=2)
106         return self
107 
108     @property
109     def colour(self):
110         warnings.warn(
111             "the .colour attribute may be removed in future, use the "
112             "equivalent `cocotb.utils.want_color_output()` instead",
113             DeprecationWarning, stacklevel=2)
114         return want_color_output()
115 
116 
117 # this used to be a class, hence the unusual capitalization
118 def SimLog(name, ident=None):
119     """ Like logging.getLogger, but append a numeric identifier to the name """
120     if ident is not None:
121         name = "%s.0x%x" % (name, ident)
122     return logging.getLogger(name)
123 
124 
125 class SimLogFormatter(logging.Formatter):
126     """Log formatter to provide consistent log message handling."""
127 
128     # Removes the arguments from the base class. Docstring needed to make
129     # sphinx happy.
130     def __init__(self):
131         """ Takes no arguments. """
132         super().__init__()
133 
134     # Justify and truncate
135     @staticmethod
136     def ljust(string, chars):
137         if len(string) > chars:
138             return ".." + string[(chars - 2) * -1:]
139         return string.ljust(chars)
140 
141     @staticmethod
142     def rjust(string, chars):
143         if len(string) > chars:
144             return ".." + string[(chars - 2) * -1:]
145         return string.rjust(chars)
146 
147     def _format(self, level, record, msg, coloured=False):
148         time_ns = get_sim_time('ns')
149         simtime = "%6.2fns" % (time_ns)
150         prefix = simtime.rjust(11) + ' ' + level + ' '
151         if not _suppress:
152             prefix += self.ljust(record.name, _RECORD_CHARS) + \
153                       self.rjust(os.path.split(record.filename)[1], _FILENAME_CHARS) + \
154                       ':' + self.ljust(str(record.lineno), _LINENO_CHARS) + \
155                       ' in ' + self.ljust(str(record.funcName), _FUNCNAME_CHARS) + ' '
156 
157         # these lines are copied from the builtin logger
158         if record.exc_info:
159             # Cache the traceback text to avoid converting it multiple times
160             # (it's constant anyway)
161             if not record.exc_text:
162                 record.exc_text = self.formatException(record.exc_info)
163         if record.exc_text:
164             if msg[-1:] != "\n":
165                 msg = msg + "\n"
166             msg = msg + record.exc_text
167 
168         prefix_len = len(prefix)
169         if coloured:
170             prefix_len -= (len(level) - _LEVEL_CHARS)
171         pad = "\n" + " " * (prefix_len)
172         return prefix + pad.join(msg.split('\n'))
173 
174     def format(self, record):
175         """Prettify the log output, annotate with simulation time"""
176         if record.args:
177             msg = record.msg % record.args
178         else:
179             msg = record.msg
180 
181         msg = str(msg)
182         level = record.levelname.ljust(_LEVEL_CHARS)
183 
184         return self._format(level, record, msg)
185 
186 
187 class SimColourLogFormatter(SimLogFormatter):
188     """Log formatter to provide consistent log message handling."""
189 
190     loglevel2colour = {
191         logging.DEBUG   :       "%s",
192         logging.INFO    :       ANSI.COLOR_INFO + "%s" + ANSI.COLOR_DEFAULT,
193         logging.WARNING :       ANSI.COLOR_WARNING + "%s" + ANSI.COLOR_DEFAULT,
194         logging.ERROR   :       ANSI.COLOR_ERROR + "%s" + ANSI.COLOR_DEFAULT,
195         logging.CRITICAL:       ANSI.COLOR_CRITICAL + "%s" + ANSI.COLOR_DEFAULT,
196     }
197 
198     def format(self, record):
199         """Prettify the log output, annotate with simulation time"""
200 
201         if record.args:
202             msg = record.msg % record.args
203         else:
204             msg = record.msg
205 
206         # Need to colour each line in case coloring is applied in the message
207         msg = '\n'.join([SimColourLogFormatter.loglevel2colour[record.levelno] % line for line in msg.split('\n')])
208         level = (SimColourLogFormatter.loglevel2colour[record.levelno] %
209                  record.levelname.ljust(_LEVEL_CHARS))
210 
211         return self._format(level, record, msg, coloured=True)
212 
213 
214 def _filter_from_c(logger_name, level):
215     return logging.getLogger(logger_name).isEnabledFor(level)
216 
217 
218 def _log_from_c(logger_name, level, filename, lineno, msg, function_name):
219     """
220     This is for use from the C world, and allows us to insert C stack
221     information.
222     """
223     logger = logging.getLogger(logger_name)
224     if logger.isEnabledFor(level):
225         record = logger.makeRecord(
226             logger.name,
227             level,
228             filename,
229             lineno,
230             msg,
231             None,
232             None,
233             function_name
234         )
235         logger.handle(record)
236
```
--- 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/cocotb/log.py b/cocotb/log.py
--- a/cocotb/log.py
+++ b/cocotb/log.py
@@ -167,12 +167,8 @@
 
     def format(self, record):
         """Prettify the log output, annotate with simulation time"""
-        if record.args:
-            msg = record.msg % record.args
-        else:
-            msg = record.msg
 
-        msg = str(msg)
+        msg = record.getMessage()
         level = record.levelname.ljust(_LEVEL_CHARS)
 
         return self._format(level, record, msg)
@@ -192,10 +188,7 @@
     def format(self, record):
         """Prettify the log output, annotate with simulation time"""
 
-        if record.args:
-            msg = record.msg % record.args
-        else:
-            msg = record.msg
+        msg = record.getMessage()
 
         # Need to colour each line in case coloring is applied in the message
         msg = '\n'.join([SimColourLogFormatter.loglevel2colour[record.levelno] % line for line in msg.split('\n')])

{"golden_diff": "diff --git a/cocotb/log.py b/cocotb/log.py\n--- a/cocotb/log.py\n+++ b/cocotb/log.py\n@@ -167,12 +167,8 @@\n \n     def format(self, record):\n         \"\"\"Prettify the log output, annotate with simulation time\"\"\"\n-        if record.args:\n-            msg = record.msg % record.args\n-        else:\n-            msg = record.msg\n \n-        msg = str(msg)\n+        msg = record.getMessage()\n         level = record.levelname.ljust(_LEVEL_CHARS)\n \n         return self._format(level, record, msg)\n@@ -192,10 +188,7 @@\n     def format(self, record):\n         \"\"\"Prettify the log output, annotate with simulation time\"\"\"\n \n-        if record.args:\n-            msg = record.msg % record.args\n-        else:\n-            msg = record.msg\n+        msg = record.getMessage()\n \n         # Need to colour each line in case coloring is applied in the message\n         msg = '\\n'.join([SimColourLogFormatter.loglevel2colour[record.levelno] % line for line in msg.split('\\n')])\n", "issue": "Logging non-string messages leads to AttributeError\nTest code:\r\n\r\n```\r\ndut._log.info(dut.empty.value)\r\n# dut.empty is a signal in the DUT, .value gets its BinaryValue object\r\n```\r\n\r\nThis worked fine in older versions of cocotb, haven't attempted to find exact regression range yet. Now I get this trace:\r\n\r\n```\r\nTraceback (most recent call last):\r\n  File \"/usr/lib64/python3.7/logging/__init__.py\", line 1034, in emit\r\n    msg = self.format(record)\r\n  File \"/usr/lib64/python3.7/logging/__init__.py\", line 880, in format\r\n    return fmt.format(record)\r\n  File \"/home/philipp/.local/lib/python3.7/site-packages/cocotb/log.py\", line 201, in format\r\n    msg = '\\n'.join([SimColourLogFormatter.loglevel2colour[record.levelno] % line for line in msg.split('\\n')])\r\nAttributeError: 'BinaryValue' object has no attribute 'split'\r\nCall stack:\r\n  File \"/home/philipp/.local/lib/python3.7/site-packages/cocotb/scheduler.py\", line 355, in react\r\n    self._event_loop(trigger)\r\n  File \"/home/philipp/.local/lib/python3.7/site-packages/cocotb/scheduler.py\", line 448, in _event_loop\r\n    self.schedule(coro, trigger=trigger)\r\n  File \"/home/philipp/.local/lib/python3.7/site-packages/cocotb/scheduler.py\", line 758, in schedule\r\n    result = coroutine._advance(send_outcome)\r\n  File \"/home/philipp/.local/lib/python3.7/site-packages/cocotb/decorators.py\", line 264, in _advance\r\n    return super(RunningTest, self)._advance(outcome)\r\n  File \"/home/philipp/.local/lib/python3.7/site-packages/cocotb/decorators.py\", line 146, in _advance\r\n    return outcome.send(self._coro)\r\n  File \"/home/philipp/.local/lib/python3.7/site-packages/cocotb/outcomes.py\", line 38, in send\r\n    return gen.send(self.value)\r\n  File \"path/to/test_fifo.py\", line 45, in test_fifo_manual\r\n    dut._log.info(dut.empty.value)\r\n```\r\n\r\nI'll try to come up with a test and a fix soon-ish.\n", "before_files": [{"content": "# Copyright (c) 2013, 2018 Potential Ventures Ltd\n# Copyright (c) 2013 SolarFlare Communications Inc\n# All rights reserved.\n#\n# Redistribution and use in source and binary forms, with or without\n# modification, are permitted provided that the following conditions are met:\n#     * Redistributions of source code must retain the above copyright\n#       notice, this list of conditions and the following disclaimer.\n#     * Redistributions in binary form must reproduce the above copyright\n#       notice, this list of conditions and the following disclaimer in the\n#       documentation and/or other materials provided with the distribution.\n#     * Neither the name of Potential Ventures Ltd,\n#       SolarFlare Communications Inc nor the\n#       names of its contributors may be used to endorse or promote products\n#       derived from this software without specific prior written permission.\n#\n# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n# DISCLAIMED. IN NO EVENT SHALL POTENTIAL VENTURES LTD BE LIABLE FOR ANY\n# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\"\"\"\nEverything related to logging\n\"\"\"\n\nimport os\nimport sys\nimport logging\nimport warnings\n\nfrom cocotb.utils import get_sim_time, want_color_output\n\nimport cocotb.ANSI as ANSI\n\nif \"COCOTB_REDUCED_LOG_FMT\" in os.environ:\n    _suppress = True\nelse:\n    _suppress = False\n\n# Column alignment\n_LEVEL_CHARS    = len(\"CRITICAL\")  # noqa\n_RECORD_CHARS   = 35  # noqa\n_FILENAME_CHARS = 20  # noqa\n_LINENO_CHARS   = 4  # noqa\n_FUNCNAME_CHARS = 31  # noqa\n\n\ndef default_config():\n    \"\"\" Apply the default cocotb log formatting to the root logger.\n\n    This hooks up the logger to write to stdout, using either\n    :class:`SimColourLogFormatter` or :class:`SimLogFormatter` depending\n    on whether colored output is requested.\n\n    The logging level for cocotb logs is set based on the\n    :envvar:`COCOTB_LOG_LEVEL` environment variable, which defaults to ``INFO``.\n\n    If desired, this logging configuration can be overwritten by calling\n    ``logging.basicConfig(..., force=True)`` (in Python 3.8 onwards), or by\n    manually resetting the root logger instance, for which examples can be\n    found online.\n    \"\"\"\n    # construct an appropriate handler\n    hdlr = logging.StreamHandler(sys.stdout)\n    if want_color_output():\n        hdlr.setFormatter(SimColourLogFormatter())\n    else:\n        hdlr.setFormatter(SimLogFormatter())\n\n    logging.setLoggerClass(SimBaseLog)  # For backwards compatibility\n    logging.basicConfig()\n    logging.getLogger().handlers = [hdlr]  # overwrite default handlers\n\n    # apply level settings for cocotb\n    log = logging.getLogger('cocotb')\n    level = os.getenv(\"COCOTB_LOG_LEVEL\", \"INFO\")\n    try:\n        _default_log = getattr(logging, level)\n    except AttributeError:\n        log.error(\"Unable to set logging level to %r\" % level)\n        _default_log = logging.INFO\n    log.setLevel(_default_log)\n\n    # Notify GPI of log level, which it uses as an optimization to avoid\n    # calling into Python.\n    if \"COCOTB_SIM\" in os.environ:\n        import simulator\n        simulator.log_level(_default_log)\n\n\nclass SimBaseLog(logging.getLoggerClass()):\n    \"\"\" This class only exists for backwards compatibility \"\"\"\n\n    @property\n    def logger(self):\n        warnings.warn(\n            \"the .logger attribute should not be used now that `SimLog` \"\n            \"returns a native logger instance directly.\",\n            DeprecationWarning, stacklevel=2)\n        return self\n\n    @property\n    def colour(self):\n        warnings.warn(\n            \"the .colour attribute may be removed in future, use the \"\n            \"equivalent `cocotb.utils.want_color_output()` instead\",\n            DeprecationWarning, stacklevel=2)\n        return want_color_output()\n\n\n# this used to be a class, hence the unusual capitalization\ndef SimLog(name, ident=None):\n    \"\"\" Like logging.getLogger, but append a numeric identifier to the name \"\"\"\n    if ident is not None:\n        name = \"%s.0x%x\" % (name, ident)\n    return logging.getLogger(name)\n\n\nclass SimLogFormatter(logging.Formatter):\n    \"\"\"Log formatter to provide consistent log message handling.\"\"\"\n\n    # Removes the arguments from the base class. Docstring needed to make\n    # sphinx happy.\n    def __init__(self):\n        \"\"\" Takes no arguments. \"\"\"\n        super().__init__()\n\n    # Justify and truncate\n    @staticmethod\n    def ljust(string, chars):\n        if len(string) > chars:\n            return \"..\" + string[(chars - 2) * -1:]\n        return string.ljust(chars)\n\n    @staticmethod\n    def rjust(string, chars):\n        if len(string) > chars:\n            return \"..\" + string[(chars - 2) * -1:]\n        return string.rjust(chars)\n\n    def _format(self, level, record, msg, coloured=False):\n        time_ns = get_sim_time('ns')\n        simtime = \"%6.2fns\" % (time_ns)\n        prefix = simtime.rjust(11) + ' ' + level + ' '\n        if not _suppress:\n            prefix += self.ljust(record.name, _RECORD_CHARS) + \\\n                      self.rjust(os.path.split(record.filename)[1], _FILENAME_CHARS) + \\\n                      ':' + self.ljust(str(record.lineno), _LINENO_CHARS) + \\\n                      ' in ' + self.ljust(str(record.funcName), _FUNCNAME_CHARS) + ' '\n\n        # these lines are copied from the builtin logger\n        if record.exc_info:\n            # Cache the traceback text to avoid converting it multiple times\n            # (it's constant anyway)\n            if not record.exc_text:\n                record.exc_text = self.formatException(record.exc_info)\n        if record.exc_text:\n            if msg[-1:] != \"\\n\":\n                msg = msg + \"\\n\"\n            msg = msg + record.exc_text\n\n        prefix_len = len(prefix)\n        if coloured:\n            prefix_len -= (len(level) - _LEVEL_CHARS)\n        pad = \"\\n\" + \" \" * (prefix_len)\n        return prefix + pad.join(msg.split('\\n'))\n\n    def format(self, record):\n        \"\"\"Prettify the log output, annotate with simulation time\"\"\"\n        if record.args:\n            msg = record.msg % record.args\n        else:\n            msg = record.msg\n\n        msg = str(msg)\n        level = record.levelname.ljust(_LEVEL_CHARS)\n\n        return self._format(level, record, msg)\n\n\nclass SimColourLogFormatter(SimLogFormatter):\n    \"\"\"Log formatter to provide consistent log message handling.\"\"\"\n\n    loglevel2colour = {\n        logging.DEBUG   :       \"%s\",\n        logging.INFO    :       ANSI.COLOR_INFO + \"%s\" + ANSI.COLOR_DEFAULT,\n        logging.WARNING :       ANSI.COLOR_WARNING + \"%s\" + ANSI.COLOR_DEFAULT,\n        logging.ERROR   :       ANSI.COLOR_ERROR + \"%s\" + ANSI.COLOR_DEFAULT,\n        logging.CRITICAL:       ANSI.COLOR_CRITICAL + \"%s\" + ANSI.COLOR_DEFAULT,\n    }\n\n    def format(self, record):\n        \"\"\"Prettify the log output, annotate with simulation time\"\"\"\n\n        if record.args:\n            msg = record.msg % record.args\n        else:\n            msg = record.msg\n\n        # Need to colour each line in case coloring is applied in the message\n        msg = '\\n'.join([SimColourLogFormatter.loglevel2colour[record.levelno] % line for line in msg.split('\\n')])\n        level = (SimColourLogFormatter.loglevel2colour[record.levelno] %\n                 record.levelname.ljust(_LEVEL_CHARS))\n\n        return self._format(level, record, msg, coloured=True)\n\n\ndef _filter_from_c(logger_name, level):\n    return logging.getLogger(logger_name).isEnabledFor(level)\n\n\ndef _log_from_c(logger_name, level, filename, lineno, msg, function_name):\n    \"\"\"\n    This is for use from the C world, and allows us to insert C stack\n    information.\n    \"\"\"\n    logger = logging.getLogger(logger_name)\n    if logger.isEnabledFor(level):\n        record = logger.makeRecord(\n            logger.name,\n            level,\n            filename,\n            lineno,\n            msg,\n            None,\n            None,\n            function_name\n        )\n        logger.handle(record)\n", "path": "cocotb/log.py"}], "after_files": [{"content": "# Copyright (c) 2013, 2018 Potential Ventures Ltd\n# Copyright (c) 2013 SolarFlare Communications Inc\n# All rights reserved.\n#\n# Redistribution and use in source and binary forms, with or without\n# modification, are permitted provided that the following conditions are met:\n#     * Redistributions of source code must retain the above copyright\n#       notice, this list of conditions and the following disclaimer.\n#     * Redistributions in binary form must reproduce the above copyright\n#       notice, this list of conditions and the following disclaimer in the\n#       documentation and/or other materials provided with the distribution.\n#     * Neither the name of Potential Ventures Ltd,\n#       SolarFlare Communications Inc nor the\n#       names of its contributors may be used to endorse or promote products\n#       derived from this software without specific prior written permission.\n#\n# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n# DISCLAIMED. IN NO EVENT SHALL POTENTIAL VENTURES LTD BE LIABLE FOR ANY\n# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\"\"\"\nEverything related to logging\n\"\"\"\n\nimport os\nimport sys\nimport logging\nimport warnings\n\nfrom cocotb.utils import get_sim_time, want_color_output\n\nimport cocotb.ANSI as ANSI\n\nif \"COCOTB_REDUCED_LOG_FMT\" in os.environ:\n    _suppress = True\nelse:\n    _suppress = False\n\n# Column alignment\n_LEVEL_CHARS    = len(\"CRITICAL\")  # noqa\n_RECORD_CHARS   = 35  # noqa\n_FILENAME_CHARS = 20  # noqa\n_LINENO_CHARS   = 4  # noqa\n_FUNCNAME_CHARS = 31  # noqa\n\n\ndef default_config():\n    \"\"\" Apply the default cocotb log formatting to the root logger.\n\n    This hooks up the logger to write to stdout, using either\n    :class:`SimColourLogFormatter` or :class:`SimLogFormatter` depending\n    on whether colored output is requested.\n\n    The logging level for cocotb logs is set based on the\n    :envvar:`COCOTB_LOG_LEVEL` environment variable, which defaults to ``INFO``.\n\n    If desired, this logging configuration can be overwritten by calling\n    ``logging.basicConfig(..., force=True)`` (in Python 3.8 onwards), or by\n    manually resetting the root logger instance, for which examples can be\n    found online.\n    \"\"\"\n    # construct an appropriate handler\n    hdlr = logging.StreamHandler(sys.stdout)\n    if want_color_output():\n        hdlr.setFormatter(SimColourLogFormatter())\n    else:\n        hdlr.setFormatter(SimLogFormatter())\n\n    logging.setLoggerClass(SimBaseLog)  # For backwards compatibility\n    logging.basicConfig()\n    logging.getLogger().handlers = [hdlr]  # overwrite default handlers\n\n    # apply level settings for cocotb\n    log = logging.getLogger('cocotb')\n    level = os.getenv(\"COCOTB_LOG_LEVEL\", \"INFO\")\n    try:\n        _default_log = getattr(logging, level)\n    except AttributeError:\n        log.error(\"Unable to set logging level to %r\" % level)\n        _default_log = logging.INFO\n    log.setLevel(_default_log)\n\n    # Notify GPI of log level, which it uses as an optimization to avoid\n    # calling into Python.\n    if \"COCOTB_SIM\" in os.environ:\n        import simulator\n        simulator.log_level(_default_log)\n\n\nclass SimBaseLog(logging.getLoggerClass()):\n    \"\"\" This class only exists for backwards compatibility \"\"\"\n\n    @property\n    def logger(self):\n        warnings.warn(\n            \"the .logger attribute should not be used now that `SimLog` \"\n            \"returns a native logger instance directly.\",\n            DeprecationWarning, stacklevel=2)\n        return self\n\n    @property\n    def colour(self):\n        warnings.warn(\n            \"the .colour attribute may be removed in future, use the \"\n            \"equivalent `cocotb.utils.want_color_output()` instead\",\n            DeprecationWarning, stacklevel=2)\n        return want_color_output()\n\n\n# this used to be a class, hence the unusual capitalization\ndef SimLog(name, ident=None):\n    \"\"\" Like logging.getLogger, but append a numeric identifier to the name \"\"\"\n    if ident is not None:\n        name = \"%s.0x%x\" % (name, ident)\n    return logging.getLogger(name)\n\n\nclass SimLogFormatter(logging.Formatter):\n    \"\"\"Log formatter to provide consistent log message handling.\"\"\"\n\n    # Justify and truncate\n    @staticmethod\n    def ljust(string, chars):\n        if len(string) > chars:\n            return \"..\" + string[(chars - 2) * -1:]\n        return string.ljust(chars)\n\n    @staticmethod\n    def rjust(string, chars):\n        if len(string) > chars:\n            return \"..\" + string[(chars - 2) * -1:]\n        return string.rjust(chars)\n\n    def _format(self, level, record, msg, coloured=False):\n        time_ns = get_sim_time('ns')\n        simtime = \"%6.2fns\" % (time_ns)\n        prefix = simtime.rjust(11) + ' ' + level + ' '\n        if not _suppress:\n            prefix += self.ljust(record.name, _RECORD_CHARS) + \\\n                      self.rjust(os.path.split(record.filename)[1], _FILENAME_CHARS) + \\\n                      ':' + self.ljust(str(record.lineno), _LINENO_CHARS) + \\\n                      ' in ' + self.ljust(str(record.funcName), _FUNCNAME_CHARS) + ' '\n\n        # these lines are copied from the builtin logger\n        if record.exc_info:\n            # Cache the traceback text to avoid converting it multiple times\n            # (it's constant anyway)\n            if not record.exc_text:\n                record.exc_text = self.formatException(record.exc_info)\n        if record.exc_text:\n            if msg[-1:] != \"\\n\":\n                msg = msg + \"\\n\"\n            msg = msg + record.exc_text\n\n        prefix_len = len(prefix)\n        if coloured:\n            prefix_len -= (len(level) - _LEVEL_CHARS)\n        pad = \"\\n\" + \" \" * (prefix_len)\n        return prefix + pad.join(msg.split('\\n'))\n\n    def format(self, record):\n        \"\"\"Prettify the log output, annotate with simulation time\"\"\"\n\n        msg = record.getMessage()\n        level = record.levelname.ljust(_LEVEL_CHARS)\n\n        return self._format(level, record, msg)\n\n\nclass SimColourLogFormatter(SimLogFormatter):\n    \"\"\"Log formatter to provide consistent log message handling.\"\"\"\n\n    loglevel2colour = {\n        logging.DEBUG   :       \"%s\",\n        logging.INFO    :       ANSI.COLOR_INFO + \"%s\" + ANSI.COLOR_DEFAULT,\n        logging.WARNING :       ANSI.COLOR_WARNING + \"%s\" + ANSI.COLOR_DEFAULT,\n        logging.ERROR   :       ANSI.COLOR_ERROR + \"%s\" + ANSI.COLOR_DEFAULT,\n        logging.CRITICAL:       ANSI.COLOR_CRITICAL + \"%s\" + ANSI.COLOR_DEFAULT,\n    }\n\n    def format(self, record):\n        \"\"\"Prettify the log output, annotate with simulation time\"\"\"\n\n        msg = record.getMessage()\n\n        # Need to colour each line in case coloring is applied in the message\n        msg = '\\n'.join([SimColourLogFormatter.loglevel2colour[record.levelno] % line for line in msg.split('\\n')])\n        level = (SimColourLogFormatter.loglevel2colour[record.levelno] %\n                 record.levelname.ljust(_LEVEL_CHARS))\n\n        return self._format(level, record, msg, coloured=True)\n\n\ndef _filter_from_c(logger_name, level):\n    return logging.getLogger(logger_name).isEnabledFor(level)\n\n\ndef _log_from_c(logger_name, level, filename, lineno, msg, function_name):\n    \"\"\"\n    This is for use from the C world, and allows us to insert C stack\n    information.\n    \"\"\"\n    logger = logging.getLogger(logger_name)\n    if logger.isEnabledFor(level):\n        record = logger.makeRecord(\n            logger.name,\n            level,\n            filename,\n            lineno,\n            msg,\n            None,\n            None,\n            function_name\n        )\n        logger.handle(record)\n", "path": "cocotb/log.py"}]}