Passed
Push — main ( 60119b...e5c7f7 )
by Douglas
02:02
created

pocketutils.misc.fancy_loguru   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 440
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 313
dl 0
loc 440
rs 6
c 0
b 0
f 0
wmc 55

18 Methods

Rating   Name   Duplication   Size   Complexity  
C FancyLoguru.config_level() 0 29 9
A FancyLoguru.paths() 0 5 2
B FancyLoguru.from_cli() 0 31 6
A FancyLoguru.main() 0 6 2
A FancyLoguru.add_path() 0 23 1
A FancyLoguru.logger() 0 3 1
A FancyLoguru.aliases() 0 3 1
A FancyLoguru.remove_path() 0 8 4
A FancyLoguru.__init__() 0 6 1
A FormatFactory.with_extras() 0 15 2
B FancyLoguru.config_levels() 0 16 6
A FancyLoguru.init() 0 17 2
A FormatFactory.plain() 0 6 1
A InterceptHandler.emit() 0 12 3
A LogSinkInfo.guess() 0 17 4
A FancyLoguruExtras.force_streams_to_utf8() 0 6 1
B FancyLoguru.config_main() 0 29 8
A FancyLoguru.levels() 0 3 1

How to fix   Complexity   

Complexity

Complex classes like pocketutils.misc.fancy_loguru often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""
2
Loguru logging extension that, configurably:
3
- redirects built-in logging to your loguru logger
4
- remembers the handlers added
5
- auto-detects compression and serialization from filenames
6
- includes extras in non-serializing handlers
7
- has a few alternative (possibly better) choices for colors, icons, and levels
8
- will complain when you do really stupid things
9
- has a convenient notation for configuring from a CLI
10
  (e.g. ``--stderr debug --log :INFO:run.log.gz``)
11
- mandes utf-8
12
"""
13
from __future__ import annotations
14
15
import logging
16
import os
17
import sys
18
from dataclasses import dataclass
19
from inspect import cleandoc
20
from pathlib import Path
21
from typing import AbstractSet, Any, Callable, Generic, Mapping, Optional, TextIO, TypeVar, Union
22
23
# noinspection PyProtectedMember
24
import loguru._defaults as _defaults
0 ignored issues
show
introduced by
Unable to import 'loguru._defaults'
Loading history...
25
import regex
0 ignored issues
show
introduced by
Unable to import 'regex'
Loading history...
26
from loguru import logger
0 ignored issues
show
introduced by
Unable to import 'loguru'
Loading history...
27
28
# noinspection PyProtectedMember
29
from loguru._logger import Logger
0 ignored issues
show
introduced by
Unable to import 'loguru._logger'
Loading history...
30
31
from pocketutils.core.exceptions import IllegalStateError, XValueError
32
33
Formatter = Union[str, Callable[[Mapping[str, Any]], str]]
34
_FMT = cleandoc(
35
    r"""
36
    <bold>{time:YYYY-MM-DD HH:mm:ss.SSS}</bold> |
37
    <level>{level: <8}</level> |
38
    <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan>
39
    — <level>{message}[EXTRA]</level>
40
    {exception}
