Python 日志记录
最后修改于 2024 年 1 月 29 日
Python 日志记录教程展示了如何使用 logging 模块在 Python 中进行日志记录。
日志记录
日志记录是将信息写入日志文件的过程。日志文件包含有关操作系统、软件或通信中发生的各种事件的信息。
日志记录的目的
日志记录的目的如下:
- 信息收集
- 故障排除
- 生成统计数据
- 审计
- 性能分析
日志记录不仅限于识别软件开发中的错误。它还用于检测安全事件、监控策略违规、在出现问题时提供信息、查找应用程序瓶颈或生成使用数据。
记录哪些事件
应记录的事件包括输入验证失败、身份验证和授权失败、应用程序错误、配置更改以及应用程序启动和关闭。
不记录哪些事件
不应记录的事件包括应用程序源代码、会话标识值、访问令牌、敏感个人数据、密码、数据库连接字符串、加密密钥、银行账户和持卡人数据。
日志记录最佳实践
以下是一些日志记录的最佳实践:
- 日志记录应有意义。
- 日志记录应包含上下文。
- 日志记录应结构化并在不同级别进行。
- 日志记录应平衡;它不应包含过少或过多的信息。
- 日志消息应易于人类理解且可被机器解析。
- 在更复杂的应用程序中,应将日志记录到多个日志文件中。
- 日志记录应适应开发和生产环境。
logging 模块
Python 的 logging
模块定义了用于实现应用程序和库的灵活事件日志记录系统的函数和类。
logging 模块的组件
logging 模块有四个主要组件:日志记录器 (loggers)、处理器 (handlers)、过滤器 (filters) 和格式化器 (formatters)。日志记录器公开应用程序代码直接使用的接口。处理器将日志记录 (由日志记录器创建) 发送到适当的目的地。过滤器提供更精细的功能来确定输出哪些日志记录。格式化器指定最终输出中日志记录的布局。
Python 日志记录层次结构
Python 日志记录器形成一个层次结构。名为 main
的日志记录器是 main.new
的父级。
子日志记录器会将消息传播到与其祖先日志记录器关联的处理器。因此,不必为应用程序中的所有日志记录器定义和配置处理器。只需为顶级日志记录器配置处理器,并根据需要创建子日志记录器即可。
Python 日志记录级别
级别用于识别事件的严重性。有六个日志记录级别:
- CRITICAL
- ERROR
- WARNING
- INFO
- DEBUG
- NOTSET
如果日志记录级别设置为 WARNING
,则所有 WARNING
、ERROR
和 CRITICAL
消息都会写入日志文件或控制台。如果设置为 ERROR
,则只记录 ERROR
和 CRITICAL
消息。
日志记录器具有有效级别的概念。如果日志记录器上没有明确设置级别,则使用其父级的级别作为其有效级别。如果父级没有设置显式级别,则检查其父级,依此类推——搜索所有祖先,直到找到显式设置的级别。
当使用 getLogger
创建日志记录器时,级别设置为 NOTSET
。如果日志记录级别未通过 setLevel
显式设置,则消息将传播到日志记录器父级。日志记录器的祖先日志记录器链被遍历,直到找到级别不是 NOTSET
的祖先,或者达到根日志记录器。根日志记录器默认设置为 WARNING
级别。
根日志记录器
所有日志记录器都是根日志记录器的后代。每个日志记录器都会将其日志消息传递给其父级。新日志记录器使用 getLogger(name)
方法创建。调用不带名称的函数 (getLogger
) 会返回根日志记录器。
根日志记录器始终具有显式设置的级别,默认为 WARNING
。
根日志记录器位于层次结构的顶部,并且始终存在,即使未配置。通常,程序或库不应直接针对根日志记录器进行日志记录。相反,应为程序配置一个特定的日志记录器。根日志记录器可用于轻松打开和关闭所有库的所有日志记录器。
Python 日志记录简单示例
logging
模块具有简单的函数,无需任何配置即可立即使用。这可用于简单的日志记录。
#!/usr/bin/python import logging logging.debug('This is a debug message') logging.info('This is an info message') logging.warning('This is a warning message') logging.error('This is an error message') logging.critical('This is a critical message')
该示例调用了 logging
模块的五个函数。消息被写入控制台。
$ simple.py WARNING:root:This is a warning message ERROR:root:This is an error message CRITICAL:root:This is a critical message
请注意,使用了根日志记录器,并且只写入了三条消息。这是因为默认情况下,只写入警告及以上级别的消息。
Python 设置日志记录级别
日志记录级别使用 setLevel
设置。它将此日志记录器的阈值设置为 lvl
。严重性低于 lvl
的日志消息将被忽略。
#!/usr/bin/python import logging logger = logging.getLogger('dev') logger.setLevel(logging.DEBUG) logger.debug('This is a debug message') logger.info('This is an info message') logger.warning('This is a warning message') logger.error('This is an error message') logger.critical('This is a critical message')
在示例中,我们将日志记录级别更改为 DEBUG
。
logger = logging.getLogger('dev')
getLogger
返回具有指定名称的日志记录器。如果名称为 None
,它将返回根日志记录器。名称可以是定义日志记录层次结构的点分隔字符串;例如 'a'、'a.b' 或 'a.b.c'。请注意,存在一个隐式的根名称,但未显示。
$ set_level.py This is a warning message This is an error message This is a critical message
现在所有消息都已写入。
Python 有效日志记录级别
有效日志记录级别是显式设置的级别或从日志记录器父级确定的级别。
#!/usr/bin/python import logging main_logger = logging.getLogger('main') main_logger.setLevel(5) dev_logger = logging.getLogger('main.dev') print(main_logger.getEffectiveLevel()) print(dev_logger.getEffectiveLevel())
在示例中,我们检查了两个日志记录器的有效日志记录级别。
dev_logger = logging.getLogger('main.dev')
dev_logger
的级别未设置;然后使用其父级的级别。
$ effective_level.py 5 5
Python 日志记录处理器
处理器是一个负责将适当的日志消息(根据日志消息的严重性)分派到处理器指定的目的地的对象。
处理器像级别一样被传播。如果日志记录器没有设置处理器,则会搜索其祖先链来查找处理器。
#!/usr/bin/python import logging logger = logging.getLogger('dev') logger.setLevel(logging.INFO) fileHandler = logging.FileHandler('test.log') fileHandler.setLevel(logging.INFO) consoleHandler = logging.StreamHandler() consoleHandler.setLevel(logging.INFO) logger.addHandler(fileHandler) logger.addHandler(consoleHandler) logger.info('information message')
该示例为日志记录器创建了两个处理器:一个文件处理器和一个控制台处理器。
fileHandler = logging.FileHandler('test.log')
FileHandler
将日志记录发送到 test.log
文件。
consoleHandler = logging.StreamHandler()
StreamHandler
将日志记录发送到流。如果未指定流,则使用 sys.stderr
。
logger.addHandler(fileHandler)
使用 addHandler
将处理器添加到日志记录器。
Python 日志记录格式化器
格式化器是一个对象,它配置日志记录的最终顺序、结构和内容。除了消息字符串之外,日志记录还包括日期和时间、日志名称以及日志级别严重性。
#!/usr/bin/python import logging logger = logging.getLogger('dev') logger.setLevel(logging.INFO) consoleHandler = logging.StreamHandler() consoleHandler.setLevel(logging.INFO) logger.addHandler(consoleHandler) formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s') consoleHandler.setFormatter(formatter) logger.info('information message')
该示例创建一个控制台日志记录器,并向其处理器添加一个格式化器。
formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s')
创建了格式化器。它包括日期时间、日志记录器名称、日志记录级别名称和日志消息。
consoleHandler.setFormatter(formatter)
使用 setFormatter
将格式化器设置为处理器。
$ formatter.py 2019-03-28 14:53:27,446 dev INFO: information message
控制台显示了具有已定义格式的消息。
Python 日志记录 basicConfig
basicConfig
配置根日志记录器。它通过创建具有默认格式化器的流处理器来对日志记录系统进行基本配置。如果根日志记录器没有定义任何处理器,debug
、info
、warning
、error
和 critical
会自动调用 basicConfig
。
#!/usr/bin/python import logging logging.basicConfig(filename='test.log', format='%(filename)s: %(message)s', level=logging.DEBUG) logging.debug('This is a debug message') logging.info('This is an info message') logging.warning('This is a warning message') logging.error('This is an error message') logging.critical('This is a critical message')
该示例使用 basicConfig
配置根日志记录器。
logging.basicConfig(filename='test.log', format='%(filename)s: %(message)s', level=logging.DEBUG)
使用 filename
,我们设置写入日志消息的文件。format
决定了写入文件的内容;我们有文件名和消息。使用 level
,我们设置日志记录阈值。
$ basic_config.py $ cat test.log basic_config.py: This is a debug message basic_config.py: This is an info message basic_config.py: This is a warning message basic_config.py: This is an error message basic_config.py: This is a critical message
运行程序后,test.log
文件中写入了五个消息。
Python 日志记录 fileConfig
fileConfig
从 configparser 格式的文件读取日志记录配置。
[loggers] keys=root,dev [handlers] keys=consoleHandler [formatters] keys=extend,simple [logger_root] level=INFO handlers=consoleHandler [logger_dev] level=INFO handlers=consoleHandler qualname=dev propagate=0 [handler_consoleHandler] class=StreamHandler level=INFO formatter=extend args=(sys.stdout,) [formatter_extend] format=%(asctime)s - %(name)s - %(levelname)s - %(message)s [formatter_simple] format=%(asctime)s - %(message)s
log.conf
定义了一个日志记录器、一个处理器和一个格式化器。
#!/usr/bin/python import logging import logging.config logging.config.fileConfig(fname='log.conf') logger = logging.getLogger('dev') logger.info('This is an information message')
该示例从 log.conf
读取日志记录配置文件。
$ file_config.py 2019-03-28 15:26:31,137 - dev - INFO - This is an information message
Python 日志记录变量
通过使用字符串格式化来记录动态数据。
#!/usr/bin/python import logging root = logging.getLogger() root.setLevel(logging.INFO) log_format = '%(asctime)s %(filename)s: %(message)s' logging.basicConfig(filename="test.log", format=log_format) # incident happens error_message = 'authentication failed' root.error(f'error: {error_message}')
该示例将自定义数据写入日志消息。
2019-03-21 14:17:23,196 log_variable.py: error: authentication failed
这是日志消息。
Python 日志记录格式化日期时间
日期时间包含在带有 asctime
日志记录的日志消息中。使用 datefmt
配置选项,我们可以格式化日期时间字符串。
#!/usr/bin/python import logging logger = logging.getLogger() logger.setLevel(logging.DEBUG) log_format = '%(asctime)s %(filename)s: %(message)s' logging.basicConfig(filename="test.log", format=log_format, datefmt='%Y-%m-%d %H:%M:%S') logger.info("information message")
该示例格式化了日志消息的日期时间。
log_format = '%(asctime)s %(filename)s: %(message)s'
我们使用 asctime
将日期时间字符串包含在日志中。
logging.basicConfig(filename="test.log", format=log_format, datefmt='%Y-%m-%d %H:%M:%S')
datefmt
选项格式化日期时间字符串。
2019-03-21 14:17:23,196 log_variable.py: error: authentication failed 2019-03-21 14:23:33 date_time.py: information message
请注意日期时间字符串格式的差异。
Python 日志记录堆栈跟踪
堆栈跟踪是函数调用栈,显示了在抛出异常之前运行的函数。堆栈跟踪包含在 exc_info
选项中。
#!/usr/bin/python import logging log_format = '%(asctime)s %(filename)s: %(message)s' logging.basicConfig(filename="test.log", format=log_format) vals = [1, 2] try: print(vals[4]) except Exception as e: logging.error("exception occurred", exc_info=True)
在示例中,我们记录了尝试访问不存在的列表索引时抛出的异常。
logging.error("exception occurred", exc_info=True)
通过将 exc_info
设置为 True
,将堆栈跟踪包含在日志中。
2019-03-21 14:56:21,313 stack_trace.py: exception occurred Traceback (most recent call last): File "C:\Users\Jano\Documents\pyprogs\pylog\stack_trace.py", line 11, in <module> print(vals[4]) IndexError: list index out of range
日志中包含了堆栈跟踪。
Python 日志记录 getLogger
getLogger
返回一个具有指定名称的日志记录器。如果未指定名称,它将返回根日志记录器。将模块名称放在那里是一个常见的做法,使用 __name__
。
所有对该函数的调用,只要给定的名称相同,就会返回相同的日志记录器实例。这意味着日志记录器实例永远不需要在应用程序的不同部分之间传递。
#!/usr/bin/python import logging import sys main = logging.getLogger('main') main.setLevel(logging.DEBUG) handler = logging.FileHandler('my.log') format = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s') handler.setFormatter(format) main.addHandler(handler) main.info('info message') main.critical('critical message') main.debug('debug message') main.warning('warning message') main.error('error message')
该示例使用 getLogger
创建了一个新的日志记录器。它被赋予了一个文件处理器和一个格式化器。
main = logging.getLogger('main') main.setLevel(logging.DEBUG)
创建了一个名为 main
的日志记录器;我们将日志记录级别设置为 DEBUG
。
handler = logging.FileHandler('my.log')
创建了一个文件处理器。消息将被写入 my.log
文件。
format = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s') handler.setFormatter(format)
创建了一个格式化器。它将时间、日志记录器名称、日志级别和消息包含在日志中。使用 setFormatter
将格式化器设置到处理器。
main.addHandler(handler)
使用 addHandler
将处理器添加到日志记录器。
$ cat my.log 2019-03-21 14:15:45,439 main INFO: info message 2019-03-21 14:15:45,439 main CRITICAL: critical message 2019-03-21 14:15:45,439 main DEBUG: debug message 2019-03-21 14:15:45,439 main WARNING: warning message 2019-03-21 14:15:45,439 main ERROR: error message
这些是写入的日志消息。
Python 日志记录 YAML 配置
日志记录的详细信息可以在 YAML 配置文件中定义。YAML 是一种人类可读的数据序列化语言。它通常用于配置文件。
$ pip install pyyaml
我们需要安装 pyyaml
模块。
version: 1 formatters: simple: format: "%(asctime)s %(name)s: %(message)s" extended: format: "%(asctime)s %(name)s %(levelname)s: %(message)s" handlers: console: class: logging.StreamHandler level: INFO formatter: simple file_handler: class: logging.FileHandler level: INFO filename: test.log formatter: extended propagate: false loggers: dev: handlers: [console, file_handler] test: handlers: [file_handler] root: handlers: [file_handler]
在配置文件中,我们定义了各种格式化器、处理器和日志记录器。propagate
选项可防止日志消息传播到父日志记录器;在我们的例子中,是根日志记录器。否则,消息将被复制。
#!/usr/bin/python import logging import logging.config import yaml with open('config.yaml', 'r') as f: log_cfg = yaml.safe_load(f.read()) logging.config.dictConfig(log_cfg) logger = logging.getLogger('dev') logger.setLevel(logging.INFO) logger.info('This is an info message') logger.error('This is an error message')
在示例中,我们读取配置文件并使用 dev
日志记录器。
$ log_yaml.py 2019-03-28 11:36:54,854 dev: This is an info message 2019-03-28 11:36:54,855 dev: This is an error message
当我们运行程序时,控制台上有两条消息。控制台处理器使用简单的格式化器,信息较少。
... 2019-03-28 11:36:54,854 dev INFO: This is an info message 2019-03-28 11:36:54,855 dev ERROR: This is an error message
test.log
文件中有日志消息。它们由扩展的格式化器生成,信息量更多。
来源
在本文中,我们使用了 Python 日志记录库。
作者
列出所有 Python 教程。