关于logging模块的基本使用参考Python之日志处理(logging模块),这里主要记录使用logging时踩过的坑。
1. 使用pytorch的DDP进行多卡训练时如何保证只有主进程将信息发送到控制台和日志文件中呢?
logging.getLogger()方法有一个可选参数name,该参数表示将要返回的日志器的名称标识,如果不提供该参数,则其值为'root',得到的就是RootLogger
- logger的名称是一个以'.'分割的层级结构,每个'.'后面的logger都是'.'前面的logger的children,例如,有一个名称为 foo 的logger,其它名称分别为 foo.bar, foo.bar.baz 和 foo.bam都是 foo 的后代。
- logger有一个"有效等级(effective level)"的概念。如果一个logger上没有被明确设置一个level,那么该logger就是使用它parent的level;如果它的parent也没有明确设置level则继续向上查找parent的parent的有效level,依次类推,直到找到个一个明确设置了level的祖先为止。需要说明的是,root logger总是会有一个明确的level设置(默认为 WARNING)。当决定是否去处理一个已发生的事件时,logger的有效等级将会被用来决定是否将该事件传递给该logger的handlers进行处理。
- child loggers在完成对日志消息的处理后,默认会将日志消息传递给与它们的祖先loggers相关的handlers。因此,我们不必为一个应用程序中所使用的所有loggers定义和配置handlers,只需要为一个顶层的logger配置handlers,然后按照需要创建child loggers就可足够了。我们也可以通过将一个logger的propagate属性设置为False来关闭这种传递机制。
综上,我们可以在程序开始时设定RootLogger的信息,后面的logger除了名字外,不指定任何参数就可以继承RootLogger的设置,包括level、handler、formatter等,以下是参考了maskrcnn-benckmark对logger进行设置的函数
def setup_root_logger(save_dir, distributed_rank, filename="log.txt"):
# 只有主进程进行log输出
if distributed_rank > 0:
return
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler(stream=sys.stdout)
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s: %(message)s")
ch.setFormatter(formatter)
root_logger.addHandler(ch)
if save_dir:
save_dir = Path(save_dir) if not isinstance(save_dir, Path) else save_dir
fh = logging.FileHandler(save_dir / filename, mode='w')
fh.setLevel(logging.DEBUG)
fh.setFormatter(formatter)
2、还要注意一点,可能在其它的库程序中会直接使用函数logging.info()进行输出。logging模块级别的函数所使用的日志器是RootLogger
类的实例,当我们没有提供任何配置信息的时候,这些函数都会去调用logging.basicConfig(**kwargs)
方法,将RootLogger日志输出位置默认为:sys.stderr,因此即便没有为事先声明的logger指定输出流,调用
logging.info()后也会让使得所有的logger将信息输出到控制台
看一下这个demo就明白了:
import logging
>>> root_logger = logging.getLogger('')
>>> root_logger.setLevel(logging.DEBUG)
>>> a = logging.getLogger('a')
>>> root_logger.info('logger root')
并没有输出,这是由于没有指定root_logger的输出流位置
>>> a.info('logger a')
并没有输出,这是由于没有指定a的输出流位置
>>> logging.info('test')
INFO:root:test
>>> root_logger.info('root logger')
INFO:root:root logger # logging.info()将RootLogger的输出指定到了控制台
>>> a.logger('logger a')
INFO:a:logger a