41
    """
42
).replace("\n", " ")
43
44
45
class FormatFactory:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
46
    @classmethod
47
    def with_extras(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
48
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
49
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
50
        fmt: str = _FMT,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
51
        sep: str = "; ",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
52
        eq_sign: str = " ",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
53
    ) -> Callable[[Mapping[str, Any]], str]:
54
        def FMT(record: Mapping[str, Any]) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Function name "FMT" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
55
            extra = sep.join([e + eq_sign + "{extra[" + e + "]}" for e in record["extra"].keys()])
56
            if len(extra) > 0:
57
                extra = f" [ {extra} ]"
58
            return fmt.replace("[EXTRA]", extra) + os.linesep
59
60
        return FMT
61
62
    @classmethod
63
    def plain(cls, *, fmt: str = _FMT) -> Callable[[Mapping[str, Any]], str]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
64
        def FMT(record: Mapping[str, Any]) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Function name "FMT" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Unused Code introduced by
The argument record seems to be unused.
Loading history...
65
            return fmt.replace("[EXTRA]", "")
66
67
        return FMT
68
69
70
class _SENTINEL:
71
    pass
72
73
74
T = TypeVar("T", covariant=True, bound=Logger)
0 ignored issues
show
Coding Style Naming introduced by
Class name "T" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
75
76
_LOGGER_ARG_PATTERN = regex.compile(r"(?:([a-zA-Z]+):)?(.*)", flags=regex.V1)
77
log_compressions = {
78
    ".xz",
79
    ".lzma",
80
    ".gz",
81
    ".zip",
82
    ".bz2",
83
    ".tar",
84
    ".tar.gz",
85
    ".tar.bz2",
86
    ".tar.xz",
87
}
88
valid_log_suffixes = {
89
    *{f".log{c}" for c in log_compressions},
90
    *{f".txt{c}" for c in log_compressions},
91
    *{f".json{c}" for c in log_compressions},
92
}
93
94
95
class FancyLoguruDefaults:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
96
    levels_built_in = dict(
97
        TRACE=_defaults.LOGURU_TRACE_NO,
98
        DEBUG=_defaults.LOGURU_DEBUG_NO,
99
        INFO=_defaults.LOGURU_INFO_NO,
100
        WARNING=_defaults.LOGURU_WARNING_NO,
101
        ERROR=_defaults.LOGURU_ERROR_NO,
102
        CRITICAL=_defaults.LOGURU_CRITICAL_NO,
103
    )
104
105
    # the levels for caution and notice are DEFINED here
106
    # trace and success must match loguru's
107
    # and the rest must match logging's
108
    # note that most of these alternate between informative and problematic
109
    # i.e. info (ok), caution (bad), success (ok), warning (bad), notice (ok), error (bad)
110
    levels_extended = {
111
        **levels_built_in,
112
        **dict(
113
            CAUTION=23,
114
            SUCCESS=25,
115
            NOTICE=35,
116
        ),
117
    }
118
119
    colors_built_in = dict(
120
        TRACE=_defaults.LOGURU_TRACE_COLOR,
121
        DEBUG=_defaults.LOGURU_DEBUG_COLOR,
122
        INFO=_defaults.LOGURU_INFO_COLOR,
123
        SUCCESS=_defaults.LOGURU_SUCCESS_COLOR,
124
        WARNING=_defaults.LOGURU_WARNING_COLOR,
125
        ERROR=_defaults.LOGURU_ERROR_COLOR,
126
        CRITICAL=_defaults.LOGURU_CRITICAL_COLOR,
127
    )
128
129
    colors_extended = dict(
130
        **colors_built_in,
131
        CAUTION=_defaults.LOGURU_WARNING_COLOR,
132
        NOTICE=_defaults.LOGURU_INFO_COLOR,
133
    )
134
135
    colors_red_green_safe = dict(
136
        TRACE="<dim>",
137
        DEBUG="<dim>",
138
        INFO="<bold>",
139
        CAUTION="<yellow>",
140
        SUCCESS="<blue>",
141
        WARNING="<yellow>",
142
        NOTICE="<blue>",
143
        ERROR="<red>",
144
        CRITICAL="<red>",
145
    )
146
147
    icons_built_in = dict(
148
        TRACE=_defaults.LOGURU_TRACE_ICON,
149
        DEBUG=_defaults.LOGURU_DEBUG_ICON,
150
        INFO=_defaults.LOGURU_INFO_ICON,
151
        SUCCESS=_defaults.LOGURU_SUCCESS_ICON,
152
        WARNING=_defaults.LOGURU_WARNING_ICON,
153
        ERROR=_defaults.LOGURU_ERROR_ICON,
154
        CRITICAL=_defaults.LOGURU_CRITICAL_ICON,
155
    )
156
157
    icons_extended = dict(
158
        **icons_built_in,
159
        CAUTION="⚐",
160
        NOTICE="★",
161
    )
162
163
    level: str = "INFO"
164
165
    fmt = FormatFactory.with_extras()
166
167
    aliases = dict(NONE=None, NO=None, OFF=None, VERBOSE="INFO", QUIET="ERROR")
168
169
170
@dataclass(frozen=True, repr=True, order=True)
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
171
class HandlerInfo:
172
    hid: int
173
    path: Optional[Path]
174
    level: Optional[str]
175
    fmt: Formatter
176
177
178
@dataclass(frozen=False, repr=True)
179
class _HandlerInfo:
180
    hid: int
181
    sink: Any
182
    level: Optional[int]
183
    fmt: Formatter
184
185
186
@dataclass(frozen=True, repr=True, order=True)
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
187
class LogSinkInfo:
188
    path: Path
189
    base: Path
190
    suffix: str
191
    serialize: bool
192
    compression: Optional[str]
193
194
    @classmethod
195
    def guess(cls, path: Union[str, Path]) -> LogSinkInfo:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
196
        path = Path(path)
197
        base, compression = path.name, None
198
        for c in log_compressions:
0 ignored issues
show
Coding Style Naming introduced by
Variable name "c" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
199
            if path.name.endswith(c):
200
                base, compression = path.name[: -len(c)], c
201
        if not [base.endswith(s) for s in [".json", ".log", ".txt"]]:
202
            raise XValueError(
203
                f"Log filename {path.name} is not .json, .log, .txt, or a compressed variant"
204
            )
205
        return LogSinkInfo(
206
            path=path,
207
            base=path.parent / base,
208
            suffix=compression,
209
            serialize=base.endswith(".json"),
210
            compression=compression,
211
        )
212
213
214
class InterceptHandler(logging.Handler):
215
    """
