kython.klogging

- move to core
- add a proper description why it's useful
- make default level INFO
- use HPI_LOGS variable for easier log level control (abdc6df1ea)
This commit is contained in:
Dima Gerasimov 2020-10-29 01:05:49 +00:00 committed by karlicoss
parent a946e23dd3
commit cc127f1876
5 changed files with 95 additions and 70 deletions

1
lint
View file

@ -42,7 +42,6 @@ def subpackages(package: str) -> Iterable[str]:
def core_modules() -> Iterable[str]:
return [
*subpackages('my.core'),
*subpackages('my.kython'),
'my.config',
'my.cfg',
'tests/misc.py',

View file

@ -111,7 +111,7 @@ def listify(fn=None, wrapper=list):
# return listify(fn=fn, wrapper=md)
from ..kython.klogging import setup_logger, LazyLogger
from .logging import setup_logger, LazyLogger
Paths = Union[Sequence[PathIsh], PathIsh]

91
my/core/logging.py Normal file
View file

@ -0,0 +1,91 @@
#!/usr/bin/env python3
'''
Default logger is a bit, see 'test'/run this file for a demo
'''
def test() -> None:
import logging
import sys
M = lambda s: print(s, file=sys.stderr)
M(" Logging module's deafults are not great...'")
l = logging.getLogger('test_logger')
l.error("For example, this should be logged as error. But it's not even formatted properly, doesn't have logger name or level")
M(" The reason is that you need to remember to call basicConfig() first")
logging.basicConfig()
l.error("OK, this is better. But the default format kinda sucks, I prefer having timestamps and the file/line number")
M("")
M(" With LazyLogger you get a reasonable logging format, colours and other neat things")
ll = LazyLogger('test') # No need for basicConfig!
ll.info("default level is INFO")
ll.debug(".. so this shouldn't be displayed")
ll.warning("warnings are easy to spot!")
ll.exception(RuntimeError("exceptions as well"))
import logging
from typing import Union, Optional
import os
Level = int
LevelIsh = Optional[Union[Level, str]]
def mklevel(level: LevelIsh) -> Level:
glevel = os.environ.get('HPI_LOGS', None)
if glevel is not None:
level = glevel
if level is None:
return logging.NOTSET
if isinstance(level, int):
return level
return getattr(logging, level.upper())
FORMAT = '{start}[%(levelname)-7s %(asctime)s %(name)s %(filename)s:%(lineno)d]{end} %(message)s'
FORMAT_COLOR = FORMAT.format(start='%(color)s', end='%(end_color)s')
FORMAT_NOCOLOR = FORMAT.format(start='', end='')
DATEFMT = '%Y-%m-%d %H:%M:%S'
def setup_logger(logger: logging.Logger, level: LevelIsh) -> None:
lvl = mklevel(level)
try:
import logzero # type: ignore[import]
except ModuleNotFoundError:
import warnings
warnings.warn("You might want to install 'logzero' for nice colored logs!")
logger.setLevel(lvl)
h = logging.StreamHandler()
h.setLevel(lvl)
h.setFormatter(logging.Formatter(fmt=FORMAT_NOCOLOR, datefmt=DATEFMT))
logger.addHandler(h)
logger.propagate = False # ugh. otherwise it duplicates log messages? not sure about it..
else:
formatter = logzero.LogFormatter(
fmt=FORMAT_COLOR,
datefmt=DATEFMT,
)
logzero.setup_logger(logger.name, level=lvl, formatter=formatter)
class LazyLogger(logging.Logger):
def __new__(cls, name, level: LevelIsh = 'INFO'):
logger = logging.getLogger(name)
# this is called prior to all _log calls so makes sense to do it here?
def isEnabledFor_lazyinit(*args, logger=logger, orig=logger.isEnabledFor, **kwargs):
att = 'lazylogger_init_done'
if not getattr(logger, att, False): # init once, if necessary
setup_logger(logger, level=level)
setattr(logger, att, True)
return orig(*args, **kwargs)
logger.isEnabledFor = isEnabledFor_lazyinit # type: ignore[assignment]
return logger
if __name__ == '__main__':
test()

View file

@ -6,10 +6,12 @@ from typing import Optional, NamedTuple, Iterable, Set, Tuple
import pytz
from ..core import warn_if_empty
from ..core import warn_if_empty, LazyLogger
from ..core.error import Res
logger = LazyLogger(__name__)
class Event(NamedTuple):
dt: datetime
summary: str
@ -23,8 +25,6 @@ Results = Iterable[Res[Event]]
@warn_if_empty
def merge_events(*sources: Results) -> Results:
from ..kython.klogging import LazyLogger
logger = LazyLogger(__name__)
from itertools import chain
emitted: Set[Tuple[datetime, str]] = set()
for e in chain(*sources):

View file

@ -1,65 +0,0 @@
#!/usr/bin/env python3
import logging
from typing import Union, Optional
Level = int
LevelIsh = Optional[Union[Level, str]]
def mklevel(level: LevelIsh) -> Level:
if level is None:
return logging.NOTSET
if isinstance(level, int):
return level
return getattr(logging, level.upper())
_FMT = '{start}[%(levelname)-7s %(asctime)s %(name)s %(filename)s:%(lineno)d]{end} %(message)s'
_FMT_COLOR = _FMT.format(start='%(color)s', end='%(end_color)s')
_FMT_NOCOLOR = _FMT.format(start='', end='')
def setup_logger(logger: logging.Logger, level: LevelIsh) -> None:
lvl = mklevel(level)
try:
import logzero # type: ignore
except ModuleNotFoundError:
import warnings
warnings.warn("You might want to install 'logzero' for nice colored logs!")
logger.setLevel(lvl)
h = logging.StreamHandler()
h.setLevel(lvl)
h.setFormatter(logging.Formatter(fmt=_FMT_NOCOLOR))
logger.addHandler(h)
else:
formatter = logzero.LogFormatter(
fmt=_FMT_COLOR,
datefmt=None, # pass None to prevent logzero from messing with date format
)
logzero.setup_logger(logger.name, level=lvl, formatter=formatter)
class LazyLogger(logging.Logger):
def __new__(cls, name, level: LevelIsh = 'DEBUG'):
logger = logging.getLogger(name)
# this is called prior to all _log calls so makes sense to do it here?
def isEnabledFor_lazyinit(*args, logger=logger, orig=logger.isEnabledFor, **kwargs):
att = 'lazylogger_init_done'
if not getattr(logger, att, False): # init once, if necessary
setup_logger(logger, level=level)
setattr(logger, att, True)
return orig(*args, **kwargs)
logger.isEnabledFor = isEnabledFor_lazyinit # type: ignore[assignment]
return logger
def test():
ll = LazyLogger('test')
ll.debug('THIS IS DEBUG')
ll.warning('THIS IS WARNING')
ll.exception(RuntimeError("oops"))
if __name__ == '__main__':
test()