Coverage for klayout_pex/log/logger.py: 95%
79 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-17 17:24 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-17 17:24 +0000
1#
2# --------------------------------------------------------------------------------
3# SPDX-FileCopyrightText: 2024 Martin Jan Köhler and Harald Pretl
4# Johannes Kepler University, Institute for Integrated Circuits.
5#
6# This file is part of KPEX
7# (see https://github.com/martinjankoehler/klayout-pex).
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21# SPDX-License-Identifier: GPL-3.0-or-later
22# --------------------------------------------------------------------------------
23#
24from __future__ import annotations
25from enum import IntEnum
26from functools import cached_property
27import logging
28import rich.console
29import rich.logging
30from typing import *
33class LogLevel(IntEnum):
34 ALL = 0
35 DEBUG = 10
36 SUBPROCESS = 12
37 VERBOSE = 15
38 INFO = 20
39 WARNING = 30
40 ERROR = 40
41 CRITICAL = 50
42 DEFAULT = SUBPROCESS
44 @classmethod
45 @cached_property
46 def level_by_name(cls) -> Dict[str, LogLevel]:
47 return {e.name: e for e in cls}
50class LogLevelFormatter(logging.Formatter):
51 def format(self, record: logging.LogRecord) -> str:
52 msg = record.getMessage()
53 match record.levelno:
54 case LogLevel.WARNING.value: return f"[yellow]{msg}"
55 case LogLevel.ERROR.value: return f"[red]{msg}"
56 case _:
57 return msg
60class LogLevelFilter(logging.Filter):
61 def __init__(self, levels: Iterable[str], invert: bool = False):
62 super().__init__()
63 self.levels = levels
64 self.invert = invert
66 def filter(self, record: logging.LogRecord) -> bool:
67 if self.invert:
68 return record.levelname not in self.levels
69 else:
70 return record.levelname in self.levels
73console = rich.console.Console()
74__logger = logging.getLogger("__kpex__")
77def set_log_level(log_level: LogLevel):
78 __logger.setLevel(log_level)
81def register_additional_handler(handler: logging.Handler):
82 """
83 Adds a new handler to the default logger.
85 :param handler: The new handler. Must be of type ``logging.Handler``
86 or its subclasses.
87 """
88 __logger.addHandler(handler)
91def deregister_additional_handler(handler: logging.Handler):
92 """
93 Removes a registered handler from the default logger.
95 :param handler: The handler. If not registered, the behavior
96 of this function is undefined.
97 """
98 __logger.removeHandler(handler)
102def configure_logger():
103 global __logger, console
105 for level in LogLevel:
106 logging.addLevelName(level=level.value, levelName=level.name)
108 subprocess_rich_handler = rich.logging.RichHandler(
109 console=console,
110 show_time=False,
111 omit_repeated_times=False,
112 show_level=False,
113 show_path=False,
114 enable_link_path=False,
115 markup=False,
116 tracebacks_word_wrap=False,
117 keywords=[]
118 )
119 subprocess_rich_handler.addFilter(LogLevelFilter(['SUBPROCESS']))
121 rich_handler = rich.logging.RichHandler(
122 console=console,
123 omit_repeated_times=False,
124 show_level=True,
125 markup=True,
126 rich_tracebacks=True,
127 tracebacks_suppress=[],
128 keywords=[]
129 )
131 rich_handler.setFormatter(LogLevelFormatter(fmt='%(message)s', datefmt='[%X]'))
132 rich_handler.addFilter(LogLevelFilter(['SUBPROCESS'], invert=True))
134 set_log_level(LogLevel.SUBPROCESS)
136 __logger.handlers.clear()
137 __logger.addHandler(subprocess_rich_handler)
138 __logger.addHandler(rich_handler)
141def debug(*args, **kwargs):
142 if not kwargs.get('stacklevel'): # ensure logged file location is correct
143 kwargs['stacklevel'] = 2
144 __logger.debug(*args, **kwargs)
147def subproc(msg: object, **kwargs):
148 if not kwargs.get('stacklevel'): # ensure logged file location is correct
149 kwargs['stacklevel'] = 2
150 __logger.log(LogLevel.SUBPROCESS, msg, **kwargs)
153def rule(title: str = '', **kwargs): # pragma: no cover
154 """
155 Prints a horizontal line on the terminal enclosing the first argument
156 if the log level is <= INFO.
158 Kwargs are passed to https://rich.readthedocs.io/en/stable/reference/console.html#rich.console.Console.rule
160 :param title: A title string to enclose in the console rule
161 """
162 console.rule(title)
165def info(*args, **kwargs):
166 if not kwargs.get('stacklevel'): # ensure logged file location is correct
167 kwargs['stacklevel'] = 2
168 __logger.info(*args, **kwargs)
171def warning(*args, **kwargs):
172 if not kwargs.get('stacklevel'): # ensure logged file location is correct
173 kwargs['stacklevel'] = 2
174 __logger.warning(*args, **kwargs)
177def error(*args, **kwargs):
178 if not kwargs.get('stacklevel'): # ensure logged file location is correct
179 kwargs['stacklevel'] = 2
180 __logger.error(*args, **kwargs)
183configure_logger()