Source code for jgdv.logging.context

  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