# Copyright (c) Ye Liu. Licensed under the MIT License.
import logging
import sys
from termcolor import colored
from .path import abs_path, dir_name, mkdir
_COLOR_MAP = {
0: 'white',
10: 'cyan',
20: 'green',
30: 'yellow',
40: 'red',
50: 'magenta'
}
_CACHED_LOGGERS = []
class _Formatter(logging.Formatter):
def formatMessage(self, record):
log = super(_Formatter, self).formatMessage(record)
anchor = -len(record.message)
prefix = colored(log[:anchor], color=_COLOR_MAP[record.levelno])
return prefix + log[anchor:]
[docs]
def get_logger(logger_or_name=None,
fmt='[%(asctime)s %(levelname)s]: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
log_level=logging.INFO,
log_file=None):
"""
Initialize and get a logger by name.
If the logger has not been initialized, this method will initialize the
logger by adding one or two handlers, otherwise the initialized logger will
be directly returned. During initialization, a :obj:`StreamHandler` will
always be added. If ``log_file`` is specified and the process rank is
``0``, a :obj:`FileHandler` will also be added.
Args:
logger_or_name (:obj:`logging.Logger` | str | None, optional): The
logger or name of the logger. Default: ``None``.
fmt (str, optional): Logging format of the logger. The format must end
with ``'%(message)s'`` to make sure that the colors can be rendered
properly. Default: ``'[%(asctime)s %(levelname)s]: %(message)s'``.
datefmt (str, optional): Date format of the logger. Default:
``'%Y-%m-%d %H:%M:%S'``.
log_level (str | int, optional): Log level of the logger. Note that
only the main process (rank 0) is affected, and other processes
will set the level to ``ERROR`` thus be silent at most of the time.
Default: :obj:`logging.INFO`.
log_file (str | None, optional): Path to the log file. If specified, a
:obj:`FileHandler` will be added to the logger of the main process.
Default: ``None``.
Returns:
:obj:`logging.Logger`: The expected logger.
"""
if isinstance(logger_or_name, logging.Logger):
return logger_or_name
logger = logging.getLogger(logger_or_name)
if logger_or_name in _CACHED_LOGGERS:
return logger
_CACHED_LOGGERS.append(logger_or_name)
logger.setLevel(log_level)
logger.propagate = False
if not fmt.endswith('%(message)s'):
raise ValueError("fmt must end with '%(message)s'")
sh = logging.StreamHandler(stream=sys.stdout)
formatter = _Formatter(fmt=fmt, datefmt=datefmt)
sh.setFormatter(formatter)
logger.addHandler(sh)
try:
from nncore.engine import is_main_process
if not is_main_process():
logger.setLevel(logging.ERROR)
return logger
except ImportError:
pass
if log_file is not None:
mkdir(dir_name(abs_path(log_file)))
fh = logging.FileHandler(log_file, mode='w')
formatter = logging.Formatter(fmt=fmt, datefmt=datefmt)
fh.setFormatter(formatter)
logger.addHandler(fh)
return logger
[docs]
def log_or_print(msg, logger_or_name=None, log_level=logging.INFO, **kwargs):
"""
Print a message with a logger. If ``logger`` is a valid
:obj:`logging.Logger` or a name of the logger, then it would be used.
Otherwise this method will use the normal :obj:`print` function instead.
Args:
msg (str): The message to be logged.
logger_or_name (:obj:`logging.Logger` | str | None, optional): The
logger or name of the logger to use. Default: ``None``.
log_level (int, optional): Log level of the logger. Default:
:obj:`logging.INFO`.
"""
level = logging._checkLevel(log_level)
if isinstance(logger_or_name, logging.Logger):
logger_or_name.log(level, msg)
elif isinstance(logger_or_name, str):
logger = get_logger(logger_or_name, **kwargs)
logger.log(level, msg)
else:
if level > 20:
level_name = logging._levelToName[level]
msg = '{} {}'.format(
colored(level_name + ':', color=_COLOR_MAP[level]), msg)
print(msg)