logger: A Python artifact for managing logs

I want to open a new project recently, and I need a logger to manage the log. I will share it today. If you like it, remember to like, follow, and favorite.

[Note] An exchange and mutual aid group is provided at the end of the article

import logging
ori_logger = logging.getLogger('custom_logger')
ori_logger.setLevel(logging.INFO)
ori_logger.addHandler(logging.StreamHandler())
ori_logger.info('learn log')
# learn log

Emmm... The log feels ugly, NOT GOOD. Why don't you try mmcv's get_logger, it is fully functional and can be done in one step. Na~ take the link:

https://github.com/open-mmlab/mmcv/blob/master/mmcv/utils/logging.py

terminal log

mmcv_logger = get_logger('mmcv_logger', log_file='a.log')
mmcv_logger.info('learn log')
# 2022-03-24 10:38:35,998 - mmcv_logger - INFO - learn log

log file

2022-03-24 10:37:25,832 - mmcv_logger - INFO - learn log

So convenient! Timestamp, log name, log level, and even save log files for you, easily done with one line of code.

How does mmcv's get_logger configure such a convenient logger? You only need to configure the three treasures of logger: log level, formatter and log handler, so that logger will obey you.

logging log level ** **

The log levels of the logging module are as follows:

CRITICAL = 50        
FATAL = CRITICAL
ERROR = 40           
WARNING = 30        
WARN = WARNING
INFO = 20            
DEBUG = 10           
NOTSET = 0          

Logging.Logger (the logger in this article is an instance) and the level attribute of logging.Handler represent the log level of the corresponding instance. When we call logger.info(msg) and logger.warning(msg), the output message also has a log level. Only when the log level of the message is greater than or equal to logger.level, the log may be output. Take the logging.root we mentioned earlier as an example:

import logging

logger = logging.root
print(logger.level)  # 30, warning。root 日志等级为 WARNING (30)
logger.info("info msg")  # 日志等级为 INFO (20),小于 WARNING,无消息
logger.warning("warn msg")  # 日志等级为 WARNING (30),输出消息 warn msg
logger.error("erro msg")  # 日志等级为 ERROR (40),输出消息 erro msg
logger.setLevel(logging.ERROR) # 设置日志等级为 ERROR (40)
print(logger.level)  # 40, ERROR
logger.warning("warn msg")  # 无消息
logger.error("erro msg")  # erro msg

The log level of logging.root is WARNING, so INFO level messages cannot be output. When we adjust the log level to ERROR, it will not be able to output WARNING level messages. But the strange thing is that we mentioned in the first issue that logging.root is an embryo by default, and it does not have the ability to output logs. **Why can you still output warning and error level logs here? **Leave a question here, and the answer will be given in the Handler section.

logging 之 Handler ** **

Since the logging module outputs logs through Handler, this section will first introduce the two major Handlers of the logging module: streamHandler and fileHandler.

StreamHandler

output information to the terminal

The logger configured with StreamHandler can output log information to the terminal in the same way as print. The example is as follows:

logger = logging.root
# 创建 streamHandler
stream_handler = logging.StreamHandler()
# 设置日志等级
logger.setLevel(logging.INFO)
logger.info("learn logging")  # 没有配置 Handler,终端不会输出日志
# 为 root logger配置 Handler
logger.addHandler(stream_handler)
logger.info("learn logging")  #  配置 Handler,输出日志

Output log information to file

When instantiating a StreamHandler, the log can be stored to a file if the stream is configured to write to the target file.

f = open('output.txt', 'w')
logger = logging.root
logger.setLevel(logging.INFO)
stream_handler = logging.StreamHandler(stream=f)
logger.addHandler(stream_handler)
logger.warning("learn logging")  # 此时 learn logging 会被写入到 output.txt 中
f.close()

FileHandler

FileHandler inherits from StreamHandler and can specify the encoding format for writing files. Compared with StreamHandler, it is more flexible and easy to use:

logger = logging.root
logger.setLevel(logging.INFO)
# 设置输出文件和编码方式
file_handler = logging.FileHandler('output.txt', encoding='utf-8')
logger.addHandler(file_handler)
logger.info("WARNING")  # 此时 learn logging 会被写入到 output.txt 中

Handler's log level

Handlers also have their own log levels. For a message to be output through the Handler, the log level of the message needs to be greater than the log level of the logger and the log level of the handler.

picture

logger = logging.root
logger.setLevel(logging.DEBUG)  # logger 的日志等级为 DEBUG
handler = logging.StreamHandler()
handler.setLevel(logging.INFO)  # handler 的日志等级为 INFO
logger.addHandler(handler)
logger.debug('learn logging')  # 不满足 handler,无法被输出
logger.info('learn logging')  # 同时满足 logger 含 handler,正常输出

The behind-the-scenes operation of logging

Can log output without handler?

