diff --git a/package/logger_36/catalog/config/console_rich.py b/package/logger_36/catalog/config/console_rich.py index 974b15ebdd903d8da521e385a0b6238084b93bff..90f7a4d78a86b5c8953c6dc6c649dd773ae98371 100644 --- a/package/logger_36/catalog/config/console_rich.py +++ b/package/logger_36/catalog/config/console_rich.py @@ -24,6 +24,9 @@ ACTUAL_COLOR = "indian_red" EXPECTED_COLOR = "green" ELAPSED_TIME_COLOR = "green" +ALTERNATIVE_BACKGROUND_FOR_LIGHT = style_t(bgcolor=color_t.from_rgb(230, 230, 230)) +ALTERNATIVE_BACKGROUND_FOR_DARK = style_t(bgcolor=color_t.from_rgb(25, 25, 25)) + GRAY_STYLE = style_t(color=color_t.from_rgb(150, 150, 150)) """ diff --git a/package/logger_36/catalog/handler/console_rich.py b/package/logger_36/catalog/handler/console_rich.py index 6036ba707173d7fb09929425fc3c2e9b69ffdd35..ba20ee969f4559c4cf5a670600bfd711900e1a75 100644 --- a/package/logger_36/catalog/handler/console_rich.py +++ b/package/logger_36/catalog/handler/console_rich.py @@ -10,6 +10,8 @@ import typing as h from logger_36.catalog.config.console_rich import ( ACTUAL_COLOR, + ALTERNATIVE_BACKGROUND_FOR_DARK, + ALTERNATIVE_BACKGROUND_FOR_LIGHT, DATE_TIME_COLOR, ELAPSED_TIME_COLOR, EXPECTED_COLOR, @@ -49,9 +51,20 @@ _EXCLUSIVE_TRACEBACK_ARGUMENTS = ( @d.dataclass(slots=True, repr=False, eq=False) class console_rich_handler_t(lggg.Handler): + """ + alternating_lines: + - Initial value: + - 1: enabled for dark background + - 2: enabled for light background + - anything else: disabled + - Runtime value: 0/1=do not/do highlight next time. + """ + extension: handler_extension_t = d.field(init=False) console: console_t = d.field(init=False) FormattedLines: h.Callable[..., tuple[str, str | None]] = d.field(init=False) + alternating_lines: int = 0 + background_is_light: bool = True name: d.InitVar[str | None] = None level: d.InitVar[int] = lggg.NOTSET @@ -60,6 +73,7 @@ class console_rich_handler_t(lggg.Handler): message_width: d.InitVar[int] = -1 formatter: d.InitVar[lggg.Formatter | None] = None should_install_traceback: d.InitVar[bool] = False + should_record: d.InitVar[bool] = False rich_kwargs: d.InitVar[dict[str, h.Any] | None] = None @@ -72,6 +86,7 @@ class console_rich_handler_t(lggg.Handler): message_width: int, formatter: lggg.Formatter | None, should_install_traceback: bool, + should_record: bool, rich_kwargs: dict[str, h.Any] | None, ) -> None: """""" @@ -103,7 +118,7 @@ class console_rich_handler_t(lggg.Handler): self.console = console_t( highlight=False, force_terminal=True, - record=True, + record=should_record, **rich_console_kwargs, ) if should_install_traceback: @@ -111,52 +126,83 @@ class console_rich_handler_t(lggg.Handler): InstallTracebackHandler(**rich_traceback_kwargs) self.FormattedLines = self.extension.FormattedLines + if self.alternating_lines == 1: + self.alternating_lines = 0 + self.background_is_light = False + elif self.alternating_lines == 2: + self.alternating_lines = 0 + self.background_is_light = True + else: + self.alternating_lines = -1 def emit(self, record: lggg.LogRecord, /) -> None: """""" - cls = self.__class__ if hasattr(record, SHOW_W_RULE_ATTR): richer = Rule(record.msg, DATE_TIME_COLOR) else: first, next_s = self.FormattedLines(record, PreProcessed=EscapedForRich) - richer = cls.HighlightedVersion(first, next_s, record.levelno) + should_highlight_back = self.alternating_lines == 1 + if self.alternating_lines >= 0: + self.alternating_lines = (self.alternating_lines + 1) % 2 + richer = HighlightedVersion( + self.console, + first, + next_s, + record.levelno, + should_highlight_back=should_highlight_back, + background_is_light=self.background_is_light, + ) self.console.print(richer, crop=False, overflow="ignore") def ShowMessage(self, message: str, /) -> None: """""" self.console.print(message, crop=False, overflow="ignore") - @classmethod - def HighlightedVersion( - cls, first_line: str, next_lines: str | None, log_level: int, / - ) -> renderable_t: - """""" - output = text_t(first_line) - - # Used instead of _CONTEXT_LENGTH which might include \t, thus creating a - # mismatch between character length and length when displayed in console. - context_end = first_line.find(LEVEL_CLOSING) - elapsed_time_separator = first_line.rfind(ELAPSED_TIME_SEPARATOR) - where_separator = first_line.rfind( - WHERE_SEPARATOR, context_end, elapsed_time_separator - ) - - output.stylize(DATE_TIME_COLOR, end=TIME_LENGTH) - output.stylize( - LEVEL_COLOR[log_level], - start=TIME_LENGTH, - end=context_end + 1, - ) - output.stylize(GRAY_STYLE, start=where_separator, end=elapsed_time_separator) - output.stylize(ELAPSED_TIME_COLOR, start=elapsed_time_separator) - - if next_lines is not None: - output.append(next_lines) - _ = output.highlight_regex(ACTUAL_PATTERNS, style=ACTUAL_COLOR) - _ = output.highlight_regex(EXPECTED_PATTERNS, style=EXPECTED_COLOR) +def HighlightedVersion( + _: console_t, + first_line: str, + next_lines: str | None, + log_level: int, + /, + *, + should_highlight_back: bool = False, + background_is_light: bool = True, +) -> renderable_t: + """""" + output = text_t(first_line) + + # Used instead of _CONTEXT_LENGTH which might include \t, thus creating a + # mismatch between character length and length when displayed in console. + context_end = first_line.find(LEVEL_CLOSING) + elapsed_time_separator = first_line.rfind(ELAPSED_TIME_SEPARATOR) + where_separator = first_line.rfind( + WHERE_SEPARATOR, context_end, elapsed_time_separator + ) + + output.stylize(DATE_TIME_COLOR, end=TIME_LENGTH) + output.stylize( + LEVEL_COLOR[log_level], + start=TIME_LENGTH, + end=context_end + 1, + ) + output.stylize(GRAY_STYLE, start=where_separator, end=elapsed_time_separator) + output.stylize(ELAPSED_TIME_COLOR, start=elapsed_time_separator) + + if next_lines is not None: + output.append(next_lines) + + _ = output.highlight_regex(ACTUAL_PATTERNS, style=ACTUAL_COLOR) + _ = output.highlight_regex(EXPECTED_PATTERNS, style=EXPECTED_COLOR) + + if should_highlight_back: + if background_is_light: + style = ALTERNATIVE_BACKGROUND_FOR_LIGHT + else: + style = ALTERNATIVE_BACKGROUND_FOR_DARK + output.stylize(style) - return output + return output """ diff --git a/package/logger_36/catalog/handler/generic.py b/package/logger_36/catalog/handler/generic.py index f6460ed8c2060121e723a078a750b602f9d45856..eac8063a9ac4104266b9bcface489050b5718e22 100644 --- a/package/logger_36/catalog/handler/generic.py +++ b/package/logger_36/catalog/handler/generic.py @@ -10,7 +10,7 @@ import typing as h try: from logger_36.catalog.config.console_rich import DATE_TIME_COLOR - from logger_36.catalog.handler.console_rich import console_rich_handler_t + from logger_36.catalog.handler.console_rich import HighlightedVersion from rich.console import Console as console_t from rich.console import ConsoleOptions as console_options_t from rich.markup import escape as EscapedForRich @@ -32,11 +32,21 @@ interface_h = can_show_message_p | h.Callable[[str], None] @d.dataclass(slots=True, repr=False, eq=False) class generic_handler_t(lggg.Handler): + """ + alternating_lines: + - Initial value: + - 1: enabled for dark background + - 2: enabled for light background + - anything else: disabled + - Runtime value: 0/1=do not/do highlight next time. + """ extension: handler_extension_t = d.field(init=False) console: console_t = None console_options: console_options_t = None FormattedLines: h.Callable[..., tuple[str, str | None]] = d.field(init=False) + alternating_lines: int = 0 + background_is_light: bool = True ShowMessage: h.Callable[[str], None] = lambda _arg: None name: d.InitVar[str | None] = None @@ -47,6 +57,7 @@ class generic_handler_t(lggg.Handler): formatter: d.InitVar[lggg.Formatter | None] = None supports_html: d.InitVar[bool] = False + should_record: d.InitVar[bool] = (False,) rich_kwargs: d.InitVar[dict[str, h.Any] | None] = None interface: d.InitVar[interface_h | None] = None # Cannot be None actually. @@ -59,6 +70,7 @@ class generic_handler_t(lggg.Handler): message_width: int, formatter: lggg.Formatter | None, supports_html: bool, + should_record: bool, rich_kwargs: dict[str, h.Any] | None, interface: interface_h | None, ) -> None: @@ -81,6 +93,7 @@ class generic_handler_t(lggg.Handler): self.console = console_t( highlight=False, force_terminal=True, + record=should_record, **rich_kwargs, ) self.console_options = self.console.options.update( @@ -88,6 +101,14 @@ class generic_handler_t(lggg.Handler): ) self.FormattedLines = self.extension.FormattedLines + if self.alternating_lines == 1: + self.alternating_lines = 0 + self.background_is_light = False + elif self.alternating_lines == 2: + self.alternating_lines = 0 + self.background_is_light = True + else: + self.alternating_lines = -1 self.ShowMessage = getattr( interface, can_show_message_p.ShowMessage.__name__, interface @@ -105,8 +126,16 @@ class generic_handler_t(lggg.Handler): richer = Rule(record.msg, DATE_TIME_COLOR) else: first, next_s = self.FormattedLines(record, PreProcessed=EscapedForRich) - richer = console_rich_handler_t.HighlightedVersion( - first, next_s, record.levelno + should_highlight_back = self.alternating_lines == 1 + if self.alternating_lines >= 0: + self.alternating_lines = (self.alternating_lines + 1) % 2 + richer = HighlightedVersion( + self.console, + first, + next_s, + record.levelno, + should_highlight_back=should_highlight_back, + background_is_light=self.background_is_light, ) segments = self.console.render(richer, options=self.console_options) diff --git a/package/logger_36/handler.py b/package/logger_36/handler.py index ad1af801cf8a969d1a890f13582e64697a5c4f1c..b8f4a73f9fb6df02cd83d54a1577cc76e8aee9f1 100644 --- a/package/logger_36/handler.py +++ b/package/logger_36/handler.py @@ -38,6 +38,8 @@ def AddGenericHandler( message_width: int = -1, formatter: lggg.Formatter | None = None, supports_html: bool = False, + alternating_lines: int = 2, + should_record: bool = False, should_hold_messages: bool = False, **kwargs, ) -> None: @@ -53,6 +55,8 @@ def AddGenericHandler( message_width=message_width, formatter=formatter, supports_html=supports_html, + alternating_lines=alternating_lines, + should_record=should_record, rich_kwargs=kwargs, interface=interface, ) @@ -94,8 +98,10 @@ def AddRichConsoleHandler( show_memory_usage: bool = False, message_width: int = -1, formatter: lggg.Formatter | None = None, + alternating_lines: int = 2, should_hold_messages: bool = False, should_install_traceback: bool = False, + should_record: bool = False, **kwargs, ) -> None: """""" @@ -111,7 +117,9 @@ def AddRichConsoleHandler( additional_s = {} else: additional_s = { + "alternating_lines": alternating_lines, "should_install_traceback": should_install_traceback, + "should_record": should_record, "rich_kwargs": kwargs, } handler = console_rich_handler_t( diff --git a/package/logger_36/task/storage.py b/package/logger_36/task/storage.py index b4c293d1afe628e7822c760d03de88d6711e36d2..e96a296f436916c804a39c8dcb63216308800f6b 100644 --- a/package/logger_36/task/storage.py +++ b/package/logger_36/task/storage.py @@ -101,22 +101,20 @@ def SaveLOGasHTML(path: str | path_t | h.TextIO = None) -> None: LOGGER.warning(f'{cannot_save}: File "{path}" already exists.') return - console = None - found = False for handler in LOGGER.handlers: console = getattr(handler, "console", None) - if found := isinstance(console, console_t): + if isinstance(console, console_t) and console.record: + html = console.export_html() + if actual_file: + with open(path, "w") as accessor: + accessor.write(html) + else: + path.write(html) break - - if found: - html = console.export_html() - if actual_file: - with open(path, "w") as accessor: - accessor.write(html) - else: - path.write(html) else: - LOGGER.warning(f"{cannot_save}: No handler has a RICH console.") + LOGGER.warning( + f"{cannot_save}: No handler has a RICH console with recording ON." + ) """ diff --git a/package/logger_36/type/logger.py b/package/logger_36/type/logger.py index 07b54cbb00de4edab2bee573e0abc43e589301d5..da0084956561aa8257f58ecc3e0e2ed88d772a61 100644 --- a/package/logger_36/type/logger.py +++ b/package/logger_36/type/logger.py @@ -215,7 +215,8 @@ class logger_t(lggg.Logger): lggg.Logger.handle(self, record) if (self.exit_on_critical and (record.levelno is lggg.CRITICAL)) or ( - self.exit_on_error and (record.levelno is lggg.ERROR)): + self.exit_on_error and (record.levelno is lggg.ERROR) + ): # Also works if self.exit_on_error and record.levelno is lggg.CRITICAL since # __post_init__ set self.exit_on_critical if self.exit_on_error. sstm.exit(1) diff --git a/test/main.py b/test/main.py index eb14d346c89c83ba85167f4590dfb9fc3cef8258..013cf8d517b24150d5fcb937f0b17d7022cf79b3 100644 --- a/test/main.py +++ b/test/main.py @@ -49,7 +49,10 @@ with TemporaryDirectory() as tmp_folder: tmp_file = tmp_folder / "log.txt" AddRichConsoleHandler( - level=lggg.DEBUG, show_memory_usage=True, should_hold_messages=True + level=lggg.DEBUG, + show_memory_usage=True, + should_hold_messages=True, + alternating_lines=1, ) AddGenericHandler(print, message_width=80, should_hold_messages=True) AddFileHandler(tmp_file, show_memory_usage=True) # Level=lggg.INFO.