mirror of
https://github.com/microsoft/qlib.git
synced 2026-06-29 17:11:20 +08:00
246 lines
6.6 KiB
Python
246 lines
6.6 KiB
Python
# Copyright (c) Microsoft Corporation.
|
|
# Licensed under the MIT License.
|
|
|
|
|
|
import logging
|
|
from typing import Optional, Text, Dict, Any
|
|
import re
|
|
from logging import config as logging_config
|
|
from time import time
|
|
from contextlib import contextmanager
|
|
|
|
from .config import C
|
|
|
|
|
|
class MetaLogger(type):
|
|
def __new__(cls, name, bases, dict):
|
|
wrapper_dict = logging.Logger.__dict__.copy()
|
|
for key in wrapper_dict:
|
|
if key not in dict and key != "__reduce__":
|
|
dict[key] = wrapper_dict[key]
|
|
return type.__new__(cls, name, bases, dict)
|
|
|
|
|
|
class QlibLogger(metaclass=MetaLogger):
|
|
"""
|
|
Customized logger for Qlib.
|
|
"""
|
|
|
|
def __init__(self, module_name):
|
|
self.module_name = module_name
|
|
self.level = 0
|
|
|
|
@property
|
|
def logger(self):
|
|
logger = logging.getLogger(self.module_name)
|
|
logger.setLevel(self.level)
|
|
return logger
|
|
|
|
def setLevel(self, level):
|
|
self.level = level
|
|
|
|
def __getattr__(self, name):
|
|
# During unpickling, python will call __getattr__. Use this line to avoid maximum recursion error.
|
|
if name in {"__setstate__"}:
|
|
raise AttributeError
|
|
return self.logger.__getattribute__(name)
|
|
|
|
|
|
def get_module_logger(module_name, level: Optional[int] = None) -> logging.Logger:
|
|
"""
|
|
Get a logger for a specific module.
|
|
|
|
:param module_name: str
|
|
Logic module name.
|
|
:param level: int
|
|
:return: Logger
|
|
Logger object.
|
|
"""
|
|
if level is None:
|
|
level = C.logging_level
|
|
|
|
module_name = "qlib.{}".format(module_name)
|
|
# Get logger.
|
|
module_logger = QlibLogger(module_name)
|
|
module_logger.setLevel(level)
|
|
return module_logger
|
|
|
|
|
|
class TimeInspector:
|
|
|
|
timer_logger = get_module_logger("timer", level=logging.WARNING)
|
|
|
|
time_marks = []
|
|
|
|
@classmethod
|
|
def set_time_mark(cls):
|
|
"""
|
|
Set a time mark with current time, and this time mark will push into a stack.
|
|
:return: float
|
|
A timestamp for current time.
|
|
"""
|
|
_time = time()
|
|
cls.time_marks.append(_time)
|
|
return _time
|
|
|
|
@classmethod
|
|
def pop_time_mark(cls):
|
|
"""
|
|
Pop last time mark from stack.
|
|
"""
|
|
return cls.time_marks.pop()
|
|
|
|
@classmethod
|
|
def get_cost_time(cls):
|
|
"""
|
|
Get last time mark from stack, calculate time diff with current time.
|
|
:return: float
|
|
Time diff calculated by last time mark with current time.
|
|
"""
|
|
cost_time = time() - cls.time_marks.pop()
|
|
return cost_time
|
|
|
|
@classmethod
|
|
def log_cost_time(cls, info="Done"):
|
|
"""
|
|
Get last time mark from stack, calculate time diff with current time, and log time diff and info.
|
|
:param info: str
|
|
Info that will be log into stdout.
|
|
"""
|
|
cost_time = time() - cls.time_marks.pop()
|
|
cls.timer_logger.info("Time cost: {0:.3f}s | {1}".format(cost_time, info))
|
|
|
|
@classmethod
|
|
@contextmanager
|
|
def logt(cls, name="", show_start=False):
|
|
"""logt.
|
|
Log the time of the inside code
|
|
|
|
Parameters
|
|
----------
|
|
name :
|
|
name
|
|
show_start :
|
|
show_start
|
|
"""
|
|
if show_start:
|
|
cls.timer_logger.info(f"{name} Begin")
|
|
cls.set_time_mark()
|
|
try:
|
|
yield None
|
|
finally:
|
|
pass
|
|
cls.log_cost_time(info=f"{name} Done")
|
|
|
|
|
|
def set_log_with_config(log_config: Dict[Text, Any]):
|
|
"""set log with config
|
|
|
|
:param log_config:
|
|
:return:
|
|
"""
|
|
logging_config.dictConfig(log_config)
|
|
|
|
|
|
class LogFilter(logging.Filter):
|
|
def __init__(self, param=None):
|
|
self.param = param
|
|
|
|
@staticmethod
|
|
def match_msg(filter_str, msg):
|
|
match = False
|
|
try:
|
|
if re.match(filter_str, msg):
|
|
match = True
|
|
except Exception:
|
|
pass
|
|
return match
|
|
|
|
def filter(self, record):
|
|
allow = True
|
|
if isinstance(self.param, str):
|
|
allow = not self.match_msg(self.param, record.msg)
|
|
elif isinstance(self.param, list):
|
|
allow = not any([self.match_msg(p, record.msg) for p in self.param])
|
|
return allow
|
|
|
|
|
|
def set_global_logger_level(level: int, return_orig_handler_level: bool = False):
|
|
"""set qlib.xxx logger handlers level
|
|
|
|
Parameters
|
|
----------
|
|
level: int
|
|
logger level
|
|
|
|
return_orig_handler_level: bool
|
|
return origin handler level map
|
|
|
|
Examples
|
|
---------
|
|
|
|
.. code-block:: python
|
|
|
|
import qlib
|
|
import logging
|
|
from qlib.log import get_module_logger, set_global_logger_level
|
|
qlib.init()
|
|
|
|
tmp_logger_01 = get_module_logger("tmp_logger_01", level=logging.INFO)
|
|
tmp_logger_01.info("1. tmp_logger_01 info show")
|
|
|
|
global_level = logging.WARNING + 1
|
|
set_global_logger_level(global_level)
|
|
tmp_logger_02 = get_module_logger("tmp_logger_02", level=logging.INFO)
|
|
tmp_logger_02.log(msg="2. tmp_logger_02 log show", level=global_level)
|
|
|
|
tmp_logger_01.info("3. tmp_logger_01 info do not show")
|
|
|
|
"""
|
|
_handler_level_map = {}
|
|
qlib_logger = logging.root.manager.loggerDict.get("qlib", None)
|
|
if qlib_logger is not None:
|
|
for _handler in qlib_logger.handlers:
|
|
_handler_level_map[_handler] = _handler.level
|
|
_handler.level = level
|
|
return _handler_level_map if return_orig_handler_level else None
|
|
|
|
|
|
@contextmanager
|
|
def set_global_logger_level_cm(level: int):
|
|
"""set qlib.xxx logger handlers level to use contextmanager
|
|
|
|
Parameters
|
|
----------
|
|
level: int
|
|
logger level
|
|
|
|
Examples
|
|
---------
|
|
|
|
.. code-block:: python
|
|
|
|
import qlib
|
|
import logging
|
|
from qlib.log import get_module_logger, set_global_logger_level_cm
|
|
qlib.init()
|
|
|
|
tmp_logger_01 = get_module_logger("tmp_logger_01", level=logging.INFO)
|
|
tmp_logger_01.info("1. tmp_logger_01 info show")
|
|
|
|
global_level = logging.WARNING + 1
|
|
with set_global_logger_level_cm(global_level):
|
|
tmp_logger_02 = get_module_logger("tmp_logger_02", level=logging.INFO)
|
|
tmp_logger_02.log(msg="2. tmp_logger_02 log show", level=global_level)
|
|
tmp_logger_01.info("3. tmp_logger_01 info do not show")
|
|
|
|
tmp_logger_01.info("4. tmp_logger_01 info show")
|
|
|
|
"""
|
|
_handler_level_map = set_global_logger_level(level, return_orig_handler_level=True)
|
|
try:
|
|
yield
|
|
finally:
|
|
for _handler, _level in _handler_level_map.items():
|
|
_handler.level = _level
|