原文: http://zetcode.com/python/logging/

Python 日志教程展示了如何使用日志模块在 Python 中进行日志。

日志

日志是将信息写入日志文件的过程。 日志文件包含有关在操作系统,软件或通信中发生的各种事件的信息。

记录目的

完成记录是出于以下目的:

  • 信息收集
  • 故障排除
  • 产生统计数据
  • 审计
  • 性能分析

记录不仅限于识别软件开发中的错误。 它还可用于检测安全事件,监视策略违规,在出现问题时提供信息,查找应用瓶颈或生成使用情况数据。

要记录哪些事件

应记录的事件包括输入验证失败,认证和授权失败,应用错误,配置更改以及应用启动和关闭。

哪些事件不记录

不应记录的事件包括应用源代码,会话标识值,访问令牌,敏感的个人数据,密码,数据库连接字符串,加密键,银行帐户和持卡人数据。

记录最佳做法

以下是进行日志的一些最佳做法:

  • 日志应该有意义。
  • 日志应包含上下文。
  • 日志应在不同级别进行结构化和完成。
  • 日志应保持平衡; 它不应包含过多或过多的信息。
  • 记录消息应该是人类可以理解的,并且可以被机器解析。
  • 记录更复杂的应用应产生几个日志文件。
  • 日志应适应开发和生产。

日志模块

Python 日志模块定义了实现用于应用和库的灵活事件日志系统的函数和类。

日志模块组件

日志模块具有四个主要组件:记录器,处理器,过滤器和格式化程序。 记录器公开了应用代码直接使用的接口。 处理器将日志(由记录器创建)发送到适当的目的地。 过滤器提供了更细粒度的功能,用于确定要输出的日志。 格式化程序在最终输出中指定日志的布局。

Python 日志层次结构

Python 记录器形成一个层次结构。 名为main的记录器是main.new的父级。

子记录器将消息传播到与其祖先记录器关联的处理器。 因此,不必为应用中的所有记录器定义和配置处理器。 为顶级记录器配置处理器并根据需要创建子记录器就足够了。

Python 日志级别

级别用于标识事件的严重性。 有六个日志级别:

  • CRITICAL
  • ERROR
  • WARNING
  • INFO
  • DEBUG
  • NOTSET

如果日志级别设置为WARNING,则所有WARNINGERRORCRITICAL消息都将写入日志文件或控制台。 如果将其设置为ERROR,则仅记录ERRORCRITICAL消息。

记录器的概念是有效级别。 如果未在记录器上显式设置级别,则将其父级别用作其有效级别。 如果父级没有显式设置的级别,则检查其父级,依此类推-搜索所有祖先,直到找到显式设置的级别。

使用getLogger()创建记录器时,级别设置为NOTSET。 如果未使用setLevel()显式设置日志级别,则消息将传播到记录器父级。 遍历记录器的祖先记录器链,直到找到具有NOTSET以外级别的祖先或到达根。 根记录器具有默认的WARNING级别设置。

根记录器

所有记录器都是根记录器的后代。 每个记录器将日志消息传递到其父级。 使用getLogger(name)方法创建新的记录器。 调用不带名称的函数(getLogger())将返回根记录器。

根记录器始终具有显式级别集,默认情况下为WARNING

根发信人位于层次结构的顶部,即使未配置,也始终存在。 通常,程序或库不应直接登录到根记录器。 而是应配置该程序的特定记录器。 根记录器可用于轻松打开和关闭所有库中的所有记录器。

Python 日志简单示例

logging模块具有简单的方法,可以立即使用而无需任何配置。 这可以用于简单的日志。

simple.py

  1. #!/usr/bin/env python
  2. import logging
  3. logging.debug('This is a debug message')
  4. logging.info('This is an info message')
  5. logging.warning('This is a warning message')
  6. logging.error('This is an error message')
  7. logging.critical('This is a critical message')

该示例调用logging模块的五个方法。 消息将写入控制台。

  1. $ simple.py
  2. WARNING:root:This is a warning message
  3. ERROR:root:This is an error message
  4. CRITICAL:root:This is a critical message

请注意,使用了根记录器,并且只写入了三则消息。 这是因为默认情况下,仅写入具有级别警告和更高级别的消息。

Python 设置日志级别

记录级别由setLevel()设置。 它将此记录器的阈值设置为lvl。 严重性不及lvl的日志消息将被忽略。

