日志

2023-01-12 更新

程序运行过程的监控非常重要,一旦出错,可以通过日志追溯问题原因。我们知道 print() 语句的作用,在开发时输出想要的信息,等开发后删除,简单有效,但是这种方法对于复杂的系统就力不从心了。所以,我们需要引入 Python 的日志模块 logging 。相较于 print() ,它的优势是,可以对消息进行级别过滤,并按特定格式输出到任意位置,包括终端、文本,甚至还能作为邮件内容发送到远程服务器。

time_str = time.strftime("%Y%m%d_%H%M%S", time.localtime())
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s %(levelname)s %(filename)s %(lineno)d %(message)s',
    filename='%s.log' % time_str
)

1 结构

在一个项目中,直接用 logging.getLogger() 调出来的都是 root logger 。 logging.getLogger() 还接受一个名称参数,名称与 python 模块格式一样用 . 隔开,表示父子关系,例如 logging.getLogger('parent.child') 获取的 logger 是 root logger 下的 parent logger 下的 child logger 。

2 级别

可供选择的过滤级别有 DEBUG 、 INFO 、 WARNING 、 ERROR 、 CRITICAL 五种,默认 WARNING 。

3 附属 handler filter formatter

如果说程序主体是油井,那么 logger 是油罐, handler 是输油管道,filter 是输油管道起点处的滤网, formatter 是输油管道终点处的仪表。一个油罐可接多条输油管道;每条输油管道可设多张滤网,但只装一只仪表。

这些附属都会被子 logger 继承,也会被子 logger 里的同名对象覆盖。

常用的 handler 有两个,一是用于标准输出的 StreamHandler ,二是用于文件输出的 FileHandler 。

logger = logging.getLogger()
format = "%(asctime)s %(levelname)s %(filename)s %(lineno)d %(message)s"

sh = logging.StreamHandler()
sh.setFormatter(logging.Formatter(format))
sh.setLevel(logging.DEBUG)

fh = logging.FileHandler('python.log')
fh.setFormatter(logging.Formatter(format))
fh.setLevel(logging.INFO)

logger.addHandler(sh)
logger.addHandler(fh)

4 示例

官网给出的示例运行报错 ConfigParser.NoSectionError: No section:'formatter'

原因竟然是, fileConfig 只接受绝对路径,不接受相对路径。由于 log 文件名通常自带时间,无法写死。勉强的解决方案是,放弃程序运行之初的少量 log ,直到生成具体时间创建 log 文件,再将剩余的 log 写入文件。

5 异常

logging.exception('...') 。换行问题就别管了。