Skip to content

Logging

duho.logging

TRACE module-attribute

VERBOSE_HELP = '' module-attribute

VERBOSE_LEVELS = {} module-attribute

__all__ = ['add_logging_level', 'DefaultFormatter', 'VERBOSE_LEVELS', 'VERBOSE_HELP', 'parse_loglevels', 'init_stderr_logging', 'initverbose'] module-attribute

DefaultFormatter(fmt='%(asctime)s | %(levelname)8s | %(name)s: %(message)s', datefmt=None, style='%', validate=True)

Bases: Formatter

Log formatter with colored output.

Source code in src/duho/logging.py
80
81
82
83
84
85
86
87
88
def __init__(
    self,
    fmt="%(asctime)s | %(levelname)8s | %(name)s: %(message)s",
    datefmt=None,
    style: "_logging._FormatStyle" = "%",
    validate=True,
) -> None:
    self._levelsize: 'int | None' = None
    super().__init__(fmt, datefmt, style, validate)

COLORS = {_logging.DEBUG: _asicode(34), _logging.INFO: _asicode(32), _logging.WARNING: _asicode(33), _logging.ERROR: _asicode(31), _logging.CRITICAL: _asicode(31, 47)} class-attribute instance-attribute

RESET_ALL = _asicode(0) class-attribute instance-attribute

format(record)

Source code in src/duho/logging.py
90
91
92
93
94
95
96
97
def format(self, record):
    record = _copy.copy(record)
    levelsize = self._levelsize if self._levelsize is not None else _LEVELSIZE
    record.levelname = record.levelname.center(levelsize)
    color = self.COLORS.get(record.levelno, None)
    if color:
        record.levelname = f"{color}{record.levelname}{self.RESET_ALL}"
    return super().format(record)

__getattr__(name)

Source code in src/duho/logging.py
23
24
def __getattr__(name: str):
    return getattr(_logging, name)

add_logging_level(name, level, force=False, color=None)

Register a custom log level.

Source code in src/duho/logging.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def add_logging_level(name: str, level: int, force=False, color: 'str | None' = None):
    """Register a custom log level."""
    name = name.upper()
    if hasattr(_logging, name) and not force:
        return
    setattr(_logging, name, level)
    _logging.addLevelName(level, name)

    def log_logger(self: _logging.Logger, message: str, *args, **kwargs):
        if self.isEnabledFor(level):
            self._log(level, message, args, **kwargs)

    name = name.lower()
    setattr(_logging.getLoggerClass(), name, log_logger)

    def log_root(msg, *args, **kwargs):
        _logging.log(level, msg, *args, **kwargs)

    if color is not None:
        DefaultFormatter.COLORS[level] = _getcolor(color)

    setattr(_logging, name, log_root)

init_stderr_logging(name=None, level=None)

Initialize logging to stderr with color support.

Source code in src/duho/logging.py
142
143
144
145
146
147
148
149
150
151
def init_stderr_logging(name=None, level: 'int | None' = None):
    """Initialize logging to stderr with color support."""
    initverbose()
    handler = _logging.StreamHandler(_sys.stderr)
    logger = _logging.getLogger(name)
    if level:
        logger.setLevel(level)
    logger.addHandler(handler)
    handler.setFormatter(DefaultFormatter())
    return logger

initverbose()

Initialize verbose level mappings.

Source code in src/duho/logging.py
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def initverbose():
    """Initialize verbose level mappings."""
    global VERBOSE_LEVELS, VERBOSE_HELP, _LEVELSIZE

    for name, loglevel in get_level_names_mapping().items():
        if not loglevel:
            continue
        aliases: list[str] = VERBOSE_LEVELS.setdefault(loglevel, [])
        _LEVELSIZE = max(_LEVELSIZE, len(name))
        if name not in aliases:
            aliases.append(name)

    VERBOSE_LEVELS = dict(
        sorted(VERBOSE_LEVELS.items(), key=lambda l: l[0], reverse=True)
    )

    VERBOSE_HELP = ", ".join([aliases[0] for aliases in VERBOSE_LEVELS.values()])

parse_loglevels(text, itemdivider=',', valkey_separator=':')

Parse a log level specification string.

Source code in src/duho/logging.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def parse_loglevels(text: str, itemdivider: str = ",", valkey_separator=":"):
    """Parse a log level specification string."""
    levels: dict[str, int] = {}
    levelmapping = get_level_names_mapping()

    for entry in text.split(itemdivider):
        name, *level = entry.split(valkey_separator, maxsplit=1)
        if not level:
            level = name
            name = ""
        else:
            level = level[0]
        level = levelmapping.get(level)
        if level is not None:
            levels[name] = level
    return levels