We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
cocotb colorizer does not support custom logging levels
The attached file demonstrates that cocotb does not support custom logging levels. This may be the intention as there is an assertion in the code regarding acceptable logging levels, it is limiting. 

I don't think cocotb should throw an exception on an unexpected level. Instead I think it should have a default behavior. It appears the log colorization system doesn't know what to do with `5`. It likely needs a `try`/`except`.

```
Traceback (most recent call last):
  File "/Users/raysalemi/opt/anaconda3/lib/python3.8/logging/__init__.py", line 1081, in emit
    msg = self.format(record)
  File "/Users/raysalemi/opt/anaconda3/lib/python3.8/logging/__init__.py", line 925, in format
    return fmt.format(record)
  File "/Users/raysalemi/opt/anaconda3/lib/python3.8/site-packages/cocotb/log.py", line 251, in format
    msg = '\n'.join([SimColourLogFormatter.loglevel2colour[record.levelno] % line for line in msg.split('\n')])
  File "/Users/raysalemi/opt/anaconda3/lib/python3.8/site-packages/cocotb/log.py", line 251, in <listcomp>
    msg = '\n'.join([SimColourLogFormatter.loglevel2colour[record.levelno] % line for line in msg.split('\n')])
KeyError: 5
Call stack:
```
[bug_example.zip](https://github.com/cocotb/cocotb/files/5842227/bug_example.zip)





--- 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 (
38     get_sim_time, get_time_from_sim_steps, want_color_output
39 )
40 
41 import cocotb.ANSI as ANSI
42 
43 if "COCOTB_REDUCED_LOG_FMT" in os.environ:
44     _suppress = True
45 else:
46     _suppress = False
47 
48 # Column alignment
49 _LEVEL_CHARS    = len("CRITICAL")  # noqa
50 _RECORD_CHARS   = 35  # noqa
51 _FILENAME_CHARS = 20  # noqa
52 _LINENO_CHARS   = 4  # noqa
53 _FUNCNAME_CHARS = 31  # noqa
54 
55 # Default log level if not overwritten by the user.
56 _COCOTB_LOG_LEVEL_DEFAULT = "INFO"
57 
58 
59 def default_config():
60     """ Apply the default cocotb log formatting to the root logger.
61 
62     This hooks up the logger to write to stdout, using either
63     :class:`SimColourLogFormatter` or :class:`SimLogFormatter` depending
64     on whether colored output is requested. It also adds a
65     :class:`SimTimeContextFilter` filter so that
66     :attr:`~logging.LogRecord.created_sim_time` is available to the formatter.
67 
68     The logging level for cocotb logs is set based on the
69     :envvar:`COCOTB_LOG_LEVEL` environment variable, which defaults to ``INFO``.
70 
71     If desired, this logging configuration can be overwritten by calling
72     ``logging.basicConfig(..., force=True)`` (in Python 3.8 onwards), or by
73     manually resetting the root logger instance.
74     An example of this can be found in the section on :ref:`rotating-logger`.
75 
76     .. versionadded:: 1.4
77     """
78     # construct an appropriate handler
79     hdlr = logging.StreamHandler(sys.stdout)
80     hdlr.addFilter(SimTimeContextFilter())
81     if want_color_output():
82         hdlr.setFormatter(SimColourLogFormatter())
83     else:
84         hdlr.setFormatter(SimLogFormatter())
85 
86     logging.setLoggerClass(SimBaseLog)  # For backwards compatibility
87     logging.basicConfig()
88     logging.getLogger().handlers = [hdlr]  # overwrite default handlers
89 
90     # apply level settings for cocotb
91     log = logging.getLogger('cocotb')
92 
93     try:
94         # All log levels are upper case, convert the user input for convenience.
95         level = os.environ["COCOTB_LOG_LEVEL"].upper()
96     except KeyError:
97         level = _COCOTB_LOG_LEVEL_DEFAULT
98 
99     try:
100         log.setLevel(level)
101     except ValueError:
102         valid_levels = ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG')
103         raise ValueError("Invalid log level %r passed through the "
104                          "COCOTB_LOG_LEVEL environment variable. Valid log "
105                          "levels: %s" % (level, ', '.join(valid_levels)))
106 
107     # Notify GPI of log level, which it uses as an optimization to avoid
108     # calling into Python.
109     from cocotb import simulator
110     simulator.log_level(log.getEffectiveLevel())
111 
112 
113 class SimBaseLog(logging.getLoggerClass()):
114     """ This class only exists for backwards compatibility """
115 
116     @property
117     def logger(self):
118         warnings.warn(
119             "the .logger attribute should not be used now that `SimLog` "
120             "returns a native logger instance directly.",
121             DeprecationWarning, stacklevel=2)
122         return self
123 
124     @property
125     def colour(self):
126         warnings.warn(
127             "the .colour attribute may be removed in future, use the "
128             "equivalent `cocotb.utils.want_color_output()` instead",
129             DeprecationWarning, stacklevel=2)
130         return want_color_output()
131 
132 
133 # this used to be a class, hence the unusual capitalization
134 def SimLog(name, ident=None):
135     """ Like logging.getLogger, but append a numeric identifier to the name """
136     if ident is not None:
137         name = "%s.0x%x" % (name, ident)
138     return logging.getLogger(name)
139 
140 
141 class SimTimeContextFilter(logging.Filter):
142     """
143     A filter to inject simulator times into the log records.
144 
145     This uses the approach described in the :ref:`Python logging cookbook <python:filters-contextual>`.
146 
147     This adds the :attr:`~logging.LogRecord.created_sim_time` attribute.
148 
149     .. versionadded:: 1.4
150     """
151 
152     # needed to make our docs render well
153     def __init__(self):
154         """"""
155         super().__init__()
156 
157     def filter(self, record):
158         try:
159             record.created_sim_time = get_sim_time()
160         except RecursionError:
161             # get_sim_time may try to log - if that happens, we can't
162             # attach a simulator time to this message.
163             record.created_sim_time = None
164         return True
165 
166 
167 class SimLogFormatter(logging.Formatter):
168     """Log formatter to provide consistent log message handling.
169 
170     This will only add simulator timestamps if the handler object this
171     formatter is attached to has a :class:`SimTimeContextFilter` filter
172     attached, which cocotb ensures by default.
173     """
174 
175     # Removes the arguments from the base class. Docstring needed to make
176     # sphinx happy.
177     def __init__(self):
178         """ Takes no arguments. """
179         super().__init__()
180 
181     # Justify and truncate
182     @staticmethod
183     def ljust(string, chars):
184         if len(string) > chars:
185             return ".." + string[(chars - 2) * -1:]
186         return string.ljust(chars)
187 
188     @staticmethod
189     def rjust(string, chars):
190         if len(string) > chars:
191             return ".." + string[(chars - 2) * -1:]
192         return string.rjust(chars)
193 
194     def _format(self, level, record, msg, coloured=False):
195         sim_time = getattr(record, 'created_sim_time', None)
196         if sim_time is None:
197             sim_time_str = "  -.--ns"
198         else:
199             time_ns = get_time_from_sim_steps(sim_time, 'ns')
200             sim_time_str = "{:6.2f}ns".format(time_ns)
201         prefix = sim_time_str.rjust(11) + ' ' + level + ' '
202         if not _suppress:
203             prefix += self.ljust(record.name, _RECORD_CHARS) + \
204                 self.rjust(os.path.split(record.filename)[1], _FILENAME_CHARS) + \
205                 ':' + self.ljust(str(record.lineno), _LINENO_CHARS) + \
206                 ' in ' + self.ljust(str(record.funcName), _FUNCNAME_CHARS) + ' '
207 
208         # these lines are copied from the builtin logger
209         if record.exc_info:
210             # Cache the traceback text to avoid converting it multiple times
211             # (it's constant anyway)
212             if not record.exc_text:
213                 record.exc_text = self.formatException(record.exc_info)
214         if record.exc_text:
215             if msg[-1:] != "\n":
216                 msg = msg + "\n"
217             msg = msg + record.exc_text
218 
219         prefix_len = len(prefix)
220         if coloured:
221             prefix_len -= (len(level) - _LEVEL_CHARS)
222         pad = "\n" + " " * (prefix_len)
223         return prefix + pad.join(msg.split('\n'))
224 
225     def format(self, record):
226         """Prettify the log output, annotate with simulation time"""
227 
228         msg = record.getMessage()
229         level = record.levelname.ljust(_LEVEL_CHARS)
230 
231         return self._format(level, record, msg)
232 
233 
234 class SimColourLogFormatter(SimLogFormatter):
235     """Log formatter to provide consistent log message handling."""
236 
237     loglevel2colour = {
238         logging.DEBUG   :       "%s",
239         logging.INFO    :       ANSI.COLOR_INFO + "%s" + ANSI.COLOR_DEFAULT,
240         logging.WARNING :       ANSI.COLOR_WARNING + "%s" + ANSI.COLOR_DEFAULT,
241         logging.ERROR   :       ANSI.COLOR_ERROR + "%s" + ANSI.COLOR_DEFAULT,
242         logging.CRITICAL:       ANSI.COLOR_CRITICAL + "%s" + ANSI.COLOR_DEFAULT,
243     }
244 
245     def format(self, record):
246         """Prettify the log output, annotate with simulation time"""
247 
248         msg = record.getMessage()
249 
250         # Need to colour each line in case coloring is applied in the message
251         msg = '\n'.join([SimColourLogFormatter.loglevel2colour[record.levelno] % line for line in msg.split('\n')])
252         level = (SimColourLogFormatter.loglevel2colour[record.levelno] %
253                  record.levelname.ljust(_LEVEL_CHARS))
254 
255         return self._format(level, record, msg, coloured=True)
256 
257 
258 def _filter_from_c(logger_name, level):
259     return logging.getLogger(logger_name).isEnabledFor(level)
260 
261 
262 def _log_from_c(logger_name, level, filename, lineno, msg, function_name):
263     """
264     This is for use from the C world, and allows us to insert C stack
265     information.
266     """
267     logger = logging.getLogger(logger_name)
268     if logger.isEnabledFor(level):
269         record = logger.makeRecord(
270             logger.name,
271             level,
272             filename,
273             lineno,
274             msg,
275             None,
276             None,
277             function_name
278         )
279         logger.handle(record)
280
```
--- 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
@@ -248,8 +248,8 @@
         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')])
-        level = (SimColourLogFormatter.loglevel2colour[record.levelno] %
+        msg = '\n'.join([SimColourLogFormatter.loglevel2colour.get(record.levelno,"%s") % line for line in msg.split('\n')])
+        level = (SimColourLogFormatter.loglevel2colour.get(record.levelno, "%s") %
                  record.levelname.ljust(_LEVEL_CHARS))
 
         return self._format(level, record, msg, coloured=True)

{"golden_diff": "diff --git a/cocotb/log.py b/cocotb/log.py\n--- a/cocotb/log.py\n+++ b/cocotb/log.py\n@@ -248,8 +248,8 @@\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+        msg = '\\n'.join([SimColourLogFormatter.loglevel2colour.get(record.levelno,\"%s\") % line for line in msg.split('\\n')])\n+        level = (SimColourLogFormatter.loglevel2colour.get(record.levelno, \"%s\") %\n                  record.levelname.ljust(_LEVEL_CHARS))\n \n         return self._format(level, record, msg, coloured=True)\n", "issue": "cocotb colorizer does not support custom logging levels\nThe attached file demonstrates that cocotb does not support custom logging levels. This may be the intention as there is an assertion in the code regarding acceptable logging levels, it is limiting. \r\n\r\nI don't think cocotb should throw an exception on an unexpected level. Instead I think it should have a default behavior. It appears the log colorization system doesn't know what to do with `5`. It likely needs a `try`/`except`.\r\n\r\n```\r\nTraceback (most recent call last):\r\n  File \"/Users/raysalemi/opt/anaconda3/lib/python3.8/logging/__init__.py\", line 1081, in emit\r\n    msg = self.format(record)\r\n  File \"/Users/raysalemi/opt/anaconda3/lib/python3.8/logging/__init__.py\", line 925, in format\r\n    return fmt.format(record)\r\n  File \"/Users/raysalemi/opt/anaconda3/lib/python3.8/site-packages/cocotb/log.py\", line 251, in format\r\n    msg = '\\n'.join([SimColourLogFormatter.loglevel2colour[record.levelno] % line for line in msg.split('\\n')])\r\n  File \"/Users/raysalemi/opt/anaconda3/lib/python3.8/site-packages/cocotb/log.py\", line 251, in <listcomp>\r\n    msg = '\\n'.join([SimColourLogFormatter.loglevel2colour[record.levelno] % line for line in msg.split('\\n')])\r\nKeyError: 5\r\nCall stack:\r\n```\r\n[bug_example.zip](https://github.com/cocotb/cocotb/files/5842227/bug_example.zip)\r\n\r\n\r\n\r\n\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 (\n    get_sim_time, get_time_from_sim_steps, want_color_output\n)\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# Default log level if not overwritten by the user.\n_COCOTB_LOG_LEVEL_DEFAULT = \"INFO\"\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. It also adds a\n    :class:`SimTimeContextFilter` filter so that\n    :attr:`~logging.LogRecord.created_sim_time` is available to the formatter.\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.\n    An example of this can be found in the section on :ref:`rotating-logger`.\n\n    .. versionadded:: 1.4\n    \"\"\"\n    # construct an appropriate handler\n    hdlr = logging.StreamHandler(sys.stdout)\n    hdlr.addFilter(SimTimeContextFilter())\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\n    try:\n        # All log levels are upper case, convert the user input for convenience.\n        level = os.environ[\"COCOTB_LOG_LEVEL\"].upper()\n    except KeyError:\n        level = _COCOTB_LOG_LEVEL_DEFAULT\n\n    try:\n        log.setLevel(level)\n    except ValueError:\n        valid_levels = ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG')\n        raise ValueError(\"Invalid log level %r passed through the \"\n                         \"COCOTB_LOG_LEVEL environment variable. Valid log \"\n                         \"levels: %s\" % (level, ', '.join(valid_levels)))\n\n    # Notify GPI of log level, which it uses as an optimization to avoid\n    # calling into Python.\n    from cocotb import simulator\n    simulator.log_level(log.getEffectiveLevel())\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 SimTimeContextFilter(logging.Filter):\n    \"\"\"\n    A filter to inject simulator times into the log records.\n\n    This uses the approach described in the :ref:`Python logging cookbook <python:filters-contextual>`.\n\n    This adds the :attr:`~logging.LogRecord.created_sim_time` attribute.\n\n    .. versionadded:: 1.4\n    \"\"\"\n\n    # needed to make our docs render well\n    def __init__(self):\n        \"\"\"\"\"\"\n        super().__init__()\n\n    def filter(self, record):\n        try:\n            record.created_sim_time = get_sim_time()\n        except RecursionError:\n            # get_sim_time may try to log - if that happens, we can't\n            # attach a simulator time to this message.\n            record.created_sim_time = None\n        return True\n\n\nclass SimLogFormatter(logging.Formatter):\n    \"\"\"Log formatter to provide consistent log message handling.\n\n    This will only add simulator timestamps if the handler object this\n    formatter is attached to has a :class:`SimTimeContextFilter` filter\n    attached, which cocotb ensures by default.\n    \"\"\"\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        sim_time = getattr(record, 'created_sim_time', None)\n        if sim_time is None:\n            sim_time_str = \"  -.--ns\"\n        else:\n            time_ns = get_time_from_sim_steps(sim_time, 'ns')\n            sim_time_str = \"{:6.2f}ns\".format(time_ns)\n        prefix = sim_time_str.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"}], "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 (\n    get_sim_time, get_time_from_sim_steps, want_color_output\n)\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# Default log level if not overwritten by the user.\n_COCOTB_LOG_LEVEL_DEFAULT = \"INFO\"\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. It also adds a\n    :class:`SimTimeContextFilter` filter so that\n    :attr:`~logging.LogRecord.created_sim_time` is available to the formatter.\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.\n    An example of this can be found in the section on :ref:`rotating-logger`.\n\n    .. versionadded:: 1.4\n    \"\"\"\n    # construct an appropriate handler\n    hdlr = logging.StreamHandler(sys.stdout)\n    hdlr.addFilter(SimTimeContextFilter())\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\n    try:\n        # All log levels are upper case, convert the user input for convenience.\n        level = os.environ[\"COCOTB_LOG_LEVEL\"].upper()\n    except KeyError:\n        level = _COCOTB_LOG_LEVEL_DEFAULT\n\n    try:\n        log.setLevel(level)\n    except ValueError:\n        valid_levels = ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG')\n        raise ValueError(\"Invalid log level %r passed through the \"\n                         \"COCOTB_LOG_LEVEL environment variable. Valid log \"\n                         \"levels: %s\" % (level, ', '.join(valid_levels)))\n\n    # Notify GPI of log level, which it uses as an optimization to avoid\n    # calling into Python.\n    from cocotb import simulator\n    simulator.log_level(log.getEffectiveLevel())\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 SimTimeContextFilter(logging.Filter):\n    \"\"\"\n    A filter to inject simulator times into the log records.\n\n    This uses the approach described in the :ref:`Python logging cookbook <python:filters-contextual>`.\n\n    This adds the :attr:`~logging.LogRecord.created_sim_time` attribute.\n\n    .. versionadded:: 1.4\n    \"\"\"\n\n    # needed to make our docs render well\n    def __init__(self):\n        \"\"\"\"\"\"\n        super().__init__()\n\n    def filter(self, record):\n        try:\n            record.created_sim_time = get_sim_time()\n        except RecursionError:\n            # get_sim_time may try to log - if that happens, we can't\n            # attach a simulator time to this message.\n            record.created_sim_time = None\n        return True\n\n\nclass SimLogFormatter(logging.Formatter):\n    \"\"\"Log formatter to provide consistent log message handling.\n\n    This will only add simulator timestamps if the handler object this\n    formatter is attached to has a :class:`SimTimeContextFilter` filter\n    attached, which cocotb ensures by default.\n    \"\"\"\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        sim_time = getattr(record, 'created_sim_time', None)\n        if sim_time is None:\n            sim_time_str = \"  -.--ns\"\n        else:\n            time_ns = get_time_from_sim_steps(sim_time, 'ns')\n            sim_time_str = \"{:6.2f}ns\".format(time_ns)\n        prefix = sim_time_str.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.get(record.levelno,\"%s\") % line for line in msg.split('\\n')])\n        level = (SimColourLogFormatter.loglevel2colour.get(record.levelno, \"%s\") %\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"}]}