日志模块简介
运维工作有很多的情况需要查问题,解决 bug,而查问题和解决 bug 的过程离不开查看日志,我们编写脚本名或程序时总是需要有日志输出,而 Python 的 logging 模块就是为纪录日志使用的,而且是线程安全的,意味着使用它完全不用担心因日志模块的异常而导致程序崩溃。
示例1: 首先看一下日志模块的第一个例子,简单讲日志打印到屏幕:
1
2
3
4
5
6
7
8import logging
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')输出信息为:
1
2
3WARNING:root:warning message
ERROR:root:error message
CRITICAL:root:critical message默认情况下,Python 的 logging 模块将日志打印到标准输出中,而且只显示大于等于 WARNING 级别的日志,这说明默认的日志级别设置为 WARNING(日志级别等级 CRITICAL > ERROR > WARNING > INFO > DEBUG)。各日志级别代表的含义如下:
- DEBUG: 调试时的信息打印
- INFO: 正常的日志信息记录
- WARNING: 发生了警告信息,但程序仍能正常工作
- ERROR: 发生了错误,部分功能已不正常
- CRITICAL: 发生严重错误,程序可能已经崩溃
示例2: 将日志信息记录至文件
1
2
3
4
5
6
7
8
9import logging
logging.basicConfig(filename='./lx_log1.log')
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')执行以上代码后发现,在当前目录多了一个 lx_log1.log 文件,文件的内容与第一个例子的输出是一致的。多次执行发现 log 文件的内容变多了,说明默认的写 log 文件的方式是追加。
logging 模块的配置与使用
我们可以通过 logging 模块的配置改变 log 文件的写入方式,日志级别,时间戳等信息。例如下面的配置
1 | logging.basicConfig(level=logging.DEBUG, # 设置日志的级别 |
可见在 logging.basicConfig()
函数中可通过具体参数来更改 logging 模块的默认行为:
filename
: 用指定的文件名创建 FileHandler,这样日志会被存储在指定的文件中;filemode
: 文件的打开方式,在指定了 filename 时使用这个参数,默认值为a
,还可以指定为w
;format
: 指定 handler 使用的日志显示格式;datefmt
: 指定日期时间格式level
: 设置 rootlogger 的日志级别stream
: 用指定的 stream 创建 StreamHandler。可以指定输出到sys.stderr
,sys.stdout
或者文件 ,默认为sys.stderr
。若同时列出了 filename 和 stream 两个参数,则 stream 参数会被忽略。
format 参数中可能用到的格式化串如下:
%(name)s
: Logger 的名字;%(levelno)s
: 数字形式的日志级别;%(levelname)s
: 文本形式的日志级别;%(pathname)s
: 调用日志输出函数的模块的完整路径名(可能没有);%(filename)s
: 调用日志输出函数的模块的文件名;%(module)s
: 调用日志输出函数的模块名;%(funcName)s
: 调用日志输出函数的函数名;%(lineno)d
: 调用日志输出函数的语句所在的代码行;%(created)f
: 当前时间,用 UNIX 标准表示时间的浮点数;%(relativeCreated)d
: 输出日志信息时,自 Logger 创建以来的毫秒数;%(asctime)s
: 字符串形式的当前时间。默认格式为 “2013-07-08 16:49:45,896”。逗号后面的是毫秒;%(thread)s
: 线程 ID,可能没有;%(threadName)s
: 线程名,可能没有;%(process)d
: 进程 ID,可能没有;%(message)s
: 用户输出的信息;
示例1: 例如以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13import logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
datefmt=' %Y-%m-%d %H:%M:%S',
filename='./lx_log1.log',
filemode='w')
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')运行代码后,我们可以看到生成的 lx_log1.log 文件内容如下:
1
2
3
4
52023-03-23 10:33:08 lx_log1.py[line:18] DEBUG debug message
2023-03-23 10:33:08 lx_log1.py[line:19] INFO info message
2023-03-23 10:33:08 lx_log1.py[line:20] WARNING warning message
2023-03-23 10:33:08 lx_log1.py[line:21] ERROR error message
2023-03-23 10:33:08 lx_log1.py[line:22] CRITICAL critical message
这样的配置基本已经满足我们写一些小程序或 Python 脚本的日志需求。然而这还不够体现 logging 模块的强大,毕竟以上功能通过自定义一个函数也可以方便实现。下面介绍几个概念以及它们之间的关系图
logger
: 记录器,应用程序代码能直接使用的接口handler
: 处理器,将(记录器产生的)日志记录发送至合适的目的地;filter
: 过滤器,提供了更好的粒度控制,可以决定输出哪些日志记录;formatter
: 格式化器,指明了最终输出日志中日志记录的布局;
日志事件信息在记录器(logger)、处理器(handler)、过滤器(filter)、格式化器(formatter) 之间通过一个日志记录实例来传递。通过调用记录器(logger)实例的方法来记录日志,每一个记录器实例都有一个名字,名字相当于其命名空间,是一个树状结构。
例如: 一个记录器叫 scan,记录器 scan.text、scan.html、scan.pdf 的父节点。记录器的名称可以任意取,但一个比较好的实践是通过下面的方式来命名一个记录器
1
logger = logging.getLogger(__name__)
上面这条语句意味着记录器的名字会通过搜索包的层级来获取,根记录器叫 root logger。
记录器通过 debug()
、info()
、warning()
、error()
和 critical()
方法记录相应级别的日志,根记录器也一样。
根记录器(root) logger 输出的名称是 ‘root’。当然,日志的输出位置可能是不同的,logging 模块支持将日志信息输出到终端、文件、HTTP GET/POST 请求、邮件、网络 socket、队列或操作系统的日志等。日志的输出位置在处理器 handler 类中进行配置,如果内建的 handler 类无法满足需求,则可以自定义 handler 类来实现自己特殊的需求。
默认情况下,日志的输出位置为终端(标准错误输出),可以通过 logging 模块的 basicConfig() 方法指定一个具体的位置来输出日志,如终端或文件.
现在让我们从整体到局部来说明 Logger 的日志记录过程:
第一步: 获取 logger 的名称
1
logger = logging.getLogger('logger nane') # 这里的 logger name 是自己定义的
配置 logger
- 配置该 logger 的输出级别,如 logger.setLevel(loggin.INFO)
- 添加该 logger 的输出位置,即 logger 的 handler,如 logger.addHandler(ch)。这里的 ch 是我们自定义的 handler,如
ch=logging.StreamHandler
,即输出到终端。我们可以添加多个 handler,一次性将日志输出到不同的位置。日志的输出格式是在 handler 中进行配置,如 ch.setFormatter(formatter),formatter 也是我们自定义的,如formatter=logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
。不同的 handler 可以配置不同的格式化器(formatter),可以实现不同的输出位置,不同的输出格式,完全可能灵活配置。
在应用程序中记录日志,如下
1
2
3
4
5logger.debug('debug message')
logger.info('info message')
logger.warning('warning message')
logger.error('error message')
logger.critical('critical message')
示例
示例1: 将日志信息显示在终端的同时也在文件中记录
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
30
31
32
33
34import logging
# 创建 logger,其名称为 simple_example,名称为任意,也可以为空
logger = logging.getLogger("simple_example")
# 打印 logger 的名称
print(logger.name)
# 设置 logger 的日志级别
logger.setLevel(logging.INFO)
# 创建两个 handler,一个负责将日志输出到终端,一个负责输出到文件,并分别设置他们的日志级别
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
fh = logging.FileHandler(filename='simple.log', mode='a', encoding='utf-8')
fh.setLevel(logging.WARNING)
# 创建一个格式化器,可以创建不同的格式化器用于不同的 handler,这里我们使用一个
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
# 设置两个 handler 的格式化器
ch.setFormatter(formatter)
fh.setFormatter(formatter)
# 为 logger 添加两个 handler
logger.addHandler(ch)
logger.addHandler(fh)
# 在程序中记录日志
logger.debug('debug message')
logger.info('info message')
logger.warning('warning message')
logger.error('error message')
logger.critical('critical message')在以上程序中我们设置了 logger 的日志级别为 INFO,handler ch 的日志级别为 DEBUG,handler fh 的日志级别为 WARNING,这样做是为了解释他们之间的优先级。
handler 的日志级别以 logger 的日志级别为基础,logger 的日志级别为 INFO,低于 INFO 级别的(如 DEBUG)均不会在 handler 中出现。
- handler 中的日志级别如果高于 logger 的日志级别,则只显示更高级别的日志信息,如 fh 应该只显示 WARNING 及以上的日志信息;
- handler 中的日志级别如果低于或等于 logger 的日志级别,则显示 logger 的日志级别及以上信息,如 ch 应该显示 INFO 及以上的日志信息.
下面运行程序进行验证,执行以上程序代码,得到如下结果
1
2
3
4
5
6
7
8
9
10
11# 控制台输出信息如下
simple_example
2023-03-23 11:54:43,732 - simple_example - INFO - info message
2023-03-23 11:54:43,732 - simple_example - WARNING - warning message
2023-03-23 11:54:43,732 - simple_example - ERROR - error message
2023-03-23 11:54:43,732 - simple_example - CRITICAL - critical message
# 日志文件 simple.log 内容如下
2023-03-23 11:53:32,403 - simple_example - WARNING - warning message
2023-03-23 11:53:32,403 - simple_example - ERROR - error message
2023-03-23 11:53:32,403 - simple_example - CRITICAL - critical message示例2: 日志的配置信息也可以放在配置文件中,如下
- lx_log.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14import logging
import logging.config
logging.config.fileConfig('logging.conf')
# 创建一个 logger
logger = logging.getLogger('simpleExample')
# 日志记录信息
logger.debug('debug message')
logger.info('info message')
logger.warning('warning message')
logger.error('error message')
logger.critical('critical message')- 配置文件 logging.conf
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[loggers]
keys = root,simpleExample
[handlers]
keys = consoleHandler
[formatters]
keys = simpleFormatter
[logger_root]
level = DEBUG
handlers = consoleHandler
[logger_simpleExample]
level = DEBUG
handlers = consoleHandler
qualname = simpleExample
propagate = 0
[handler_consoleHandler]
class = StreamHandler
level = DEBUG
formatter = simpleFormatter
args = (sys.stdout,)
[formatter_simpleFormatter]
format = %(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt = %Y-%m-%d %H:%M:%S
其他类型的 Handler
从运行结果来看,符合我们的预期。除了 StreamHandler 和 FileHandler 外,logging 模块还提供了其他更为实用的 Handler 子类,它们都继承在 Handler 基类,如下所示
- BaseRotatingHandler: 是循环日志处理器的基类,不能直接被实例化,可使用
RotatingFileHandler
和TimedRotatingFileHandler
- RotatingFileHandler: 将日志文件记录至磁盘文件,可以设置每个诶之文件的最大占用空间
- TimedRotatingFileHandler: 将日志文件记录至磁盘文件,按固定的时间间隔来循环记录日志
- SocketHandler: 可以将日志信息发送到 TCP/IP 套接字;
- DatagramHandler: 可以将日志信息发送到 UDP 套接字;
- SMTPHandler: 可以将日志文件发送到邮箱;
- SysLogHandler: 系统日志处理器,可以将日志发送至 UNIX 系统日志,也可以是一个远程机器;
- NTEventLogHandler: Windows 系统事件日志处理器,可以将日志文件发送到 Windows 系统事件日志;
- MemoryHandler: MemoryHandler 实例向内存中的缓冲区发送消息,只要满足特定的条件,缓冲区就会被刷新;
- HTTPHandler: 使用 GET 或 POST 方法向 HTTP 服务器发送消息;
- WatchedFileHandler: WatchedFileHandler 实例监听它们登录到的文件。如果文件发生更改,则使用文件名关闭并重新打开。这个处理器只适用于 UNIX 系统,Windows 系统不支持使用的底层机制;
- QueueHandler: QueueHandler 实例向队列发送消息,比如在队列或多处理模块中实现的消息;
- NullHandler: NullHandler 实例不使用错误消息。库开发人员使用日志记录,但希望避免在库用户未配置日志记录时显示 “日志记录器 XXX 无法找到任何处理程序” 消息。