set_level.py

  1. #!/usr/bin/env python
  2. import logging
  3. logger = logging.getLogger('dev')
  4. logger.setLevel(logging.DEBUG)
  5. logger.debug('This is a debug message')
  6. logger.info('This is an info message')
  7. logger.warning('This is a warning message')
  8. logger.error('This is an error message')
  9. logger.critical('This is a critical message')

在示例中,我们将日志级别更改为DEBUG

  1. logger = logging.getLogger('dev')

getLogger()返回具有指定名称的记录器。 如果名称为None,则返回根记录器。 名称可以是点分隔的字符串,用于定义日志层次结构。 例如"a""a.b""a.b.c"。 请注意,有一个隐式根名,未显示。

  1. $ set_level.py
  2. This is a warning message
  3. This is an error message
  4. This is a critical message

现在,所有消息均已写入。

Python 有效日志级别

有效日志级别是显式设置的级别或由记录器父级确定的级别。

effective_level.py

  1. #!/usr/bin/env python
  2. import logging
  3. main_logger = logging.getLogger('main')
  4. main_logger.setLevel(5)
  5. dev_logger = logging.getLogger('main.dev')
  6. print(main_logger.getEffectiveLevel())
  7. print(dev_logger.getEffectiveLevel())

在示例中,我们检查了两个记录器的有效记录级别。

  1. dev_logger = logging.getLogger('main.dev')

未设置dev_logger的电平; 然后使用其父级。

  1. $ effective_level.py
  2. 5
  3. 5

这是输出。

Python 日志处理器

处理器是一个对象,负责将适当的日志消息(基于日志消息的严重性)调度到处理器的指定目标。

处理器像级别一样传播。 如果记录器未设置处理器,则其祖先链将搜索处理器。

handlers.py

  1. #!/usr/bin/env python
  2. import logging
  3. logger = logging.getLogger('dev')
  4. logger.setLevel(logging.INFO)
  5. fileHandler = logging.FileHandler('test.log')
  6. fileHandler.setLevel(logging.INFO)
  7. consoleHandler = logging.StreamHandler()
  8. consoleHandler.setLevel(logging.INFO)
  9. logger.addHandler(fileHandler)
  10. logger.addHandler(consoleHandler)
  11. logger.info('information message')

该示例为记录器创建两个处理器:文件处理器和控制台处理器。

  1. fileHandler = logging.FileHandler('test.log')

FileHandler将日志发送到test.log文件。

  1. consoleHandler = logging.StreamHandler()

StreamHandler将日志发送到流。 如果未指定流,则使用sys.stderr

  1. logger.addHandler(fileHandler)

该处理器将通过addHandler()添加到记录器。

Python 日志格式化程序

格式化程序是一个对象,用于配置日志的最终顺序,结构和内容。 除消息字符串外,日志还包括日期和时间,日志名称和日志级别严重性。

formatter.py

  1. #!/usr/bin/env python
  2. import logging
  3. logger = logging.getLogger('dev')
  4. logger.setLevel(logging.INFO)
  5. consoleHandler = logging.StreamHandler()
  6. consoleHandler.setLevel(logging.INFO)
  7. logger.addHandler(consoleHandler)
  8. formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s')
  9. consoleHandler.setFormatter(formatter)
  10. logger.info('information message')

该示例创建一个控制台记录器,并将格式化程序添加到其处理器。

  1. formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s')

格式化程序已创建。 它包括日期时间,记录器名称,记录级别名称和记录消息。

  1. consoleHandler.setFormatter(formatter)

格式化程序通过setFormatter()设置为处理器。

  1. $ formatter.py
  2. 2019-03-28 14:53:27,446 dev INFO: information message

具有定义格式的消息显示在控制台中。

Python 日志basicConfig

basicConfig()配置根记录器。 它通过使用默认格式化程序创建流处理器来为日志系统进行基本配置。 如果没有为根记录器定义处理器,则debug()info()warning()error()critical()自动调用basicConfig()

basic_config.py

  1. #!/usr/bin/env python
  2. import logging
  3. logging.basicConfig(filename='test.log', format='%(filename)s: %(message)s',
  4. level=logging.DEBUG)
  5. logging.debug('This is a debug message')
  6. logging.info('This is an info message')
  7. logging.warning('This is a warning message')
  8. logging.error('This is an error message')
  9. logging.critical('This is a critical message')

该示例使用basicConfig配置根记录器。

  1. logging.basicConfig(filename='test.log', format='%(filename)s: %(message)s',
  2. level=logging.DEBUG)