216
    Redirects standard logging to loguru.
217
    """
218
219
    def emit(self, record):
220
        # Get corresponding Loguru level if it exists
221
        try:
222
            level = logger.level(record.levelname).name
223
        except ValueError:
224
            level = record.levelno
225
        # Find caller from where originated the logged message
226
        frame, depth = logging.currentframe(), 2
227
        while frame.f_code.co_filename == logging.__file__:
228
            frame = frame.f_back
229
            depth += 1
230
        logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
231
232
233
class FancyLoguru(Generic[T]):
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
234
    def __init__(self, log: T):
235
        self._levels = dict(FancyLoguruDefaults.levels_built_in)
236
        self._logger = log
237
        self._main = None
238
        self._paths = {}
239
        self._aliases = dict(FancyLoguruDefaults.aliases)
240
241
    @property
242
    def logger(self) -> T:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
243
        return self._logger
244
245
    @property
246
    def levels(self) -> Mapping[str, int]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
247
        return dict(self._levels)
248
249
    @property
250
    def aliases(self) -> Mapping[str, str]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
251
        return self._aliases
252
253
    @property
254
    def main(self) -> Optional[HandlerInfo]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
255
        if self._main is None:
256
            return None
257
        return HandlerInfo(
258
            hid=self._main.hid, level=self._main.level, fmt=self._main.fmt, path=None
259
        )
260
261
    @property
262
    def paths(self) -> AbstractSet[HandlerInfo]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
263
        if self._main is None:
264
            return set()
265
        return {HandlerInfo(hid=h.hid, level=h.level, fmt=h.fmt, path=h.sink) for h in self._paths}
266
267
    def config_levels(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
268
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
269
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
270
        levels: Mapping[str, int] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
271
        colors: Mapping[str, str] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
272
        icons: Mapping[str, str] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
273
        aliases: Mapping[str, str] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
274
    ) -> __qualname__:
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable '__qualname__'
Loading history...
275
        levels = FancyLoguruDefaults.levels_extended if levels is _SENTINEL else levels
276
        colors = FancyLoguruDefaults.colors_extended if colors is _SENTINEL else colors
277
        icons = FancyLoguruDefaults.icons_extended if icons is _SENTINEL else icons
278
        aliases = FancyLoguruDefaults.aliases if aliases is _SENTINEL else aliases
279
        for k, v in levels.items():
0 ignored issues
show
Coding Style Naming introduced by
Variable name "v" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
280
            self.config_level(k, v, color=colors.get(k, _SENTINEL), icon=icons.get(k, _SENTINEL))
281
        self._aliases = dict(aliases)
282
        return self
283
284
    def init(
285
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
286
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
287
        level: str = FancyLoguruDefaults.level,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
288
        sink=sys.stderr,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
289
        fmt: Formatter = FancyLoguruDefaults.fmt,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
290
        intercept: bool = True,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
291
    ) -> __qualname__:
292
        """
293
        Sets an initial configuration.
294
        """
295
        if intercept:
296
            # noinspection PyArgumentList
297
            logging.basicConfig(handlers=[InterceptHandler()], level=0, encoding="utf-8")
298
        self.logger.remove(None)  # get rid of the built-in handler
299
        self.config_main(level=level, sink=sink, fmt=fmt)
300
        return self
301
302
    def config_level(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
303
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
304
        name: str,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
305
        level: int,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
306
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
307
        color: Union[None, str, _SENTINEL] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
308
        icon: Union[None, str, _SENTINEL] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
309
        replace: bool = True,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
310
    ) -> __qualname__:
311
        try:
312
            data = logger.level(name)
313
        except ValueError:
314
            data = None
315
        if data is None:
316
            logger.level(
317
                name,
318
                no=level,
319
                color=None if color is _SENTINEL else color,
320
                icon=None if icon is _SENTINEL else icon,
321
            )
322
        elif replace:
323
            if level != data.no:  # loguru doesn't check whether they're eq; it just errors
324
                raise IllegalStateError(f"Cannot set level={level}!={data.no} for {name}")
325
            logger.level(
326
                name,
327
                color=data.color if color is _SENTINEL else color,
328
                icon=data.icon if icon is _SENTINEL else icon,
329
            )
330
        return self
331
332
    def config_main(
333
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
334
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
335
        sink: TextIO = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
336
        level: Optional[str] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
337
        fmt: Formatter = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
338
    ) -> __qualname__:
339
        """
