1#!/usr/bin/env python3
2"""
3
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 as logmod
15import pathlib as pl
16import re
17import time
18import weakref
19from uuid import UUID, uuid1
20
21# ##-- end stdlib imports
22
23# ##-- types
24# isort: off
25# General
26import abc
27import collections.abc
28import typing
29import types
30from typing import cast, assert_type, assert_never
31from typing import Generic, NewType, Never
32from typing import no_type_check, final, override, overload
33# Protocols and Interfaces:
34from typing import Protocol, runtime_checkable
35# isort: on
36# ##-- end types
37
38# ##-- type checking
39# isort: off
40if typing.TYPE_CHECKING:
41 from typing import Final, ClassVar, Any, Self
42 from typing import Literal, LiteralString
43 from typing import TypeGuard
44 from collections.abc import Iterable, Iterator, Callable, Generator
45 from collections.abc import Sequence, Mapping, MutableMapping, Hashable
46
47 from ._interface import Logger
48 from jgdv import Maybe, Traceback
49## isort: on
50# ##-- end type checking
51
52
53##-- logging
54logging = logmod.getLogger(__name__)
55##-- end logging
56
[docs]
57class LogContext:
58 """
59 a really simple wrapper to set a logger's level, then roll it back
60
61 use as:
62 with LogContext(logger, level=logmod.INFO) as ctx:
63 ctx.log("blah")
64 # or
65 logger.info("blah")
66 """
67
68 def __init__(self, logger:Logger, level:Maybe[int]=None) -> None:
69 self._logger = logger
70 self._original_level = self._logger.level
71 self._level_stack = [self._original_level]
72 self._temp_level = level or self._original_level
73
74 def __call__(self, level:int) -> Self:
75 self._temp_level = level
76 return self
77
78 def __enter__(self) -> Self:
79 match self._temp_level:
80 case int() | str():
81 self._level_stack.append(self._logger.level)
82 self._logger.setLevel(self._temp_level)
83 return self
84
85 def __exit__(self, etype:Maybe[type], err:Maybe[Exception], tb:Maybe[Traceback]) -> bool:
86 if bool(self._level_stack):
87 self._logger.setLevel(self._level_stack.pop())
88 else:
89 self._logger.setLevel(self._original_level)
90 return False
91
92 def __getattr__(self, key:str) -> Logger:
93 return cast("Logger", getattr(self._logger, key))
94
[docs]
95 def log(self, msg:str, *args:Any, **kwargs:Any) -> None: # noqa: ANN401
96 self._logger.log(self._temp_level, msg, *args, **kwargs)
97
[docs]
98class TempLogger:
99 """ For using a specific type of logger in a context, or getting
100 a custom logger class without changing it globally
101
102 use as:
103 with TempLogger(MyLoggerClass) as ctx:
104 # Either:
105 ctx['name'].info(...)
106 # or:
107 logmod.getLogger('name').info(...)
108 """
109 _target_cls : type[Logger]
110 _original : Maybe[type[Logger]]
111
112 def __init__(self, logger:type[Logger]) -> None:
113 self._target_cls = logger
114 self._original = None
115
116 def __enter__(self) -> Self:
117 self._original = logmod.getLoggerClass()
118 logmod.setLoggerClass(self._target_cls)
119 return self
120
121 def __exit__(self, etype:Maybe[type], err:Maybe[Exception], tb:Maybe[Traceback]) -> bool:
122 if self._original is not None:
123 logmod.setLoggerClass(self._original)
124
125 return False