Source code for jgdv.logging.format.colour

  1#!/usr/bin/env python3
  2"""
  3see `Alexandra Zaharia <https://alexandra-zaharia.github.io/posts/make-your-own-custom-color-formatter-with-python-logging/>`_
  4
  5"""
  6# Imports:
  7from __future__ import annotations
  8
  9# ##-- stdlib imports
 10import datetime
 11import enum
 12import functools as ftz
 13import itertools as itz
 14import logging
 15import logging as logmod
 16import pathlib as pl
 17import re
 18import warnings
 19from collections import defaultdict
 20from string import Formatter
 21from uuid import UUID, uuid1
 22
 23# ##-- end stdlib imports
 24
 25# ##-- 3rd party imports
 26import sty
 27from sty import bg, ef, fg, rs
 28
 29# ##-- end 3rd party imports
 30
 31# ##-- 1st party imports
 32from jgdv import Mixin, Proto
 33
 34# ##-- end 1st party imports
 35
 36from . import _interface as API # noqa: N812
 37from .stack_m import StackFormatter_m
 38
 39# ##-- types
 40# isort: off
 41import abc
 42import collections.abc
 43from typing import TYPE_CHECKING, cast, assert_type, assert_never
 44from typing import Generic, NewType
 45# Protocols:
 46from typing import Protocol, runtime_checkable
 47# Typing Decorators:
 48from typing import no_type_check, final, override, overload
 49
 50if TYPE_CHECKING:
 51    from jgdv import Maybe, Rx
 52    from typing import Final
 53    from typing import ClassVar, Any, LiteralString
 54    from typing import Never, Self, Literal
 55    from typing import TypeGuard
 56    from collections.abc import Iterable, Iterator, Callable, Generator
 57    from collections.abc import Sequence, Mapping, MutableMapping, Hashable
 58
 59    from logging import LogRecord
 60
 61    type StyleChar = Literal["%", "{", "$"]
 62##--|
 63
 64# isort: on
 65# ##-- end types
 66
 67COLOUR_RESET       : str    = rs.all
 68##--|
 69
 70
[docs] 71@Mixin(StackFormatter_m) 72class ColourFormatter(logging.Formatter): 73 """ 74 Stream Formatter for logging, enables use of colour sent to console 75 76 Guarded Formatter for adding colour. 77 Uses the sty module. 78 If sty is missing, behaves as the default formatter class 79 80 # Do *not* use for on filehandler 81 Usage reminder: 82 # Create stdout handler for logging to the console (logs all five levels) 83 stdout_handler = logging.StreamHandler() 84 stdout_handler.setFormatter(ColourFormatter(fmt)) 85 logger.addHandler(stdout_handler) 86 """ 87 88 _default_fmt : str = '{asctime} | {levelname:9} | {message}' 89 _default_date_fmt : str = "%H:%M:%S" 90 _default_style : StyleChar = '{' 91 colours : dict[int|str, str] 92 93 def __init__(self, *, fmt:Maybe[str]=None, style:Maybe[StyleChar]=None) -> None: 94 """ 95 Create the ColourFormatter with a given *Brace* style log format 96 """ 97 super().__init__(fmt or self._default_fmt, 98 datefmt=self._default_date_fmt, 99 style=style or self._default_style) 100 self.colours = defaultdict(lambda: rs.all) 101 self.apply_colour_mapping(API.default_colour_mapping) 102 self.apply_colour_mapping(API.default_log_colours) 103
[docs] 104 @override 105 def format(self, record:LogRecord) -> str: 106 if hasattr(record, "colour"): 107 log_colour = self.colours[record.colour] 108 else: 109 log_colour = self.colours[record.levelno] 110 111 return log_colour + super().format(record) + COLOUR_RESET
112
[docs] 113 def apply_colour_mapping(self, mapping:dict) -> None: 114 """ applies a mapping of colours by treating each value as a pair of attrs of sty 115 116 eg: {logging.DEBUG: ("fg", "blue"), logging.INFO: ("bg", "red")} 117 """ 118 for x,(a,b) in mapping.items(): 119 accessor = getattr(sty, a) 120 val = getattr(accessor, b) 121 self.colours[x] = val
122 123
[docs] 124@Mixin(StackFormatter_m) 125class StripColourFormatter(logging.Formatter): 126 """ 127 Force Colour Command codes to be stripped out of a string. 128 Useful for when you redirect printed strings with colour 129 to a file 130 """ 131 132 _default_fmt : str = "{asctime} | {levelname:9} | {shortname:25} | {message}" 133 _default_date_fmt : str = "%Y-%m-%d %H:%M:%S" 134 _default_style : StyleChar = '{' 135 _colour_strip_re : Rx = re.compile(r'\x1b\[([\d;]+)m?') 136 137 def __init__(self, *, fmt:Maybe[str]=None, style:Maybe[StyleChar]=None) -> None: 138 """ 139 Create the StripColourFormatter with a given *Brace* style log format 140 """ 141 super().__init__(fmt or self._default_fmt, 142 datefmt=style or self._default_date_fmt, 143 style=self._default_style) 144
[docs] 145 @override 146 def format(self, record:LogRecord) -> str: 147 result = super().format(record) 148 no_colour = self._colour_strip_re.sub("", result) 149 return no_colour