使用filename,我们设置要写入日志消息的文件。 format确定将什么内容记录到文件中; 我们有文件名和消息。 使用level,设置记录阈值。

  1. $ basic_config.py
  2. $ cat test.log
  3. basic_config.py: This is a debug message
  4. basic_config.py: This is an info message
  5. basic_config.py: This is a warning message
  6. basic_config.py: This is an error message
  7. basic_config.py: This is a critical message

运行该程序后,我们将五条消息写入test.log文件。

Python 日志文件配置

fileConfig()configparser格式文件中读取日志配置。

log.conf

  1. [loggers]
  2. keys=root,dev
  3. [handlers]
  4. keys=consoleHandler
  5. [formatters]
  6. keys=extend,simple
  7. [logger_root]
  8. level=INFO
  9. handlers=consoleHandler
  10. [logger_dev]
  11. level=INFO
  12. handlers=consoleHandler
  13. qualname=dev
  14. propagate=0
  15. [handler_consoleHandler]
  16. class=StreamHandler
  17. level=INFO
  18. formatter=extend
  19. args=(sys.stdout,)
  20. [formatter_extend]
  21. format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
  22. [formatter_simple]
  23. format=%(asctime)s - %(message)s

log.conf定义了记录器,处理器和格式化程序。

file_config.py

  1. #!/usr/bin/env python
  2. import logging
  3. import logging.config
  4. logging.config.fileConfig(fname='log.conf')
  5. logger = logging.getLogger('dev')
  6. logger.info('This is an information message')

该示例从log.conf读取日志配置文件。

  1. $ file_config.py
  2. 2019-03-28 15:26:31,137 - dev - INFO - This is an information message

这是输出。

Python 日志变量

通过使用字符串格式记录动态数据。

log_variable.py

  1. #!/usr/bin/env python
  2. import logging
  3. root = logging.getLogger()
  4. root.setLevel(logging.INFO)
  5. log_format = '%(asctime)s %(filename)s: %(message)s'
  6. logging.basicConfig(filename="test.log", format=log_format)
  7. # incident happens
  8. error_message = 'authentication failed'
  9. root.error(f'error: {error_message}')

该示例将自定义数据写入日志消息。

  1. 2019-03-21 14:17:23,196 log_variable.py: error: authentication failed

这是日志消息。

Python 日志格式化日期时间

日期时间包含在asctime日志的日志消息中。 使用datefmt配置选项,我们可以格式化日期时间字符串。

date_time.py

  1. #!/usr/bin/env python
  2. import logging
  3. logger = logging.getLogger()
  4. logger.setLevel(logging.DEBUG)
  5. log_format = '%(asctime)s %(filename)s: %(message)s'
  6. logging.basicConfig(filename="test.log", format=log_format,
  7. datefmt='%Y-%m-%d %H:%M:%S')
  8. logger.info("information message")

该示例格式化日志消息的日期时间。

  1. log_format = '%(asctime)s %(filename)s: %(message)s'

我们将日期时间字符串包含在asctime中。

  1. logging.basicConfig(filename="test.log", format=log_format,
  2. datefmt='%Y-%m-%d %H:%M:%S')

datefmt选项格式化日期时间字符串。

  1. 2019-03-21 14:17:23,196 log_variable.py: error: authentication failed
  2. 2019-03-21 14:23:33 date_time.py: information message

注意日期时间字符串格式的不同。

Python 日志栈跟踪

栈跟踪是调用函数的栈,这些函数一直运行到引发异常时为止。 栈跟踪包含在exc_info选项中。

stack_trace.py

  1. #!/usr/bin/env python
  2. import logging
  3. log_format = '%(asctime)s %(filename)s: %(message)s'
  4. logging.basicConfig(filename="test.log", format=log_format)
  5. vals = [1, 2]
  6. try:
  7. print(vals[4])
  8. except Exception as e:
  9. logging.error("exception occurred", exc_info=True)

在该示例中,我们记录了尝试访问不存在的列表索引时引发的异常。

  1. logging.error("exception occurred", exc_info=True)

通过将exc_info设置为True,栈跟踪将包含在日志中。

  1. 2019-03-21 14:56:21,313 stack_trace.py: exception occurred
  2. Traceback (most recent call last):
  3. File "C:\Users\Jano\Documents\pyprogs\pylog\stack_trace.py", line 11, in <module>
  4. print(vals[4])
  5. IndexError: list index out of range

