Python日志模块-logging

logging

相关概念

首先的概念是日志等级logging level。在logging中有如下几个日志等级,同时每个日志等级对应一个整数:NOTSET=0,DEBUG=10, INFO=20,WARN=30,ERROR=40, and CRITICAL=50。注意这里的NOTSET,严格来说它并不是一个日志等级,它的作用与真实场景有关,实际起作用的日志等级与它的父日志器有关。

第二个概念是日志格式化器logging formatter,它是用来指定我们日志的记录格式的。在这里我们可以添加程序的上下文信息,包括程序执行时间,所在line number,方法名等等,全部可以使用的上下文信息可以参考官方文档:python-logging #LogRecord attributes。我们原始的日志信息会经过格式化器formatter形成新的字符串格式。

第三个概念是日志处理器logging handler,同它的名称一样,这是用来处理我们的日志的。当我们的原始日志信息经过格式化器之后,实际上得到的还是字符串,要如何处理这些字符串,就是handler完成的事情。例如,将日志信息直接打印是一种处理方式,将日志信息持久化到文件中是一种处理方式,甚至我们可以将日志信息发送到其他机器上等等。每个日志处理器都会包含两个重要的属性,一个是日志formatter,用于完成日志的格式化,另一个是logging level,代表该handler的日志等级,低于设置日志等级的日志是不会被记录的。在Python中已经提供了一些封装好的handler,例如用于将日志输出到流当中的StreamHandler,以及将日志保存到文件中的FileHandler.

第四个概念是日志记录器logger,它就是我们用于日志记录的对象,也就是我们在程序中直接使用的对象。如果我们需要在某个地方进行日志记录的话,就调用该对象的相关方法,例如info/debug/error等,用于表示当前这条日志的等级同时进行日志记录。

每个logger可以用一个字符串进行标识,我们可以使用字符串来获取一个日志记录器。相同的字符串得到的日志记录器也是相同的。同时logger的标识字符串使用.来表示层级关系。表示一个从根日志记录器开始的继承关系。不过需要注意的是,层级关系是无法递归构建出日志记录器的。也就是说,在下面的例子中,如果我们没有a logger的话,a.b logger则表示一个没有层级的日志记录器,它的名称就是a.b。每个日志记录器有三个重要属性,第一个是propagate,表示是否将日志传播到父级logger中,默认为true;第二个是日志等级,同样用于过滤较低等级的日志;第三个是handler日志处理器。

1
2
3
4
5
logging.getLogger("a")
# same logger if their name is the same
assert id(logging.getLogger("a")) == id(logging.getLogger("a"))

logging.getLogger("a.b")

使用实践

虽然我们可以直接使用类似于logging.info("xxx")的方式来输出日志,但是不推荐这样使用。实际上,我们可以事先准备好一个获取日志记录器Logger的py文件,在其中暴露一些方法来统一获取Logger,后面在需要的时候直接进行日志输出即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import logging
import sys
from logging.handlers import TimedRotatingFileHandler

FORMATTER = logging.Formatter("%(asctime)s %(levelname)s in %(filename)s,%(funcName)s,line%(lineno)s "
"[%(processName)s-%(process)d-%(threadName)s-%(thread)d][%(name)s] %(message)s")
LOG_FILE = "my_app.log"


def get_console_handler():
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(FORMATTER)
return console_handler


def get_file_handler(logging_path=LOG_FILE):
file_handler = TimedRotatingFileHandler(logging_path, when='midnight')
file_handler.setFormatter(FORMATTER)
return file_handler


def get_logger(logger_name, logging_level=logging.DEBUG, logging_path=LOG_FILE, propagate=True):
logger = logging.getLogger(logger_name)
logger.setLevel(logging_level) # better to have too much log than not enough
logger.addHandler(get_console_handler())
logger.addHandler(get_file_handler(logging_path))
logger.propagate = propagate
return logger

之后我们就可以在需要使用的地方调用,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
from mylogger import get_logger


def test_logging():
logger = get_logger("test-logger")
logger.info("test info")
logger.debug("test debug")
logger.warning("test warning")


if __name__ == '__main__':
test_logging()

于是可以得到如下输出:

1
2
3
2023-04-03 14:52:04,443 INFO in main.py,test_logging,line8 [MainProcess-89912-MainThread-4559111680][test-logger] test info
2023-04-03 14:52:04,443 DEBUG in main.py,test_logging,line9 [MainProcess-89912-MainThread-4559111680][test-logger] test debug
2023-04-03 14:52:04,443 WARNING in main.py,test_logging,line10 [MainProcess-89912-MainThread-4559111680][test-logger] test warning

参考文章

  1. Python Logging: An In Depth Tutorial

Python日志模块-logging
http://example.com/2023/04/03/Python日志模块-logging/
作者
EverNorif
发布于
2023年4月3日
许可协议