From 007307b03a65af2ac1bf15c498b966d0d02a989f Mon Sep 17 00:00:00 2001 From: DEBREUVE Eric <eric.debreuve@cnrs.fr> Date: Fri, 7 Feb 2025 17:31:56 +0100 Subject: [PATCH] better management of rich presence, refactoring, better exception hook --- package/logger_36/api/logger.py | 2 +- package/logger_36/api/storage.py | 2 +- .../logger_36/catalog/config/console_rich.py | 4 +- .../logger.py => catalog/config/optional.py} | 56 +++------------ package/logger_36/catalog/handler/console.py | 9 ++- .../logger_36/catalog/handler/console_rich.py | 19 ++++-- package/logger_36/catalog/handler/file.py | 8 ++- package/logger_36/catalog/handler/generic.py | 18 +++-- package/logger_36/catalog/logger/gpu.py | 4 +- package/logger_36/constant/error.py | 2 +- package/logger_36/constant/handler.py | 4 +- package/logger_36/constant/logger.py | 11 +-- package/logger_36/content.py | 6 +- package/logger_36/exception.py | 58 ++++++++++++---- package/logger_36/gpu.py | 2 +- package/logger_36/handler.py | 27 ++++---- package/logger_36/memory.py | 15 ++-- package/logger_36/storage.py | 2 +- package/logger_36/system.py | 2 +- package/logger_36/task/format/rule.py | 6 +- package/logger_36/task/measure/memory.py | 4 +- package/logger_36/task/storage.py | 12 ++-- package/logger_36/time.py | 4 +- package/logger_36/type/handler.py | 27 ++++++-- package/logger_36/type/logger.py | 68 +++++++++++++++---- package/logger_36/version.py | 2 +- test/exception.py | 5 +- 27 files changed, 229 insertions(+), 150 deletions(-) rename package/logger_36/{config/logger.py => catalog/config/optional.py} (59%) diff --git a/package/logger_36/api/logger.py b/package/logger_36/api/logger.py index 906dfca..f45bc0b 100644 --- a/package/logger_36/api/logger.py +++ b/package/logger_36/api/logger.py @@ -4,7 +4,7 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023 SEE COPYRIGHT NOTICE BELOW """ -from logger_36.type.logger import logger_t +from logger_36.type.logger import logger_t # noqa """ COPYRIGHT NOTICE diff --git a/package/logger_36/api/storage.py b/package/logger_36/api/storage.py index ada69d3..9802750 100644 --- a/package/logger_36/api/storage.py +++ b/package/logger_36/api/storage.py @@ -4,7 +4,7 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023 SEE COPYRIGHT NOTICE BELOW """ -from logger_36.task.storage import html_reader_t +from logger_36.task.storage import html_reader_t # noqa """ COPYRIGHT NOTICE diff --git a/package/logger_36/catalog/config/console_rich.py b/package/logger_36/catalog/config/console_rich.py index bd09679..838e3f5 100644 --- a/package/logger_36/catalog/config/console_rich.py +++ b/package/logger_36/catalog/config/console_rich.py @@ -6,8 +6,8 @@ SEE COPYRIGHT NOTICE BELOW import logging as l -from rich.color import Color as color_t -from rich.style import Style as style_t +from rich.color import Color as color_t # noqa +from rich.style import Style as style_t # noqa """ Colors: See https://rich.readthedocs.io/en/stable/appendix/colors.html. diff --git a/package/logger_36/config/logger.py b/package/logger_36/catalog/config/optional.py similarity index 59% rename from package/logger_36/config/logger.py rename to package/logger_36/catalog/config/optional.py index 41a29ae..a0f50e0 100644 --- a/package/logger_36/config/logger.py +++ b/package/logger_36/catalog/config/optional.py @@ -4,54 +4,14 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023 SEE COPYRIGHT NOTICE BELOW """ -import logging as l - -from logger_36.catalog.handler.console import console_handler_t -from logger_36.catalog.handler.console_rich import console_rich_handler_t -from logger_36.catalog.handler.file import file_handler_t -from logger_36.catalog.handler.generic import generic_handler_t -from logger_36.constant.handler import HANDLER_CODES, handler_codes_h -from logger_36.instance.logger import L -from logger_36.task.format.message import MessageWithActualExpected - - -def SetLOGLevel( - level: int, - /, - *, - logger: l.Logger = L, - which: handler_codes_h | str = "a", -) -> None: - """ - which: g=generic, c=console, f=file, a=all, str=name. - """ - which_is_name = which not in HANDLER_CODES - found = False - for handler in logger.handlers: - if ( - (which == "a") - or ((which == "g") and isinstance(handler, generic_handler_t)) - or ( - (which == "c") - and isinstance(handler, (console_handler_t, console_rich_handler_t)) - ) - or ((which == "f") and isinstance(handler, file_handler_t)) - or (which == handler.name) - ): - handler.setLevel(level) - if which_is_name: - return - found = True - - if not found: - raise ValueError( - MessageWithActualExpected( - "Handler not found", - actual=which, - expected=f"{str(HANDLER_CODES)[1:-1]}, or a handler name", - ) - ) - +try: + import rich # noqa +except ModuleNotFoundError: + RICH_IS_AVAILABLE = False + from logger_36.constant.error import MISSING_RICH_MESSAGE +else: + RICH_IS_AVAILABLE = True + MISSING_RICH_MESSAGE = None """ COPYRIGHT NOTICE diff --git a/package/logger_36/catalog/handler/console.py b/package/logger_36/catalog/handler/console.py index 31fb26b..ca990e7 100644 --- a/package/logger_36/catalog/handler/console.py +++ b/package/logger_36/catalog/handler/console.py @@ -13,12 +13,19 @@ from logger_36.constant.message import LINE_INDENT from logger_36.constant.record import SHOW_W_RULE_ATTR from logger_36.task.format.rule import RuleAsText from logger_36.type.handler import handler_extension_t +from logger_36.type.handler import message_from_record_raw_p as message_from_record_p @d.dataclass(slots=True, repr=False, eq=False) class console_handler_t(l.Handler): + """ + kind: See logger_36.constant.handler.handler_codes_h. + """ + + kind: h.ClassVar[str] = "c" + extension: handler_extension_t = d.field(init=False) - MessageFromRecord: h.Callable[..., tuple[str, str | None]] = d.field(init=False) + MessageFromRecord: message_from_record_p = d.field(init=False) name: d.InitVar[str | None] = None level: d.InitVar[int] = l.NOTSET diff --git a/package/logger_36/catalog/handler/console_rich.py b/package/logger_36/catalog/handler/console_rich.py index 3e62bca..46ec039 100644 --- a/package/logger_36/catalog/handler/console_rich.py +++ b/package/logger_36/catalog/handler/console_rich.py @@ -24,11 +24,14 @@ from logger_36.constant.message import CONTEXT_LENGTH, LINE_INDENT from logger_36.constant.record import SHOW_W_RULE_ATTR from logger_36.task.format.rule import Rule, rule_t from logger_36.type.handler import handler_extension_t -from rich.console import Console as console_t -from rich.console import RenderableType as renderable_t -from rich.markup import escape as EscapedVersion -from rich.text import Text as text_t -from rich.traceback import install as InstallTracebackHandler +from logger_36.type.handler import ( + message_from_record_preprocessed_p as message_from_record_p, +) +from rich.console import Console as console_t # noqa +from rich.console import RenderableType as renderable_t # noqa +from rich.markup import escape as EscapedVersion # noqa +from rich.text import Text as text_t # noqa +from rich.traceback import install as InstallTracebackHandler # noqa _COMMON_TRACEBACK_ARGUMENTS = ("theme", "width") _EXCLUSIVE_TRACEBACK_ARGUMENTS = ( @@ -47,6 +50,8 @@ _EXCLUSIVE_TRACEBACK_ARGUMENTS = ( @d.dataclass(slots=True, repr=False, eq=False) class console_rich_handler_t(l.Handler): """ + kind: See logger_36.constant.handler.handler_codes_h. + alternating_lines: - Initial value: - 1: enabled for dark background @@ -55,9 +60,11 @@ class console_rich_handler_t(l.Handler): - Runtime value: 0/1=do not/do highlight next time. """ + kind: h.ClassVar[str] = "c" + extension: handler_extension_t = d.field(init=False) console: console_t = d.field(init=False) - MessageFromRecord: h.Callable[..., str] = d.field(init=False) + MessageFromRecord: message_from_record_p = d.field(init=False) alternating_lines: int = 0 background_is_light: bool = True diff --git a/package/logger_36/catalog/handler/file.py b/package/logger_36/catalog/handler/file.py index 96f3a7e..a73ef49 100644 --- a/package/logger_36/catalog/handler/file.py +++ b/package/logger_36/catalog/handler/file.py @@ -14,13 +14,19 @@ from logger_36.constant.message import LINE_INDENT from logger_36.constant.record import SHOW_W_RULE_ATTR from logger_36.task.format.rule import RuleAsText from logger_36.type.handler import handler_extension_t +from logger_36.type.handler import message_from_record_raw_p as message_from_record_p @d.dataclass(slots=True, repr=False, eq=False) class file_handler_t(l.FileHandler): + """ + kind: See logger_36.constant.handler.handler_codes_h. + """ + + kind: h.ClassVar[str] = "f" extension: handler_extension_t = d.field(init=False) - MessageFromRecord: h.Callable[..., tuple[str, str | None]] = d.field(init=False) + MessageFromRecord: message_from_record_p = d.field(init=False) name: d.InitVar[str | None] = None level: d.InitVar[int] = l.NOTSET diff --git a/package/logger_36/catalog/handler/generic.py b/package/logger_36/catalog/handler/generic.py index 0f743d9..5914602 100644 --- a/package/logger_36/catalog/handler/generic.py +++ b/package/logger_36/catalog/handler/generic.py @@ -8,19 +8,23 @@ import dataclasses as d import logging as l import typing as h -try: +from logger_36.catalog.config.optional import RICH_IS_AVAILABLE + +if RICH_IS_AVAILABLE: from logger_36.catalog.config.console_rich import DATE_TIME_COLOR from logger_36.catalog.handler.console_rich import HighlightedVersion from rich.console import Console as console_t # noqa from rich.console import ConsoleOptions as console_options_t # noqa from rich.markup import escape as EscapedForRich # noqa from rich.terminal_theme import DEFAULT_TERMINAL_THEME # noqa -except ModuleNotFoundError: - console_t = console_options_t = EscapedForRich = DEFAULT_TERMINAL_THEME = None +else: + DATE_TIME_COLOR = HighlightedVersion = console_t = console_options_t = ( + EscapedForRich + ) = DEFAULT_TERMINAL_THEME = None from logger_36.constant.record import SHOW_W_RULE_ATTR from logger_36.task.format.rule import Rule, RuleAsText -from logger_36.type.handler import handler_extension_t +from logger_36.type.handler import handler_extension_t, message_from_record_h @h.runtime_checkable @@ -36,6 +40,8 @@ class display_rule_p(h.Protocol): @d.dataclass(slots=True, repr=False, eq=False) class generic_handler_t(l.Handler): """ + kind: See logger_36.constant.handler.handler_codes_h. + alternating_lines: - Initial value: - 1: enabled for dark background @@ -54,6 +60,8 @@ class generic_handler_t(l.Handler): it is indeed called at the end of the emit method. """ + kind: h.ClassVar[str] = "g" + ShowMessage: show_message_p # "None -> h.Any" (twice below) since None | None is invalid. console: console_t | h.Any = None @@ -63,7 +71,7 @@ class generic_handler_t(l.Handler): DisplayRule: display_rule_p = d.field(init=False) extension: handler_extension_t = d.field(init=False) - MessageFromRecord: h.Callable[..., tuple[str, str | None]] = d.field(init=False) + MessageFromRecord: message_from_record_h = d.field(init=False) name: d.InitVar[str | None] = None level: d.InitVar[int] = l.NOTSET diff --git a/package/logger_36/catalog/logger/gpu.py b/package/logger_36/catalog/logger/gpu.py index bc855ee..2cc37d9 100644 --- a/package/logger_36/catalog/logger/gpu.py +++ b/package/logger_36/catalog/logger/gpu.py @@ -13,11 +13,11 @@ from logger_36.type.logger import logger_t try: import tensorflow as tsfl # noqa import tensorrt as tsrt # noqa - - _GPU_LOGGING_ERROR = None except ModuleNotFoundError: tsfl = tsrt = None _GPU_LOGGING_ERROR = GPU_LOGGING_ERROR +else: + _GPU_LOGGING_ERROR = None def LogGPURelatedDetails(*, logger: logger_t = L) -> None: diff --git a/package/logger_36/constant/error.py b/package/logger_36/constant/error.py index 1c7d6d4..8a741a8 100644 --- a/package/logger_36/constant/error.py +++ b/package/logger_36/constant/error.py @@ -16,7 +16,7 @@ MEMORY_MEASURE_ERROR = ( "is not installed or not importable." ) -MISSING_RICH_ERROR = ( +MISSING_RICH_MESSAGE = ( "The Rich console handler is not available because the Rich package " "(https://rich.readthedocs.io/en/stable/) " "is not installed or not importable. " diff --git a/package/logger_36/constant/handler.py b/package/logger_36/constant/handler.py index f323b62..34ddb1d 100644 --- a/package/logger_36/constant/handler.py +++ b/package/logger_36/constant/handler.py @@ -6,8 +6,8 @@ SEE COPYRIGHT NOTICE BELOW import typing as h -handler_codes_h = h.Literal["g", "c", "f", "a"] -HANDLER_CODES: tuple[str, ...] = h.get_args(handler_codes_h) +handler_codes_h = h.Literal["g", "c", "f", "a"] # g=generic, c=console, f=file, a=all. +HANDLER_KINDS: tuple[str, ...] = h.get_args(handler_codes_h) ANONYMOUS = "<Anonymous>" diff --git a/package/logger_36/constant/logger.py b/package/logger_36/constant/logger.py index 53d39e7..870a8eb 100644 --- a/package/logger_36/constant/logger.py +++ b/package/logger_36/constant/logger.py @@ -4,21 +4,14 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023 SEE COPYRIGHT NOTICE BELOW """ -import logging as l -import re as regx -import typing as h +import re as r LOGGER_NAME = "logger-36" # https://docs.python.org/3/library/logging.html#logging.captureWarnings WARNING_LOGGER_NAME = "py.warnings" WARNING_TYPE_PATTERN = r"\s*([^:]+):([0-9]+):\s*([^:]+)\s*:((.|\n)*)" -WARNING_TYPE_COMPILED_PATTERN = regx.compile(WARNING_TYPE_PATTERN) - -# Second version: with self as first parameter. -logger_handle_h = ( - h.Callable[[l.LogRecord], None] | h.Callable[[l.Logger, l.LogRecord], None] -) +WARNING_TYPE_COMPILED_PATTERN = r.compile(WARNING_TYPE_PATTERN) """ COPYRIGHT NOTICE diff --git a/package/logger_36/content.py b/package/logger_36/content.py index 4e82a16..b50d82e 100644 --- a/package/logger_36/content.py +++ b/package/logger_36/content.py @@ -4,9 +4,9 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023 SEE COPYRIGHT NOTICE BELOW """ -from logger_36.constant.message import LINE_INDENT -from logger_36.task.format.message import MessageWithActualExpected -from logger_36.task.format.rule import Rule, RuleAsText +from logger_36.constant.message import LINE_INDENT # noqa +from logger_36.task.format.message import MessageWithActualExpected # noqa +from logger_36.task.format.rule import Rule, RuleAsText # noqa """ COPYRIGHT NOTICE diff --git a/package/logger_36/exception.py b/package/logger_36/exception.py index 2066fdc..0b82c1e 100644 --- a/package/logger_36/exception.py +++ b/package/logger_36/exception.py @@ -4,6 +4,7 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023 SEE COPYRIGHT NOTICE BELOW """ +import re as r import sys as s import tempfile as tmpf import traceback as tcbk @@ -30,24 +31,57 @@ def _HandleException( while trace.tb_next is not None: trace = trace.tb_next frame = trace.tb_frame - module = frame.f_code.co_filename - function = frame.f_code.co_name - line = frame.f_lineno - - home = str(path_t.home()) - if module.startswith(home): - module = "~" + module[home.__len__() :] - - message = str(exception) + code = frame.f_code + module = path_t(code.co_filename) + function = code.co_name + line_number = frame.f_lineno + line_content = module.read_text().splitlines()[line_number - 1].strip() + + # Format module. + home = path_t.home() + if module.is_relative_to(home): + module = path_t("~") / module.relative_to(home) + + # Format line content. + if line_content.startswith("raise "): + # Do not display code of explicit exception raising. + line_content = None + + # Find variables appearing in the line. + if line_content is None: + line_content = variables = "" + else: + all_variables = frame.f_locals + found_names = [] + for match in r.finditer(r"[^\d\W]\w*", line_content): + name = match.group() + if name in all_variables: + found_names.append(name) + if found_names.__len__() > 0: + longest = max(map(len, found_names)) + variables = map( + lambda _: f"{_:{longest}} = {all_variables[_]}", sorted(found_names) + ) + variables = " " + "\n ".join(variables) + "\n" + else: + variables = "" + + line_content = f" {line_content}\n" + + # Format message. + message = str(exception).strip() if message.__len__() > 0: - message = f" {message}\n" + message = f" {message[0].title()}{message[1:]}\n" document = tmpf.NamedTemporaryFile(delete=False) print( f"{stripe.__name__}\n" - f" {module}.{function}@{line}\n" - f"{message} Full report at: {document.name}", + f" {module}:{function}@{line_number}\n" + f"{line_content}" + f"{variables}" + f"{message}" + f" Full report at: {document.name}", file=s.stderr, ) diff --git a/package/logger_36/gpu.py b/package/logger_36/gpu.py index af0df2d..b1477a6 100644 --- a/package/logger_36/gpu.py +++ b/package/logger_36/gpu.py @@ -4,7 +4,7 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023 SEE COPYRIGHT NOTICE BELOW """ -from logger_36.catalog.logger.gpu import LogGPURelatedDetails +from logger_36.catalog.logger.gpu import LogGPURelatedDetails # noqa """ COPYRIGHT NOTICE diff --git a/package/logger_36/handler.py b/package/logger_36/handler.py index c5a6532..c09b904 100644 --- a/package/logger_36/handler.py +++ b/package/logger_36/handler.py @@ -8,21 +8,18 @@ import logging as l import sys as s from pathlib import Path as path_t +from logger_36.catalog.config.optional import MISSING_RICH_MESSAGE, RICH_IS_AVAILABLE from logger_36.catalog.handler.console import console_handler_t from logger_36.catalog.handler.file import file_handler_t from logger_36.catalog.handler.generic import generic_handler_t, show_message_p -from logger_36.constant.error import MISSING_RICH_ERROR -try: +if RICH_IS_AVAILABLE: from logger_36.catalog.handler.console_rich import console_rich_handler_t - - _MISSING_RICH_ERROR = None -except ModuleNotFoundError: +else: from logger_36.catalog.handler.console import ( console_handler_t as console_rich_handler_t, ) - - _MISSING_RICH_ERROR = MISSING_RICH_ERROR +_MISSING_RICH_MESSAGE = MISSING_RICH_MESSAGE def AddGenericHandler( @@ -54,7 +51,7 @@ def AddGenericHandler( rich_kwargs=kwargs, ShowMessage=ShowMessage, ) - logger.AddHandler(handler, should_hold_messages) + logger.AddHandler(handler, should_hold_messages=should_hold_messages) def AddConsoleHandler( @@ -76,7 +73,7 @@ def AddConsoleHandler( message_width=message_width, formatter=formatter, ) - logger.AddHandler(handler, should_hold_messages) + logger.AddHandler(handler, should_hold_messages=should_hold_messages) def AddRichConsoleHandler( @@ -95,10 +92,10 @@ def AddRichConsoleHandler( **kwargs, ) -> None: """""" - global _MISSING_RICH_ERROR - if _MISSING_RICH_ERROR is not None: - print(_MISSING_RICH_ERROR, file=s.stderr) - _MISSING_RICH_ERROR = None + global _MISSING_RICH_MESSAGE + if _MISSING_RICH_MESSAGE is not None: + print(_MISSING_RICH_MESSAGE, file=s.stderr) + _MISSING_RICH_MESSAGE = None if console_rich_handler_t is console_handler_t: additional_s = {} @@ -117,7 +114,7 @@ def AddRichConsoleHandler( formatter=formatter, **additional_s, ) - logger.AddHandler(handler, should_hold_messages) + logger.AddHandler(handler, should_hold_messages=should_hold_messages) def AddFileHandler( @@ -149,7 +146,7 @@ def AddFileHandler( handler_args=args, handler_kwargs=kwargs, ) - logger.AddHandler(handler, should_hold_messages) + logger.AddHandler(handler, should_hold_messages=should_hold_messages) """ diff --git a/package/logger_36/memory.py b/package/logger_36/memory.py index 40d5590..d8fdc4a 100644 --- a/package/logger_36/memory.py +++ b/package/logger_36/memory.py @@ -4,14 +4,17 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023 SEE COPYRIGHT NOTICE BELOW """ -from logger_36.catalog.logger.memory import LogMaximumMemoryUsage, LogMemoryUsages -from logger_36.task.format.memory import FormattedUsage as FormattedMemoryUsage -from logger_36.task.format.memory import ( +from logger_36.catalog.logger.memory import ( # noqa + LogMaximumMemoryUsage, + LogMemoryUsages, +) +from logger_36.task.format.memory import FormattedUsage as FormattedMemoryUsage # noqa +from logger_36.task.format.memory import ( # noqa FormattedUsageWithAutoUnit as FormattedMemoryUsageWithAutoUnit, ) -from logger_36.task.format.memory import UsageBar as MemoryUsageBar -from logger_36.task.measure.memory import CanCheckUsage as CanCheckMemoryUsage -from logger_36.task.measure.memory import CurrentUsage as CurrentMemoryUsage +from logger_36.task.format.memory import UsageBar as MemoryUsageBar # noqa +from logger_36.task.measure.memory import CanCheckUsage as CanCheckMemoryUsage # noqa +from logger_36.task.measure.memory import CurrentUsage as CurrentMemoryUsage # noqa """ COPYRIGHT NOTICE diff --git a/package/logger_36/storage.py b/package/logger_36/storage.py index f5d15d0..3dd694f 100644 --- a/package/logger_36/storage.py +++ b/package/logger_36/storage.py @@ -4,7 +4,7 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023 SEE COPYRIGHT NOTICE BELOW """ -from logger_36.task.storage import SaveLOGasHTML +from logger_36.task.storage import SaveLOGasHTML # noqa """ COPYRIGHT NOTICE diff --git a/package/logger_36/system.py b/package/logger_36/system.py index 00c1b9a..4599d5b 100644 --- a/package/logger_36/system.py +++ b/package/logger_36/system.py @@ -4,7 +4,7 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023 SEE COPYRIGHT NOTICE BELOW """ -from logger_36.catalog.logger.system import LogSystemDetails +from logger_36.catalog.logger.system import LogSystemDetails # noqa """ COPYRIGHT NOTICE diff --git a/package/logger_36/task/format/rule.py b/package/logger_36/task/format/rule.py index 70a8dff..c3a80ab 100644 --- a/package/logger_36/task/format/rule.py +++ b/package/logger_36/task/format/rule.py @@ -4,6 +4,8 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023 SEE COPYRIGHT NOTICE BELOW """ +from logger_36.catalog.config.optional import RICH_IS_AVAILABLE + def RuleAsText(text: str | None, /) -> str: """""" @@ -13,7 +15,7 @@ def RuleAsText(text: str | None, /) -> str: return f"---- ---- ---- ---- {text} ---- ---- ---- ----" -try: +if RICH_IS_AVAILABLE: from rich.rule import Rule as rule_t # noqa from rich.text import Text as text_t # noqa @@ -24,7 +26,7 @@ try: else: return rule_t(title=text_t(text, style=f"bold {color}"), style=color) -except ModuleNotFoundError: +else: Rule = lambda _txt, _: RuleAsText(_txt) """ diff --git a/package/logger_36/task/measure/memory.py b/package/logger_36/task/measure/memory.py index 903346f..33b7713 100644 --- a/package/logger_36/task/measure/memory.py +++ b/package/logger_36/task/measure/memory.py @@ -6,10 +6,10 @@ SEE COPYRIGHT NOTICE BELOW try: from psutil import Process as process_t # noqa - - _PROCESS = process_t() except ModuleNotFoundError: _PROCESS = None +else: + _PROCESS = process_t() def CanCheckUsage() -> bool: diff --git a/package/logger_36/task/storage.py b/package/logger_36/task/storage.py index cf79fe9..cfe82f2 100644 --- a/package/logger_36/task/storage.py +++ b/package/logger_36/task/storage.py @@ -6,17 +6,19 @@ SEE COPYRIGHT NOTICE BELOW import dataclasses as d import logging as l -import re as regx +import re as r from html.parser import HTMLParser as html_parser_t from io import IOBase as io_base_t from pathlib import Path as path_t -try: +from logger_36.catalog.config.optional import RICH_IS_AVAILABLE +from logger_36.instance.logger import L + +if RICH_IS_AVAILABLE: from rich.console import Console as console_t # noqa -except ModuleNotFoundError: +else: console_t = None -from logger_36.instance.logger import L _BODY_END_PATTERN = r"</[bB][oO][dD][yY]>(.|\n)*$" @@ -60,7 +62,7 @@ class html_reader_t(html_parser_t): output[self.body_position_start[0] : (self.body_position_end[0] + 1)] ) output = output[self.body_position_start[1] :] - output = regx.sub(_BODY_END_PATTERN, "", output, count=1) + output = r.sub(_BODY_END_PATTERN, "", output, count=1) return output.strip() diff --git a/package/logger_36/time.py b/package/logger_36/time.py index d79b049..af0820d 100644 --- a/package/logger_36/time.py +++ b/package/logger_36/time.py @@ -4,8 +4,8 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023 SEE COPYRIGHT NOTICE BELOW """ -from logger_36.catalog.logger.chronos import LogElapsedTime -from logger_36.task.measure.chronos import ElapsedTime, TimeStamp +from logger_36.catalog.logger.chronos import LogElapsedTime # noqa +from logger_36.task.measure.chronos import ElapsedTime, TimeStamp # noqa """ COPYRIGHT NOTICE diff --git a/package/logger_36/type/handler.py b/package/logger_36/type/handler.py index 88f1c0d..7f40840 100644 --- a/package/logger_36/type/handler.py +++ b/package/logger_36/type/handler.py @@ -16,12 +16,31 @@ from logger_36.config.message import ( WHERE_SEPARATOR, ) from logger_36.constant.error import MEMORY_MEASURE_ERROR -from logger_36.constant.handler import HANDLER_CODES +from logger_36.constant.handler import HANDLER_KINDS from logger_36.constant.message import NEXT_LINE_PROLOGUE from logger_36.task.format.message import MessageWithActualExpected from logger_36.task.measure.chronos import TimeStamp from logger_36.task.measure.memory import CanCheckUsage as CanCheckMemoryUsage + +@h.runtime_checkable +class message_from_record_raw_p(h.Protocol): + def __call__(self, record: l.LogRecord, /) -> str: ... + + +@h.runtime_checkable +class message_from_record_preprocessed_p(h.Protocol): + def __call__( + self, + record: l.LogRecord, + /, + *, + PreProcessed: h.Callable[[str], str] | None = None, + ) -> str: ... + + +message_from_record_h = message_from_record_raw_p | message_from_record_preprocessed_p + _MEMORY_MEASURE_ERROR = MEMORY_MEASURE_ERROR @@ -30,7 +49,7 @@ class handler_extension_t: name: str | None = None should_store_memory_usage: bool = False message_width: int = -1 - MessageFromRecord: h.Callable[..., str] = d.field(init=False) + MessageFromRecord: message_from_record_h = d.field(init=False) handler: d.InitVar[l.Handler | None] = None level: d.InitVar[int] = l.NOTSET @@ -42,12 +61,12 @@ class handler_extension_t: """""" global _MEMORY_MEASURE_ERROR - if self.name in HANDLER_CODES: + if self.name in HANDLER_KINDS: raise ValueError( MessageWithActualExpected( "Invalid handler name", actual=self.name, - expected=f"a name not in {str(HANDLER_CODES)[1:-1]}", + expected=f"a name not in {str(HANDLER_KINDS)[1:-1]}", ) ) diff --git a/package/logger_36/type/logger.py b/package/logger_36/type/logger.py index 888d726..b44a9ed 100644 --- a/package/logger_36/type/logger.py +++ b/package/logger_36/type/logger.py @@ -24,13 +24,12 @@ from logger_36.config.message import ( TIME_FORMAT, ) from logger_36.constant.generic import NOT_PASSED -from logger_36.constant.handler import ANONYMOUS +from logger_36.constant.handler import ANONYMOUS, HANDLER_KINDS, handler_codes_h from logger_36.constant.issue import ISSUE_LEVEL_SEPARATOR, ORDER, order_h from logger_36.constant.logger import ( LOGGER_NAME, WARNING_LOGGER_NAME, WARNING_TYPE_COMPILED_PATTERN, - logger_handle_h, ) from logger_36.constant.memory import UNKNOWN_MEMORY_USAGE from logger_36.constant.message import TIME_LENGTH_m_1, expected_op_h @@ -46,14 +45,18 @@ from logger_36.task.measure.chronos import ElapsedTime from logger_36.task.measure.memory import CurrentUsage as CurrentMemoryUsage from logger_36.type.issue import NewIssue, issue_t -logger_base_t = l.Logger +base_t = l.Logger + +logger_handle_raw_h = h.Callable[[l.LogRecord], None] +logger_handle_with_self_h = h.Callable[[l.Logger, l.LogRecord], None] +logger_handle_h = logger_handle_raw_h | logger_handle_with_self_h _DATE_TIME_ORIGIN = date_time_t.fromtimestamp(1970, None) _DATE_ORIGIN = _DATE_TIME_ORIGIN.date() @d.dataclass(slots=True, repr=False, eq=False) -class logger_t(logger_base_t): +class logger_t(base_t): """ intercepted_wrn_handle: When warning interception is on, this stores the original "handle" method of the Python warning logger. @@ -85,9 +88,9 @@ class logger_t(logger_base_t): self, name_: str, level_: int, activate_wrn_interceptions: bool ) -> None: """""" - logger_base_t.__init__(self, name_) + base_t.__init__(self, name_) self.setLevel(level_) - self.propagate = False # Part of logger_base_t. + self.propagate = False # Part of base_t. for level in l.getLevelNamesMapping().values(): self.events[level] = 0 @@ -97,6 +100,39 @@ class logger_t(logger_base_t): if self.exit_on_error: self.exit_on_critical = True + def SetLevel( + self, + level: int, + /, + *, + which: handler_codes_h | str = "a", + ) -> None: + """ + Set level of handlers, but the logger level is not modified. + + which: if not a handler_codes_h, then it corresponds to a handler name. + """ + found = False + for handler in self.handlers: + if ( + (which == "a") + or ((which in "cfg") and (getattr(handler, "kind", None) == which)) + or (which == handler.name) + ): + handler.setLevel(level) + if which not in HANDLER_KINDS: + return + found = True + + if not found: + raise ValueError( + MessageWithActualExpected( + "Handler not found", + actual=which, + expected=f"{str(HANDLER_KINDS)[1:-1]}, or a handler name", + ) + ) + def MakeRich(self, *, alternating_lines: int = 2) -> None: """""" OverrideExceptionFormat() @@ -182,10 +218,12 @@ class logger_t(logger_base_t): return "?", UNKNOWN_MEMORY_USAGE - def AddHandler(self, handler: l.Handler, should_hold_messages: bool, /) -> None: + def AddHandler( + self, handler: l.Handler, /, *, should_hold_messages: bool = False + ) -> None: """""" self.should_hold_messages = should_hold_messages - logger_base_t.addHandler(self, handler) + base_t.addHandler(self, handler) extension = getattr(handler, "extension", None) if extension is None: @@ -212,7 +250,7 @@ class logger_t(logger_base_t): if (self.on_hold.__len__() > 0) and not self.should_hold_messages: for held in self.on_hold: - logger_base_t.handle(self, held) + base_t.handle(self, held) self.on_hold.clear() if (date := now.date()) != self.last_message_date: @@ -229,7 +267,7 @@ class logger_t(logger_base_t): if self.should_hold_messages: self.on_hold.append(date_record) else: - logger_base_t.handle(self, date_record) + base_t.handle(self, date_record) # When. if now - self.last_message_now > LONG_ENOUGH: @@ -271,7 +309,7 @@ class logger_t(logger_base_t): if self.should_hold_messages: self.on_hold.append(record) else: - logger_base_t.handle(self, record) + base_t.handle(self, record) if (self.exit_on_critical and (record.levelno is l.CRITICAL)) or ( self.exit_on_error and (record.levelno is l.ERROR) @@ -462,10 +500,10 @@ class logger_t(logger_base_t): return False -def _HandleForWarnings(interceptor: logger_base_t, /) -> logger_handle_h: +def _HandleForWarnings(interceptor: base_t, /) -> logger_handle_h: """""" - def handle_p(_: logger_base_t, record: l.LogRecord, /) -> None: + def handle_p(_: base_t, record: l.LogRecord, /) -> None: pieces = WARNING_TYPE_COMPILED_PATTERN.match(record.msg) if pieces is None: # The warning message does not follow the default format. @@ -491,11 +529,11 @@ def _HandleForWarnings(interceptor: logger_base_t, /) -> logger_handle_h: def _HandleForInterceptions( - intercepted: logger_base_t, interceptor: logger_base_t, / + intercepted: base_t, interceptor: base_t, / ) -> logger_handle_h: """""" - def handle_p(_: logger_base_t, record: l.LogRecord, /) -> None: + def handle_p(_: base_t, record: l.LogRecord, /) -> None: duplicate = l.makeLogRecord(record.__dict__) duplicate.msg = f"{record.msg} :{intercepted.name}:" interceptor.handle(duplicate) diff --git a/package/logger_36/version.py b/package/logger_36/version.py index c4211f1..c819449 100644 --- a/package/logger_36/version.py +++ b/package/logger_36/version.py @@ -4,7 +4,7 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023 SEE COPYRIGHT NOTICE BELOW """ -__version__ = "2025.5" +__version__ = "2025.6" """ COPYRIGHT NOTICE diff --git a/test/exception.py b/test/exception.py index 7db549b..e6bd080 100644 --- a/test/exception.py +++ b/test/exception.py @@ -7,7 +7,10 @@ SEE COPYRIGHT NOTICE BELOW from logger_36.exception import OverrideExceptionFormat OverrideExceptionFormat() -raise ValueError("Invalid value") +numerator = 1 +denominator = 0 +invalid = numerator / denominator +# raise ValueError("Invalid value") """ -- GitLab