First of all, the conclusion is that the logger itself does not have the ability to output messages without a handler. The first example of streamHandler has already explained this problem. But why logger.warning(msg) and logger.error(msg) can output logs without configuring handler? This is actually the protection mechanism of the logging module . For messages of warning and error levels, if the log level of the message is greater than the log level of the logger, and the logger is not configured with any handler, the built-in streamHandler of the logging module will be called to output the information.

picture

In order to prove this logic, we configure a fileHandler for the logger. At this time, logger.warning(msg) will not output logs in the terminal.

logger = logging.root
logger.warning("learn logging")  # 输出日志到终端
file_handler = logging.FileHandler('output.txt', encoding='utf-8')
logger.addHandler(file_handler)
logger.warning("learn logging")  # 不会输出日志到终端

Crazy logging.xxx

When you call logging.info and logging.warning happily, you think you just output a log, but in fact, you may have secretly configured streamHandler for logging.root.

logging.info('learn logging')
print(logging.root.handlers)
# [<StreamHandler <stderr> (NOTSET)>]

You may be thinking, what's the point of adding a streamHandler. Remember the horror of being dominated by multiple logs? Why am I getting multiple logs all of a sudden using pytorch 1.10? Tracing the source is because the DistributedDataParallel module of pytorch 1.10 calls logging.info (https://github.com/pytorch/pytorch/blob/71f889c7d265b9636b93ede9d651c0a9c4bee191/torch/nn/parallel/distributed.py#L874) during the forward process, and then There is one more streamHandler in logging.root (this process happens after mmcv.get_logger, the log level of the handler is not set to ERROR), which eventually leads to the occurrence of multiple logs.

logging 之 Formatter

If configuring a handler is equivalent to teaching a logger to speak, then configuring a formatter for a handler is equivalent to teaching a logger to speak "elegantly".

Add a "subject" to the log

We can configure the formatter so that the log output by the logger has its own logger name.

logger = logging.root
handler = logging.StreamHandler()
handler.setFormatter(Formatter('%(name)s - %(message)s'))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
logger.info('learn logging')
# root - learn logging

In this way, the output log will have its own "root" log name.

add time to log

Primary school Chinese teachers must have taught that time, place, characters are the top three of the seven elements, and time is at the top of the list. Therefore, we can configure the formatter to allow the log to carry time information.

logger = logging.root
handler = logging.StreamHandler()
handler.setFormatter(Formatter('%(asctime)s - %(name)s - %(message)s'))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
logger.info('learn logging')
# 2022-03-22 01:36:42,900 - root - learn logging

add a rating to the log

The log level can highlight the importance of the log. For example, we will pay special attention to the log with the ERROR field. Therefore, we can let the log carry the level information through the formatter.

logger = logging.root
handler = logging.StreamHandler()
handler.setFormatter(Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
logger.info('learn logging')
# 2022-03-22 01:46:26,667 - root - INFO - learn logging

Hey, it smells like that. Is it the same as the logs of the OpenMMLab series? In fact, the process of mmcv configuring logger is similar, and there is a more comprehensive handler configuration logic: mmcv/logging.py at master · open-mmlab/mmcv (github.com), you can refer to it for reference.

summary

After understanding the concepts of loglevel, handler and formatter, we can also write a simplified version of the configuration logger function ourselves.

def custom_get_logger(name, out_file, log_level):
    # 设置日志名
    logger = logging.getLogger(name)
    # 设置日志登记
    logger.setLevel(log_level)
    # 设置格式
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s'
                                  ' - %(message)s')
    # 配置输出日志到终端的 Handler
    stream_handler = logging.StreamHandler()
    stream_handler.setFormatter(formatter)
    # 配置保存日志到文件的 Handler
    file_handler = logging.FileHandler(out_file)
    file_handler.setFormatter(formatter)
    # 添加 Handler
    logger.addHandler(file_handler)
    logger.addHandler(stream_handler)
    return logger

custom_logger = custom_get_logger('custom_logger', 'a.log', 'INFO')
custom_logger.info('learn log')
# 2022-03-24 11:18:52,120 - custom_logger - INFO - learn log

The logger obtained through the custom_get_logger interface has a good-looking log format and can also be stored in a local backup, which is basically aligned with mmcv. However, the logger configured in this way still has some hidden dangers, such as the multiple logs mentioned in the previous issue. To solve these problems, we might as well go back and look at the code of mmcv.get_logger. I believe that after this period of study, a lot of logic will become easier to understand.

So far, the basic functions of the logging module have been introduced, but there are still some implicit dark-box operations in logging. If you want to fully understand the logging module, please look forward to the content of the next issue~

recommended article

Technology Exchange

Welcome to reprint, collect, like and support!

insert image description here

At present, a technical exchange group has been opened, and the group has more than 2,000 members . The best way to remark when adding is: source + interest direction, which is convenient to find like-minded friends

  • Method 1. Send the following picture to WeChat, long press to identify, and reply in the background: add group;
  • Method ②, add micro-signal: dkl88191 , note: from CSDN
  • Method ③, WeChat search public account: Python learning and data mining , background reply: add group

long press follow

Guess you like

Origin blog.csdn.net/weixin_38037405/article/details/123745406