Python-logging模块实现同时向控制台和文件打印日志
前言
在写我自己的练手项目的时候,需要写一系列Python脚本来帮助我进行运维/环境配置,我希望这些脚本能够有比较好的日志记录。
一方面我希望其能够直接打印到控制台,方便我实时查看,另一方面我也希望能够记录到日志文件中,这样能够方便我日后翻旧账。
更进一步,我希望控制台输出比较重要的信息,让我的控制台不至于太乱,而日志文件中我希望能够尽量详细。
参考
basicConfig
参考Python 官方文档 | logging模块#basicConfig
这个函数对logging模块的日志记录器进行基本配置,无论我们直接通过logging.info(msg)
进行日志记录,还是我们先通过logging.getLogger(name)
获取一个logger再进行日志记录,通过basicConfig
函数加入的配置都会生效。
值得注意的是,因为有些操作会导致basicConfig
函数被自动调用,所以我们尽量在程序开始时尽早进行调用。
我们需要关注的是basicConfig
的handlers
参数。
handler
如果说logger是面向上层开发者的接口,那么handler就是面向底层的执行者。开发者通过调用logger的方法来输出日志,而logger通过handler将日志进行实际的记录。
一个logger可以有若干handler,logger和handler都可以有一个日志等级。当记录等级高于logger的日志时,logger会通知每一个handler对该日志进行记录,而每一个handler记录会通过该条日志的等级和handler的等级进行比较,当日志等级高于handler等级的时候,才会进行记录。
handler主要位于logging.handler包下,我们这次需要使用StreamHandler
以及其子类FileHandler
。
实现
通过前面的分析,思路已经很清晰了:
- 创建两个handler,其中一个向控制台进行输出,一个向文件进行输出;
- 为这两个handler配置不同的日志等级和日志格式;
- 通过
basicConfig
函数来将这两个handler添加到全局日志的默认配置;
以下为实现:
import logging
import sys
def config_logging(file_name: str, console_level: int=logging.INFO, file_level: int=logging.DEBUG):
file_handler = logging.FileHandler(file_name, mode='a', encoding="utf8")
file_handler.setFormatter(logging.Formatter(
'%(asctime)s [%(levelname)s] %(module)s.%(lineno)d %(name)s:\t%(message)s')
)
file_handler.setLevel(file_level)
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(logging.Formatter(
'[%(asctime)s %(levelname)s] %(message)s',
datefmt="%Y/%m/%d %H:%M:%S")
)
console_handler.setLevel(console_level)
logging.basicConfig(
level=min(console_level, file_level),
handlers=[file_handler, console_handler],
)
if __name__ == '__main__':
config_logging("test.log", logging.WARNING, logging.DEBUG)
logging.debug("debug")
logging.info("info")
logging.warning("warning")
logging.critical("critical")
logger = logging.getLogger(__name__)
logger.debug("debug")
logger.info("info")
logger.warning("warning")
logger.critical("critical")
关键是config_logging
函数,这个函数进行了基本的配置,我们只需要在程序的入口处先调用这个函数就能完成全局的日志配置。
需要注意的是在调用logging.basicConfig
时,我们需要设置level=min(console_level, file_level)
,这是因为这个参数设置的是logger的等级,如果一条日志已经被logger过滤掉了,那么handler的等级设置的再低也不会进行记录。直接level=logging.DEBUG
、level=0
应该也可以,但是我认为比所有handler的等级都低的日志是不会进行输出的,与其让这条日志到了handler这一步才被过滤,不如直接就在logger这一步就将其过滤掉。