You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

logger.py 12 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. # Copyright (c) OpenMMLab. All rights reserved.
  2. import logging
  3. import os
  4. import os.path as osp
  5. import sys
  6. from logging import Logger, LogRecord
  7. from typing import Optional, Union
  8. from termcolor import colored
  9. from .manager import ManagerMixin, _accquire_lock, _release_lock
  10. class FilterDuplicateWarning(logging.Filter):
  11. """Filter the repeated warning message.
  12. Args:
  13. name (str): name of the filter.
  14. """
  15. def __init__(self, name: str = "abl"):
  16. super().__init__(name)
  17. self.seen: set = set()
  18. def filter(self, record: LogRecord) -> bool:
  19. """Filter the repeated warning message.
  20. Args:
  21. record (LogRecord): The log record.
  22. Returns:
  23. bool: Whether to output the log record.
  24. """
  25. if record.levelno != logging.WARNING:
  26. return True
  27. if record.msg not in self.seen:
  28. self.seen.add(record.msg)
  29. return True
  30. return False
  31. class ABLFormatter(logging.Formatter):
  32. """Colorful format for ABLLogger. If the log level is error, the logger will
  33. additionally output the location of the code.
  34. Args:
  35. color (bool): Whether to use colorful format. filehandler is not
  36. allowed to use color format, otherwise it will be garbled.
  37. blink (bool): Whether to blink the ``INFO`` and ``DEBUG`` logging
  38. level.
  39. **kwargs: Keyword arguments passed to
  40. :meth:`logging.Formatter.__init__`.
  41. """
  42. _color_mapping: dict = dict(
  43. ERROR="red", WARNING="yellow", INFO="white", DEBUG="green"
  44. )
  45. def __init__(self, color: bool = True, blink: bool = False, **kwargs):
  46. super().__init__(**kwargs)
  47. assert not (
  48. not color and blink
  49. ), "blink should only be available when color is True"
  50. # Get prefix format according to color.
  51. error_prefix = self._get_prefix("ERROR", color, blink=True)
  52. warn_prefix = self._get_prefix("WARNING", color, blink=True)
  53. info_prefix = self._get_prefix("INFO", color, blink)
  54. debug_prefix = self._get_prefix("DEBUG", color, blink)
  55. # Config output format.
  56. self.err_format = (
  57. f"%(asctime)s - %(name)s - {error_prefix} - "
  58. "%(pathname)s - %(funcName)s - %(lineno)d - "
  59. "%(message)s"
  60. )
  61. self.warn_format = f"%(asctime)s - %(name)s - {warn_prefix} - %(" "message)s"
  62. self.info_format = f"%(asctime)s - %(name)s - {info_prefix} - %(" "message)s"
  63. self.debug_format = f"%(asctime)s - %(name)s - {debug_prefix} - %(" "message)s"
  64. def _get_prefix(self, level: str, color: bool, blink=False) -> str:
  65. """Get the prefix of the target log level.
  66. Args:
  67. level (str): log level.
  68. color (bool): Whether to get colorful prefix.
  69. blink (bool): Whether the prefix will blink.
  70. Returns:
  71. str: The plain or colorful prefix.
  72. """
  73. if color:
  74. attrs = ["underline"]
  75. if blink:
  76. attrs.append("blink")
  77. prefix = colored(level, self._color_mapping[level], attrs=attrs)
  78. else:
  79. prefix = level
  80. return prefix
  81. def format(self, record: LogRecord) -> str:
  82. """Override the `logging.Formatter.format`` method `. Output the
  83. message according to the specified log level.
  84. Args:
  85. record (LogRecord): A LogRecord instance represents an event being
  86. logged.
  87. Returns:
  88. str: Formatted result.
  89. """
  90. if record.levelno == logging.ERROR:
  91. self._style._fmt = self.err_format
  92. elif record.levelno == logging.WARNING:
  93. self._style._fmt = self.warn_format
  94. elif record.levelno == logging.INFO:
  95. self._style._fmt = self.info_format
  96. elif record.levelno == logging.DEBUG:
  97. self._style._fmt = self.debug_format
  98. result = logging.Formatter.format(self, record)
  99. return result
  100. class ABLLogger(Logger, ManagerMixin):
  101. """Formatted logger used to record messages.
  102. ``ABLLogger`` can create formatted logger to log message with different
  103. log levels and get instance in the same way as ``ManagerMixin``.
  104. ``ABLLogger`` has the following features:
  105. - Distributed log storage, ``ABLLogger`` can choose whether to save log of
  106. different ranks according to `log_file`.
  107. - Message with different log levels will have different colors and format
  108. when displayed on terminal.
  109. Note:
  110. - The `name` of logger and the ``instance_name`` of ``ABLLogger`` could
  111. be different. We can only get ``ABLLogger`` instance by
  112. ``ABLLogger.get_instance`` but not ``logging.getLogger``. This feature
  113. ensures ``ABLLogger`` will not be incluenced by third-party logging
  114. config.
  115. - Different from ``logging.Logger``, ``ABLLogger`` will not log warning
  116. or error message without ``Handler``.
  117. Examples:
  118. >>> logger = ABLLogger.get_instance(name='ABLLogger',
  119. >>> logger_name='Logger')
  120. >>> # Although logger has name attribute just like `logging.Logger`
  121. >>> # We cannot get logger instance by `logging.getLogger`.
  122. >>> assert logger.name == 'Logger'
  123. >>> assert logger.instance_name = 'ABLLogger'
  124. >>> assert id(logger) != id(logging.getLogger('Logger'))
  125. >>> # Get logger that do not store logs.
  126. >>> logger1 = ABLLogger.get_instance('logger1')
  127. >>> # Get logger only save rank0 logs.
  128. >>> logger2 = ABLLogger.get_instance('logger2', log_file='out.log')
  129. >>> # Get logger only save multiple ranks logs.
  130. >>> logger3 = ABLLogger.get_instance('logger3', log_file='out.log',
  131. >>> distributed=True)
  132. Args:
  133. name (str): Global instance name.
  134. logger_name (str): ``name`` attribute of ``Logging.Logger`` instance.
  135. If `logger_name` is not defined, defaults to 'abl'.
  136. log_file (str, optional): The log filename. If specified, a
  137. ``FileHandler`` will be added to the logger. Defaults to None.
  138. log_level (str): The log level of the handler. Defaults to
  139. 'INFO'. If log level is 'DEBUG', distributed logs will be saved
  140. during distributed training.
  141. file_mode (str): The file mode used to open log file. Defaults to 'w'.
  142. distributed (bool): Whether to save distributed logs, Defaults to
  143. false.
  144. """
  145. def __init__(
  146. self,
  147. name: str,
  148. logger_name="abl",
  149. log_file: Optional[str] = None,
  150. log_level: Union[int, str] = "INFO",
  151. file_mode: str = "w"
  152. ):
  153. Logger.__init__(self, logger_name)
  154. ManagerMixin.__init__(self, name)
  155. if isinstance(log_level, str):
  156. log_level = logging._nameToLevel[log_level]
  157. stream_handler = logging.StreamHandler(stream=sys.stdout)
  158. # `StreamHandler` record month, day, hour, minute, and second
  159. # timestamp.
  160. stream_handler.setFormatter(ABLFormatter(color=True, datefmt="%m/%d %H:%M:%S"))
  161. stream_handler.setLevel(log_level)
  162. stream_handler.addFilter(FilterDuplicateWarning(logger_name))
  163. self.handlers.append(stream_handler)
  164. if log_file is None:
  165. import time
  166. local_time = time.strftime("%Y%m%d_%H_%M_%S", time.localtime())
  167. save_dir = os.path.join("results", local_time)
  168. self.save_dir = save_dir
  169. if not os.path.exists(save_dir):
  170. os.makedirs(save_dir)
  171. log_file = osp.join(save_dir, local_time + ".log")
  172. file_handler = logging.FileHandler(log_file, file_mode)
  173. file_handler.setFormatter(
  174. ABLFormatter(color=False, datefmt="%Y/%m/%d %H:%M:%S")
  175. )
  176. file_handler.setLevel(log_level)
  177. file_handler.addFilter(FilterDuplicateWarning(logger_name))
  178. self.handlers.append(file_handler)
  179. self._log_file = log_file
  180. @property
  181. def log_file(self):
  182. return self._log_file
  183. @classmethod
  184. def get_current_instance(cls) -> "ABLLogger":
  185. """Get latest created ``ABLLogger`` instance.
  186. :obj:`ABLLogger` can call :meth:`get_current_instance` before any
  187. instance has been created, and return a logger with the instance name
  188. "abl".
  189. Returns:
  190. ABLLogger: Configured logger instance.
  191. """
  192. if not cls._instance_dict:
  193. cls.get_instance("abl")
  194. return super().get_current_instance()
  195. def callHandlers(self, record: LogRecord) -> None:
  196. """Pass a record to all relevant handlers.
  197. Override ``callHandlers`` method in ``logging.Logger`` to avoid
  198. multiple warning messages in DDP mode. Loop through all handlers of
  199. the logger instance and its parents in the logger hierarchy. If no
  200. handler was found, the record will not be output.
  201. Args:
  202. record (LogRecord): A ``LogRecord`` instance contains logged
  203. message.
  204. """
  205. for handler in self.handlers:
  206. if record.levelno >= handler.level:
  207. handler.handle(record)
  208. def setLevel(self, level):
  209. """Set the logging level of this logger.
  210. If ``logging.Logger.selLevel`` is called, all ``logging.Logger``
  211. instances managed by ``logging.Manager`` will clear the cache. Since
  212. ``ABLLogger`` is not managed by ``logging.Manager`` anymore,
  213. ``ABLLogger`` should override this method to clear caches of all
  214. ``ABLLogger`` instance which is managed by :obj:`ManagerMixin`.
  215. level must be an int or a str.
  216. """
  217. self.level = logging._checkLevel(level)
  218. _accquire_lock()
  219. # The same logic as `logging.Manager._clear_cache`.
  220. for logger in ABLLogger._instance_dict.values():
  221. logger._cache.clear()
  222. _release_lock()
  223. def print_log(
  224. msg, logger: Optional[Union[Logger, str]] = None, level=logging.INFO
  225. ) -> None:
  226. """Print a log message.
  227. Args:
  228. msg (str): The message to be logged.
  229. logger (Logger or str, optional): If the type of logger is
  230. ``logging.Logger``, we directly use logger to log messages.
  231. Some special loggers are:
  232. - "silent": No message will be printed.
  233. - "current": Use latest created logger to log message.
  234. - other str: Instance name of logger. The corresponding logger
  235. will log message if it has been created, otherwise ``print_log``
  236. will raise a `ValueError`.
  237. - None: The `print()` method will be used to print log messages.
  238. level (int): Logging level. Only available when `logger` is a Logger
  239. object, "current", or a created logger instance name.
  240. """
  241. if logger is None:
  242. print(msg)
  243. elif isinstance(logger, logging.Logger):
  244. logger.log(level, msg)
  245. elif logger == "silent":
  246. pass
  247. elif logger == "current":
  248. logger_instance = ABLLogger.get_current_instance()
  249. logger_instance.log(level, msg)
  250. elif isinstance(logger, str):
  251. # If the type of `logger` is `str`, but not with value of `current` or
  252. # `silent`, we assume it indicates the name of the logger. If the
  253. # corresponding logger has not been created, `print_log` will raise
  254. # a `ValueError`.
  255. if ABLLogger.check_instance_created(logger):
  256. logger_instance = ABLLogger.get_instance(logger)
  257. logger_instance.log(level, msg)
  258. else:
  259. raise ValueError(f"ABLLogger: {logger} has not been created!")
  260. else:
  261. raise TypeError(
  262. "`logger` should be either a logging.Logger object, str, "
  263. f'"silent", "current" or None, but got {type(logger)}'
  264. )

An efficient Python toolkit for Abductive Learning (ABL), a novel paradigm that integrates machine learning and logical reasoning in a unified framework.