栈跟踪包含在日志中。

Python 日志getLogger

getLogger()返回具有指定名称的记录器。 如果未指定名称,则返回根记录器。 在__name__中放置模块名称是一种常见的做法。

使用给定名称对该函数的所有调用均返回相同的记录器实例。 这意味着记录器实例永远不需要在应用的不同部分之间传递。

get_logger.py

  1. #!/usr/bin/env python
  2. import logging
  3. import sys
  4. main = logging.getLogger('main')
  5. main.setLevel(logging.DEBUG)
  6. handler = logging.FileHandler('my.log')
  7. format = logging.Formatter('%(asctime)s %(name)s
  8. %(levelname)s: %(message)s')
  9. handler.setFormatter(format)
  10. main.addHandler(handler)
  11. main.info('info message')
  12. main.critical('critical message')
  13. main.debug('debug message')
  14. main.warning('warning message')
  15. main.error('error message')

该示例使用getLogger()创建一个新的记录器。 它有一个文件处理器和一个格式化程序。

  1. main = logging.getLogger('main')
  2. main.setLevel(logging.DEBUG)

创建了一个名为main的记录器; 我们将日志级别设置为DEBUG

  1. handler = logging.FileHandler('my.log')

创建一个文件处理器。 消息将被写入my.log文件。

  1. format = logging.Formatter('%(asctime)s %(name)s
  2. %(levelname)s: %(message)s')
  3. handler.setFormatter(format)

格式化程序已创建。 它包括时间,记录器名称,记录级别以及要记录的消息。 格式化程序通过setFormatter()设置为处理器。

  1. main.addHandler(handler)

该处理器将通过addHandler()添加到记录器。

  1. $ cat my.log
  2. 2019-03-21 14:15:45,439 main INFO: info message
  3. 2019-03-21 14:15:45,439 main CRITICAL: critical message
  4. 2019-03-21 14:15:45,439 main DEBUG: debug message
  5. 2019-03-21 14:15:45,439 main WARNING: warning message
  6. 2019-03-21 14:15:45,439 main ERROR: error message

这些是书面的日志消息。

Python 日志 YAML 配置

日志详细信息可以在 YAML 配置文件中定义。 YAML 是一种人类可读的数据序列化语言。 它通常用于配置文件。

  1. $ pip install pyyaml

我们需要安装pyyaml模块。

config.yaml

  1. version: 1
  2. formatters:
  3. simple:
  4. format: "%(asctime)s %(name)s: %(message)s"
  5. extended:
  6. format: "%(asctime)s %(name)s %(levelname)s: %(message)s"
  7. handlers:
  8. console:
  9. class: logging.StreamHandler
  10. level: INFO
  11. formatter: simple
  12. file_handler:
  13. class: logging.FileHandler
  14. level: INFO
  15. filename: test.log
  16. formatter: extended
  17. propagate: false
  18. loggers:
  19. dev:
  20. handlers: [console, file_handler]
  21. test:
  22. handlers: [file_handler]
  23. root:
  24. handlers: [file_handler]

在配置文件中,我们定义了各种格式化程序,处理器和记录器。 propagate选项可防止将日志消息传播到父级记录器。 就我们而言,是根记录器。 否则,消息将被复制。

log_yaml.py

  1. #!/usr/bin/env python
  2. import logging
  3. import logging.config
  4. import yaml
  5. with open('config.yaml', 'r') as f:
  6. log_cfg = yaml.safe_load(f.read())
  7. logging.config.dictConfig(log_cfg)
  8. logger = logging.getLogger('dev')
  9. logger.setLevel(logging.INFO)
  10. logger.info('This is an info message')
  11. logger.error('This is an error message')

在示例中,我们读取配置文件并使用dev记录器。

  1. $ log_yaml.py
  2. 2019-03-28 11:36:54,854 dev: This is an info message
  3. 2019-03-28 11:36:54,855 dev: This is an error message

当我们运行程序时,控制台上有两条消息。 控制台处理器使用带有较少信息的简单格式化程序。

  1. ...
  2. 2019-03-28 11:36:54,854 dev INFO: This is an info message
  3. 2019-03-28 11:36:54,855 dev ERROR: This is an error message

test.log文件中有日志消息。 它们由扩展的格式化程序提供,具有更多信息。

在本教程中,我们使用了 Python 日志库。 您可能也对相关教程感兴趣: Python Jinja 教程Bottle 教程Python 教程或列表 Python 教程