340
        Sets the logging level for the main handler (normally stderr).
341
        """
342
        if level is not None and level is not _SENTINEL:
343
            level = level.upper()
344
        if self._main is None:
345
            self._main = _HandlerInfo(
346
                hid=-1,
347
                sink=sys.stderr,
348
                level=self._levels[FancyLoguruDefaults.level],
349
                fmt=FancyLoguruDefaults.fmt,
350
            )
351
        else:
352
            try:
353
                logger.remove(self._main.hid)
354
            except ValueError:
355
                logger.error(f"Cannot remove handler {self._main.hid}")
356
        self._main.level = self._main.level if level is _SENTINEL else level
357
        self._main.sink = self._main.sink if sink is _SENTINEL else sink
358
        self._main.fmt = self._main.fmt if fmt is _SENTINEL else fmt
359
        self._main.hid = logger.add(self._main.sink, level=self._main.level, format=self._main.fmt)
360
        return self
361
362
    def remove_path(self, path: Path) -> __qualname__:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
363
        for k, h in self._paths.items():
0 ignored issues
show
Coding Style Naming introduced by
Variable name "h" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
364
            if h.path.resolve() == path.resolve():
365
                h.level = None
366
                try:
367
                    logger.remove(k)
368
                except ValueError:
369
                    logger.error(f"Cannot remove handler {k} to {path}")
370
371
    def add_path(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
372
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
373
        path: Path,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
374
        level: str = FancyLoguruDefaults.level,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
375
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
376
        fmt: str = FancyLoguruDefaults.fmt,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
377
    ) -> __qualname__:
378
        level = level.upper()
379
        ell = self._levels[level]
380
        info = LogSinkInfo.guess(path)
381
        x = logger.add(
0 ignored issues
show
Coding Style Naming introduced by
Variable name "x" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
382
            str(info.base),
383
            format=fmt,
384
            level=level,
385
            compression=info.compression,
386
            serialize=info.serialize,
387
            backtrace=True,
388
            diagnose=True,
389
            enqueue=True,
390
            encoding="utf-8",
391
        )
392
        self._paths[x] = _HandlerInfo(hid=x, sink=info.base, level=ell, fmt=fmt)
393
        return self
394
395
    def from_cli(
396
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
397
        path: Union[None, str, Path] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
398
        main: Optional[str] = FancyLoguruDefaults.level,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
399
    ) -> __qualname__:
400
        """
401
        This function controls logging set via command-line.
402
403
        Args:
404
            main: The level for stderr
405
            path: If set, the path to a file. Can be prefixed with ``:level:`` to set the level
406
                  (e.g. ``:INFO:mandos-run.log.gz``). Can serialize to JSON if .json is used
407
                  instead of .log or .txt.
408
        """
409
        if main is None:
410
            main = FancyLoguruDefaults.level
411
        main = self._aliases.get(main.upper(), main.upper())
412
        if main not in FancyLoguruDefaults.levels_extended:
413
            _permitted = ", ".join(
414
                [*FancyLoguruDefaults.levels_extended, *FancyLoguruDefaults.aliases.keys()]
415
            )
416
            raise XValueError(f"{main.lower()} not a permitted log level (allowed: {_permitted}")
417
        self.config_main(level=main)
418
        if path is not None or len(str(path)) == 0:
419
            match = _LOGGER_ARG_PATTERN.match(str(path))
420
            path_level = "DEBUG" if match.group(1) is None else match.group(1)
421
            path = Path(match.group(2))
422
            self.add_path(path, path_level)
423
            self.logger.info(f"Added logger to {path} at level {path_level}")
424
        self.logger.info(f"Set main log level to {main}")
425
        return self
426
427
    __call__ = from_cli
428
429
430
class FancyLoguruExtras:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
431
    @classmethod
432
    def force_streams_to_utf8(cls) -> None:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
433
        # we warn the user about this in the docs!
434
        sys.stderr.reconfigure(encoding="utf-8")
435
        sys.stdout.reconfigure(encoding="utf-8")
436
        sys.stdin.reconfigure(encoding="utf-8")
437
438
439
__all__ = ["FancyLoguruDefaults", "FancyLoguru", "FancyLoguruExtras", "HandlerInfo"]
440