首页 > 编程语言 >python 日志使用

python 日志使用

时间:2023-06-19 17:24:47浏览次数:43  
标签:记录器 python message 处理器 使用 logging 日志 logger

python 日志使用

日志基础教程

日志是对软件执行时所发生事件的一种追踪方式。软件开发人员对他们的代码添加日志调用,借此来指示某事件的发生。一个事件通过一些包含变量数据的描述信息来描述(比如:每个事件发生时的数据都是不同的)。开发者还会区分事件的重要性,重要性也被称为 等级严重性

什么时候使用日志

对于简单的日志使用来说日志功能提供了一系列便利的函数。它们是 debug()info()warning()error()critical()。想要决定何时使用日志,请看下表,其中显示了对于每个通用任务集合来说最好的工具。

你想要执行的任务 此任务最好的工具
对于命令行或程序的应用,结果显示在控制台。 print()
在对程序的普通操作发生时提交事件报告(比如:状态监控和错误调查) logging.info() 函数(当有诊断目的需要详细输出信息时使用 logging.debug() 函数)
提出一个警告信息基于一个特殊的运行时事件 warnings.warn() 位于代码库中,该事件是可以避免的,需要修改客户端应用以消除告警logging.warning() 不需要修改客户端应用,但是该事件还是需要引起关注
对一个特殊的运行时事件报告错误 引发异常
报告错误而不引发异常(如在长时间运行中的服务端进程的错误处理) logging.error(), logging.exception()logging.critical() 分别适用于特定的错误及应用领域

日志功能应以所追踪事件级别或严重性而定。各级别适用性如下(以严重性递增):

级别 何时使用
DEBUG 细节信息,仅当诊断问题时适用。
INFO 确认程序按预期运行。
WARNING 表明有已经或即将发生的意外(例如:磁盘空间不足)。程序仍按预期进行。
ERROR 由于严重的问题,程序的某些功能已经不能正常执行
CRITICAL 严重的错误,表明程序已不能继续执行

默认的级别是 WARNING,意味着只会追踪该级别及以上的事件,除非更改日志配置。

所追踪事件可以以不同形式处理。最简单的方式是输出到控制台。另一种常用的方式是写入磁盘文件。

一个简单的例子

一个非常简单的例子:

import logging
logging.warning('Watch out!')  # will print a message to the console
logging.info('I told you so')  # will not print anything

如果你在命令行中输入这些代码并运行,你将会看到:

WARNING:root:Watch out!

输出到命令行。INFO 消息并没有出现,因为默认级别是 WARNING 。打印的信息包含事件的级别以及在日志调用中的对于事件的描述,例如 “Watch out!”。暂时不用担心 “root” 部分:之后会作出解释。输出格式可按需要进行调整,格式化选项同样会在之后作出解释。

记录日志到文件

import logging
logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG)
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')
logging.error('And non-ASCII stuff, too, like Øresund and Malmö')

#输出
DEBUG:root:This message should go to the log file
INFO:root:So should this
WARNING:root:And this, too
ERROR:root:And non-ASCII stuff, too, like Øresund and Malmö

从多个模块记录日志

如果你的程序包含多个模块,这里有一个如何组织日志记录的示例:

# myapp.py
import logging
import mylib

def main():
    logging.basicConfig(filename='myapp.log', level=logging.INFO)
    logging.info('Started')
    mylib.do_something()
    logging.info('Finished')

if __name__ == '__main__':
    main()
# mylib.py
import logging

def do_something():
    logging.info('Doing something')

如果你运行 myapp.py ,你应该在 myapp.log 中看到:

INFO:root:Started
INFO:root:Doing something
INFO:root:Finished

这是你期待看到的。 你可以使用 mylib.py 中的模式将此概括为多个模块。 请注意,对于这种简单的使用模式,除了查看事件描述之外,你不能通过查看日志文件来了解应用程序中消息的 来源 。 如果要跟踪消息的位置,则需要参考教程级别以外的文档 —— 请参阅 进阶日志教程

记录变量数据

要记录变量数据,请使用格式字符串作为事件描述消息,并附加传入变量数据作为参数。 例如:

import logging
logging.warning('%s before you %s', 'Look', 'leap!')

将显示:

WARNING:root:Look before you leap!

更改显示消息的格式

要更改用于显示消息的格式,你需要指定要使用的格式:

import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should appear on the console')
logging.info('So should this')
logging.warning('And this, too')

这将输出:

DEBUG:This message should appear on the console
INFO:So should this
WARNING:And this, too

请注意,前面示例中出现的 “root” 已消失。 对于可以出现在格式字符串中的全部内容,你可以参考以下文档 LogRecord 属性 ,但为了简单使用,你只需要 levelname (严重性), message (事件描述,包括可变数据),也许在事件发生时显示。 这将在下一节中介绍。

在消息中显示日期/时间

要显示事件的日期和时间,你可以在格式字符串中放置 '%(asctime)s'

import logging
logging.basicConfig(format='%(asctime)s %(message)s')
logging.warning('is when this event was logged.')

应该打印这样的东西:

2010-12-12 11:41:42,612 is when this event was logged.

日期/时间显示的默认格式(如上所示)类似于 ISO8601 或 RFC 3339 。 如果你需要更多地控制日期/时间的格式,请为 basicConfig 提供 datefmt 参数,如下例所示:

import logging
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
logging.warning('is when this event was logged.')

这会显示如下内容:

12/12/2010 11:46:36 AM is when this event was logged.

datefmt 参数的格式与 time.strftime() 支持的格式相同。


进阶日志教程

日志库采用模块化方法,并提供几类组件:记录器、处理器、过滤器和格式器。

  • 记录器暴露了应用程序代码直接使用的接口。
  • 处理器将日志记录(由记录器创建)发送到适当的目标。
  • 过滤器提供了更细粒度的功能,用于确定要输出的日志记录。
  • 格式器指定最终输出中日志记录的样式。

日志事件信息在 LogRecord 实例中的记录器、处理器、过滤器和格式器之间传递。

通过调用 Logger 类(以下称为 loggers , 记录器)的实例来执行日志记录。 每个实例都有一个名称,它们在概念上以点(句点)作为分隔符排列在命名空间的层次结构中。 例如,名为 'scan' 的记录器是记录器 'scan.text' ,'scan.html' 和 'scan.pdf' 的父级。 记录器名称可以是你想要的任何名称,并指示记录消息源自的应用程序区域。

在命名记录器时使用的一个好习惯是在每个使用日志记录的模块中使用模块级记录器,命名如下:

logger = logging.getLogger(__name__)

这意味着记录器名称跟踪包或模块的层次结构,并且直观地从记录器名称显示记录事件的位置。

记录流程

记录器和处理器中的日志事件信息流程如下图所示。

../_images/logging_flow.png

记录器

Logger 对象有三重任务。首先,它们向应用程序代码公开了几种方法,以便应用程序可以在运行时记录消息。其次,记录器对象根据严重性(默认过滤工具)或过滤器对象确定要处理的日志消息。第三,记录器对象将相关的日志消息传递给所有感兴趣的日志处理器。

记录器对象上使用最广泛的方法分为两类:配置和消息发送。

这些是最常见的配置方法:

  • Logger.setLevel() 指定记录器将处理的最低严重性日志消息,其中 debug 是最低内置严重性级别, critical 是最高内置严重性级别。 例如,如果严重性级别为 INFO ,则记录器将仅处理 INFO 、 WARNING 、 ERROR 和 CRITICAL 消息,并将忽略 DEBUG 消息。
  • Logger.addHandler()Logger.removeHandler() 从记录器对象中添加和删除处理器对象。处理器在以下内容中有更详细的介绍 处理器
  • Logger.addFilter()Logger.removeFilter() 可以添加或移除记录器对象中的过滤器。 过滤器对象 包含更多的过滤器细节。

你不需要总是在你创建的每个记录器上都调用这些方法。 请参阅本节的最后两段。

配置记录器对象后,以下方法将创建日志消息:

  • Logger.debug()Logger.info()Logger.warning()Logger.error()Logger.critical() 都创建日志记录,包含消息和与其各自方法名称对应的级别。该消息实际上是一个格式化字符串,它可能包含标题字符串替换语法 %s%d%f 等等。其余参数是与消息中的替换字段对应的对象列表。关于 **kwargs ,日志记录方法只关注 exc_info 的关键字,并用它来确定是否记录异常信息。
  • Logger.exception() 创建与 Logger.error() 相似的日志信息。 不同之处是, Logger.exception() 同时还记录当前的堆栈追踪。仅从异常处理程序调用此方法。
  • Logger.log() 将日志级别作为显式参数。对于记录消息而言,这比使用上面列出的日志级别便利方法更加冗长,但这是使用自定义日志级别的方法。

getLogger() 返回对具有指定名称的记录器实例的引用(如果已提供),或者如果没有则返回 root 。名称是以句点分隔的层次结构。多次调用 getLogger() 具有相同的名称将返回对同一记录器对象的引用。在分层列表中较低的记录器是列表中较高的记录器的子项。例如,给定一个名为 foo 的记录器,名称为 foo.barfoo.bar.bazfoo.bam 的记录器都是 foo 子项。

记录器具有 有效等级 的概念。如果未在记录器上显式设置级别,则使用其父记录器的级别作为其有效级别。如果父记录器没有明确的级别设置,则检查 父级。依此类推,搜索所有上级元素,直到找到明确设置的级别。根记录器始终具有明确的级别配置(默认情况下为 WARNING )。在决定是否处理事件时,记录器的有效级别用于确定事件是否传递给记录器相关的处理器。

子记录器将消息传播到与其父级记录器关联的处理器。因此,不必为应用程序使用的所有记录器定义和配置处理器。一般为顶级记录器配置处理器,再根据需要创建子记录器就足够了。(但是,你可以通过将记录器的 propagate 属性设置为 False 来关闭传播。)

处理器

Handler 对象负责将适当的日志消息(基于日志消息的严重性)分派给处理器的指定目标。 Logger 对象可以使用 addHandler() 方法向自己添加零个或多个处理器对象。作为示例场景,应用程序可能希望将所有日志消息发送到日志文件,将错误或更高的所有日志消息发送到标准输出,以及将所有关键消息发送至一个邮件地址。 此方案需要三个单独的处理器,其中每个处理器负责将特定严重性的消息发送到特定位置。

标准库包含很多处理器类型(参见 有用的处理器 );教程主要使用 StreamHandlerFileHandler

处理器中很少有方法可供应用程序开发人员使用。使用内置处理器对象(即不创建自定义处理器)的应用程序开发人员能用到的仅有以下配置方法:

  • setLevel() 方法,就像在记录器对象中一样,指定将被分派到适当目标的最低严重性。为什么有两个 setLevel() 方法?记录器中设置的级别确定将传递给其处理器的消息的严重性。每个处理器中设置的级别确定该处理器将发送哪些消息。
  • setFormatter() 选择一个该处理器使用的 Formatter 对象。
  • addFilter()removeFilter() 分别在处理器上配置和取消配置过滤器对象。

应用程序代码不应直接实例化并使用 Handler 的实例。 相反, Handler 类是一个基类,它定义了所有处理器应该具有的接口,并建立了子类可以使用(或覆盖)的一些默认行为。

格式器

格式化器对象配置日志消息的最终顺序、结构和内容。 与 logging.Handler 类不同,应用程序代码可以实例化格式器类,但如果应用程序需要特殊行为,则可能会对格式化器进行子类化定制。构造函数有三个可选参数 —— 消息格式字符串、日期格式字符串和样式指示符。

  • logging.Formatter.init(fmt=None, datefmt=None, style='%')

如果没有消息格式字符串,则默认使用原始消息。如果没有日期格式字符串,则默认日期格式为:

%Y-%m-%d %H:%M:%S

with the milliseconds tacked on at the end. The style is one of '%', '{', or '$'. If one of these is not specified, then '%' will be used.

If the style is '%', the message format string uses %(<dictionary key>)s styled string substitution; the possible keys are documented in LogRecord 属性. If the style is '{', the message format string is assumed to be compatible with str.format() (using keyword arguments), while if the style is '$' then the message format string should conform to what is expected by string.Template.substitute().

在 3.2 版更改: 添加 style 形参。

以下消息格式字符串将以人类可读的格式记录时间、消息的严重性以及消息的内容,按此顺序:

'%(asctime)s - %(levelname)s - %(message)s'

格式器通过用户可配置的函数将记录的创建时间转换为元组。 默认情况下,使用 time.localtime() ;要为特定格式器实例更改此项,请将实例的 converter 属性设置为与 time.localtime()time.gmtime() 具有相同签名的函数。 要为所有格式器更改它,例如,如果你希望所有记录时间都以 GMT 显示,请在格式器类中设置 converter 属性(对于 GMT 显示,设置为 time.gmtime )。

配置日志记录

开发者可以通过三种方式配置日志记录:

  1. 使用调用上面列出的配置方法的 Python 代码显式创建记录器、处理器和格式器。
  2. 创建日志配置文件并使用 fileConfig() 函数读取它。
  3. 创建配置信息字典并将其传递给 dictConfig() 函数。

有关最后两个选项的参考文档,请参阅 配置函数 。 以下示例使用 Python 代码配置一个非常简单的记录器、一个控制台处理器和一个简单的格式器:

import logging

# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)

# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# add formatter to ch
ch.setFormatter(formatter)

# add ch to logger
logger.addHandler(ch)

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

从命令行运行此模块将生成以下输出:

$ python simple_logging_module.py
2005-03-19 15:10:26,618 - simple_example - DEBUG - debug message
2005-03-19 15:10:26,620 - simple_example - INFO - info message
2005-03-19 15:10:26,695 - simple_example - WARNING - warn message
2005-03-19 15:10:26,697 - simple_example - ERROR - error message
2005-03-19 15:10:26,773 - simple_example - CRITICAL - critical message

以下 Python 模块创建的记录器、处理器和格式器几乎与上面列出的示例中的相同,唯一的区别是对象的名称:

import logging
import logging.config

logging.config.fileConfig('logging.conf')

# create logger
logger = logging.getLogger('simpleExample')

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

这是 logging.conf 文件:

[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

其输出与不基于配置文件的示例几乎相同:

$ python simple_logging_config.py
2005-03-19 15:38:55,977 - simpleExample - DEBUG - debug message
2005-03-19 15:38:55,979 - simpleExample - INFO - info message
2005-03-19 15:38:56,054 - simpleExample - WARNING - warn message
2005-03-19 15:38:56,055 - simpleExample - ERROR - error message
2005-03-19 15:38:56,130 - simpleExample - CRITICAL - critical message

你可以看到配置文件方法相较于 Python 代码方法有一些优势,主要是配置和代码的分离以及非开发者轻松修改日志记录属性的能力。

警告

fileConfig() 函数接受一个默认参数 disable_existing_loggers ,出于向后兼容的原因,默认为 True 。这可能与您的期望不同,因为除非在配置中明确命名它们(或其父级),否则它将导致在 fileConfig() 调用之前存在的任何非 root 记录器被禁用。有关更多信息,请参阅参考文档,如果需要,请将此参数指定为 False

传递给 dictConfig() 的字典也可以用键 disable_existing_loggers 指定一个布尔值,如果没有在字典中明确指定,也默认被解释为 True 。这会导致上面描述的记录器禁用行为,这可能与你的期望不同——在这种情况下,请明确地为其提供 False 值。

请注意,配置文件中引用的类名称需要相对于日志记录模块,或者可以使用常规导入机制解析的绝对值。因此,你可以使用 WatchedFileHandler (相对于日志记录模块)或 mypackage.mymodule.MyHandler (对于在 mypackage 包中定义的类和模块 mymodule ,其中 mypackage 在 Python 导入路径上可用)。

在 Python 3.2 中,引入了一种新的配置日志记录的方法,使用字典来保存配置信息。 这提供了上述基于配置文件方法的功能的超集,并且是新应用程序和部署的推荐配置方法。 因为 Python 字典用于保存配置信息,并且由于你可以使用不同的方式填充该字典,因此你有更多的配置选项。 例如,你可以使用 JSON 格式的配置文件,或者如果你有权访问 YAML 处理功能,则可以使用 YAML 格式的文件来填充配置字典。当然,你可以在 Python 代码中构造字典,通过套接字以 pickle 形式接收它,或者使用对你的应用程序合理的任何方法。

以下是与上述相同配置的示例,采用 YAML 格式,用于新的基于字典的方法:

version: 1
formatters:
  simple:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
  console:
    class: logging.StreamHandler
    level: DEBUG
    formatter: simple
    stream: ext://sys.stdout
loggers:
  simpleExample:
    level: DEBUG
    handlers: [console]
    propagate: no
root:
  level: DEBUG
  handlers: [console]

有关使用字典进行日志记录的更多信息,请参阅 配置函数

如果没有提供配置会发生什么

如果未提供日志记录配置,则可能出现需要输出日志记录事件但无法找到输出事件的处理器的情况。 在这些情况下,logging 包的行为取决于 Python 版本。

对于 3.2 之前的 Python 版本,行为如下:

  • 如果 logging.raiseExceptionsFalse (生产模式),则会以静默方式丢弃该事件。
  • 如果 logging.raiseExceptionsTrue (开发模式),则会打印一条消息 'No handlers could be found for logger X.Y.Z'。

在 Python 3.2 及更高版本中,行为如下:

  • 事件使用 “最后的处理器” 输出,存储在 logging.lastResort 中。 这个内部处理器与任何记录器都没有关联,它的作用类似于 StreamHandler ,它将事件描述消息写入 sys.stderr 的当前值(因此服从任何可能的重定向影响)。 没有对消息进行格式化——只打印裸事件描述消息。处理器的级别设置为 WARNING,因此将输出此级别和更高级别的所有事件。

要获得 3.2 之前的行为,可以设置 logging.lastResortNone

配置库的日志记录

在开发使用日志记录的库时,你应该注意记录库如何使用日志记录——例如,使用的记录器的名称。还需要考虑其日志记录配置。如果应用程序不使用日志记录,并且库代码进行日志记录调用,那么(如上一节所述)严重性为 WARNING 及更高级别的事件将打印到 sys.stderr 。这被认为是最好的默认行为。

如果由于某种原因,你 希望在没有任何日志记录配置的情况下打印这些消息,则可以将无操作处理器附加到库的顶级记录器。这样可以避免打印消息,因为将始终为库的事件找到处理器:它不会产生任何输出。如果库用户配置应用程序使用的日志记录,可能是配置将添加一些处理器,如果级别已适当配置,则在库代码中进行的日志记录调用将正常地将输出发送给这些处理器。

日志包中包含一个不做任何事情的处理器: NullHandler (自 Python 3.1 起)。可以将此处理器的实例添加到库使用的日志记录命名空间的顶级记录器中( 如果 你希望在没有日志记录配置的情况下阻止库的记录事件输出到 sys.stderr )。如果库 foo 的所有日志记录都是使用名称匹配 'foo.x' , 'foo.x.y' 等的记录器完成的,那么代码:

import logging
logging.getLogger('foo').addHandler(logging.NullHandler())

应该有预计的效果。如果一个组织生成了许多库,则指定的记录器名称可以是 “orgname.foo” 而不仅仅是 “foo” 。

备注

It is strongly advised that you do not log to the root logger in your library. Instead, use a logger with a unique and easily identifiable name, such as the __name__ for your library's top-level package or module. Logging to the root logger will make it difficult or impossible for the application developer to configure the logging verbosity or handlers of your library as they wish.

备注

强烈建议你 不要将 NullHandler 以外的任何处理器添加到库的记录器中 。这是因为处理器的配置是使用你的库的应用程序开发人员的权利。应用程序开发人员了解他们的目标受众以及哪些处理器最适合他们的应用程序:如果你在“底层”添加处理器,则可能会干扰他们执行单元测试和提供符合其要求的日志的能力。

日志级别

日志记录级别的数值在下表中给出。如果你想要定义自己的级别,并且需要它们具有相对于预定义级别的特定值,那么这你可能对以下内容感兴趣。如果你定义具有相同数值的级别,它将覆盖预定义的值;预定义的名称将失效。

级别 数值
CRITICAL 50
ERROR 40
WARNING 30
INFO 20
DEBUG 10
NOTSET 0

级别也可以与记录器相关联,由开发人员设置或通过加载已保存的日志记录配置。在记录器上调用日志记录方法时,记录器会将其自己的级别与与方法调用关联的级别进行比较。如果记录器的级别高于方法调用的级别,则实际上不会生成任何记录消息。这是控制日志记录输出详细程度的基本机制。

记录消息被编码为 LogRecord 类的实例。当记录器决定实际记录事件时,将用记录消息创建 LogRecord 实例。

记录消息受 handlers 建立的调度机制控制,它们是 Handler 类的子类实例。处理器负责确保记录的消息(以 LogRecord 的形式)最终位于对该消息的目标受众(例如最终用户、 支持服务台员工、系统管理员、开发人员)有用的特定位置(或一组位置)上。处理器传递适用于特定目标的 LogRecord 实例。 每个记录器可以有零个、一个或多个与之关联的处理器(通过 LoggeraddHandler() 方法)。除了与记录器直接关联的所有处理器之外,还调用与记录器的 所有祖先关联的处理器来分派消息(除非记录器的 *propagate 标志设置为 false 值,这将停止传递到上级处理器)。

就像记录器一样,处理器可以具有与它们相关联的级别。处理器的级别作为过滤器,其方式与记录器级别相同。如果处理器决定调度一个事件,则使用 emit() 方法将消息发送到其目标。大多数用户定义的 Handler 子类都需要重载 emit()

自定义级别

定义你自己的级别是可能的,但不一定是必要的,因为现有级别是根据实践经验选择的。但是,如果你确信需要自定义级别,那么在执行此操作时应特别小心,如果你正在开发库,则 定义自定义级别可能是一个非常糟糕的主意 。 这是因为如果多个库作者都定义了他们自己的自定义级别,那么使用开发人员很难控制和解释这些多个库的日志记录输出,因为给定的数值对于不同的库可能意味着不同的东西。

有用的处理器

作为 Handler 基类的补充,提供了很多有用的子类:

  1. StreamHandler 实例发送消息到流(类似文件对象)。
  2. FileHandler 实例将消息发送到硬盘文件。
  3. BaseRotatingHandler 是轮换日志文件的处理器的基类。它并不应该直接实例化。而应该使用 RotatingFileHandlerTimedRotatingFileHandler 代替它。
  4. RotatingFileHandler 实例将消息发送到硬盘文件,支持最大日志文件大小和日志文件轮换。
  5. TimedRotatingFileHandler 实例将消息发送到硬盘文件,以特定的时间间隔轮换日志文件。
  6. SocketHandler 实例将消息发送到 TCP/IP 套接字。从 3.4 开始,也支持 Unix 域套接字。
  7. DatagramHandler 实例将消息发送到 UDP 套接字。从 3.4 开始,也支持 Unix 域套接字。
  8. SMTPHandler 实例将消息发送到指定的电子邮件地址。
  9. SysLogHandler 实例将消息发送到 Unix syslog 守护程序,可能在远程计算机上。
  10. NTEventLogHandler 实例将消息发送到 Windows NT/2000/XP 事件日志。
  11. MemoryHandler 实例将消息发送到内存中的缓冲区,只要满足特定条件,缓冲区就会刷新。
  12. HTTPHandler 实例使用 GETPOST 方法将消息发送到 HTTP 服务器。
  13. WatchedFileHandler 实例会监视他们要写入日志的文件。如果文件发生更改,则会关闭该文件并使用文件名重新打开。此处理器仅在类 Unix 系统上有用; Windows 不支持依赖的基础机制。
  14. QueueHandler 实例将消息发送到队列,例如在 queuemultiprocessing 模块中实现的队列。
  15. NullHandler 实例对错误消息不执行任何操作。它们由想要使用日志记录的库开发人员使用,但是想要避免如果库用户没有配置日志记录,则显示 'No handlers could be found for logger XXX' 消息的情况。更多有关信息,请参阅 配置库的日志记录

3.1 新版功能: NullHandler 类。

3.2 新版功能: QueueHandler 类。

The NullHandlerStreamHandlerFileHandler 类在核心日志包中定义。其他处理器定义在 logging.handlers 中。(还有另一个子模块 logging.config ,用于配置功能)

记录的消息通过 Formatter 类的实例进行格式化后呈现。 它们使用能与 % 运算符一起使用的格式字符串和字典进行初始化。

要批量格式化多个消息,可以使用 BufferingFormatter 的实例。除了格式字符串(应用于批处理中的每个消息)之外,还提供了标题和尾部格式字符串。

当基于记录器级别和处理器级别的过滤不够时,可以将 Filter 的实例添加到 LoggerHandler 实例(通过它们的 addFilter() 方法)。在决定进一步处理消息之前,记录器和处理器都会查询其所有过滤器以获得许可。如果任何过滤器返回 false 值,则不会进一步处理该消息。

基本 Filter 的功能允许按特定的记录器名称进行过滤。如果使用此功能,则允许通过过滤器发送到指定记录器及其子项的消息,并丢弃其他所有消息。

记录日志时引发的异常

logging 包设计为忽略记录日志生产时发生的异常。这样,处理日志记录事件时发生的错误(例如日志记录错误配置、网络或其他类似错误)不会导致使用日志记录的应用程序过早终止。

SystemExitKeyboardInterrupt 异常永远不会被忽略。 在 Handler 子类的 emit() 方法中发生的其他异常被传递给它的 handleError() 方法。

Handler 中默认实现的 handleError() 检查是否设置了模块级变量 raiseExceptions 。如果有设置,则会将回溯打印到 sys.stderr 。如果未设置,则忽略异常。

备注

raiseExceptions 默认值是 True。 这是因为在开发期间,你通常希望收到任何发生异常的通知。建议你将 raiseExceptions 设置为 False 以供生产环境使用。

使用任意对象作为消息

在前面的部分和示例中,都假设记录事件时传递的消息是字符串。 但是,这不是唯一的可能性。你可以将任意对象作为消息传递,并且当日志记录系统需要将其转换为字符串表示时,将调用其 __ str__() 方法。实际上,如果你愿意,你可以完全避免计算字符串表示。例如, SocketHandler 用 pickle 处理事件后,通过网络发送。

优化

消息参数的格式化将被推迟,直到无法避免。但是,计算传递给日志记录方法的参数也可能很消耗资源,如果记录器只是丢弃你的事件,你可能希望避免这样做。要决定做什么,可以调用 isEnabledFor() 方法,该方法接受一个 level 参数,如果记录器为该级别的调用创建了该事件,则返回 true 。 你可以写这样的代码:

if logger.isEnabledFor(logging.DEBUG):
    logger.debug('Message with %s, %s', expensive_func1(),
                                        expensive_func2())

因此,如果记录器的阈值设置在“DEBUG”以上,则永远不会调用 expensive_func1()expensive_func2()

备注

在某些情况下, isEnabledFor() 本身可能比你想要的更消耗资源(例如,对于深度嵌套的记录器,其中仅在记录器层次结构中设置了显式级别)。在这种情况下(或者如果你想避免在紧密循环中调用方法),你可以在本地或实例变量中将调用的结果缓存到 isEnabledFor() ,并使用它而不是每次调用方法。在日志记录配置在应用程序运行时动态更改(这不常见)时,只需要重新计算这样的缓存值即可。

对于需要对收集的日志信息进行更精确控制的特定应用程序,还可以进行其他优化。以下列出了在日志记录过程中您可以避免的非必须处理操作:

你不想收集的内容 如何避免收集它
有关调用来源的信息 logging._srcfile 设置为 None 。这避免了调用 sys._getframe() ,如果 PyPy 支持 Python 3.x ,这可能有助于加速 PyPy (无法加速使用 sys._getframe() 的代码)等环境中的代码。
线程信息 logging.logThreads 设为 False
当前进程 ID (os.getpid()) logging.logProcesses 设为 False
当使用 multiprocessing 来管理多个进程时的当前进程名称。 logging.logMultiprocessing 设为 False

另请注意,核心日志记录模块仅包含基本处理器。如果你不导入 logging.handlerslogging.config ,它们将不会占用任何内存。


日志操作手册

在多个线程中记录日志

多线程记录日志并不需要特殊处理,以下示例演示了在主线程(起始线程)和其他线程中记录日志的过程:

import logging
import threading
import time

def worker(arg):
    while not arg['stop']:
        logging.debug('Hi from myfunc')
        time.sleep(0.5)

def main():
    logging.basicConfig(level=logging.DEBUG, format='%(relativeCreated)6d %(threadName)s %(message)s')
    info = {'stop': False}
    thread = threading.Thread(target=worker, args=(info,))
    thread.start()
    while True:
        try:
            logging.debug('Hello from main')
            time.sleep(0.75)
        except KeyboardInterrupt:
            info['stop'] = True
            break
    thread.join()

if __name__ == '__main__':
    main()

多个 handler 和多种 formatter

日志是个普通的 Python 对象。 addHandler() 方法可加入不限数量的日志 handler。有时候,应用程序需把严重错误信息记入文本文件,而将一般错误或其他级别的信息输出到控制台。若要进行这样的设定,只需多配置几个日志 handler 即可,应用程序的日志调用代码可以保持不变。下面对之前的分模块日志示例略做修改:

import logging

logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)
# create file handler which logs even debug messages
fh = logging.FileHandler('spam.log')
fh.setLevel(logging.DEBUG)
# create console handler with a higher log level
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
# create formatter and add it to the handlers
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
fh.setFormatter(formatter)
# add the handlers to logger
logger.addHandler(ch)
logger.addHandler(fh)

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

需要注意的是,“应用程序”内的代码并不关心是否存在多个日志 handler。示例中所做的改变,只是新加入并配置了一个名为 fh 的 handler。

在编写和测试应用程序时,若能创建日志 handler 对不同严重级别的日志信息进行过滤,这将十分有用。调试时无需用多条 print 语句,而是采用 logger.debug :print 语句以后还得注释或删掉,而 logger.debug 语句可以原样留在源码中保持静默。当需要再次调试时,只要改变日志对象或 handler 的严重级别即可。

在多个地方记录日志

假定要根据不同的情况将日志以不同的格式写入控制台和文件。比如把 DEBUG 以上级别的日志信息写于文件,并且把 INFO 以上的日志信息输出到控制台。再假设日志文件需要包含时间戳,控制台信息则不需要。以下演示了做法:

import logging

# set up logging to file - see previous section for more details
logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
                    datefmt='%m-%d %H:%M',
                    filename='/tmp/myapp.log',
                    filemode='w')
# define a Handler which writes INFO messages or higher to the sys.stderr
console = logging.StreamHandler()
console.setLevel(logging.INFO)
# set a format which is simpler for console use
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
# tell the handler to use this format
console.setFormatter(formatter)
# add the handler to the root logger
logging.getLogger('').addHandler(console)

# Now, we can log to the root logger, or any other logger. First the root...
logging.info('Jackdaws love my big sphinx of quartz.')

# Now, define a couple of other loggers which might represent areas in your
# application:

logger1 = logging.getLogger('myapp.area1')
logger2 = logging.getLogger('myapp.area2')

logger1.debug('Quick zephyrs blow, vexing daft Jim.')
logger1.info('How quickly daft jumping zebras vex.')
logger2.warning('Jail zesty vixen who grabbed pay from quack.')
logger2.error('The five boxing wizards jump quickly.')

当运行后,你会看到控制台如下所示

root        : INFO     Jackdaws love my big sphinx of quartz.
myapp.area1 : INFO     How quickly daft jumping zebras vex.
myapp.area2 : WARNING  Jail zesty vixen who grabbed pay from quack.
myapp.area2 : ERROR    The five boxing wizards jump quickly.

而日志文件将如下所示:

10-22 22:19 root         INFO     Jackdaws love my big sphinx of quartz.
10-22 22:19 myapp.area1  DEBUG    Quick zephyrs blow, vexing daft Jim.
10-22 22:19 myapp.area1  INFO     How quickly daft jumping zebras vex.
10-22 22:19 myapp.area2  WARNING  Jail zesty vixen who grabbed pay from quack.
10-22 22:19 myapp.area2  ERROR    The five boxing wizards jump quickly.

如您所见,DEBUG 级别的日志信息只出现在了文件中,而其他信息则两个地方都会输出。

上述示例只用到了控制台和文件 handler,当然还可以自由组合任意数量的日志 handler。

通过网络收发日志事件

假定现在要通过网络发送日志事件,并在接收端进行处理。有一种简单的方案,就是在发送端的根日志对象连接一个 SocketHandler 实例:

import logging, logging.handlers

rootLogger = logging.getLogger('')
rootLogger.setLevel(logging.DEBUG)
socketHandler = logging.handlers.SocketHandler('localhost',
                    logging.handlers.DEFAULT_TCP_LOGGING_PORT)
# don't bother with a formatter, since a socket handler sends the event as
# an unformatted pickle
rootLogger.addHandler(socketHandler)

# Now, we can log to the root logger, or any other logger. First the root...
logging.info('Jackdaws love my big sphinx of quartz.')

# Now, define a couple of other loggers which might represent areas in your
# application:

logger1 = logging.getLogger('myapp.area1')
logger2 = logging.getLogger('myapp.area2')

logger1.debug('Quick zephyrs blow, vexing daft Jim.')
logger1.info('How quickly daft jumping zebras vex.')
logger2.warning('Jail zesty vixen who grabbed pay from quack.')
logger2.error('The five boxing wizards jump quickly.')

在接收端,可以用 socketserver 模块设置一个接收器。简要示例如下:

import pickle
import logging
import logging.handlers
import socketserver
import struct


class LogRecordStreamHandler(socketserver.StreamRequestHandler):
    """Handler for a streaming logging request.

    This basically logs the record using whatever logging policy is
    configured locally.
    """

    def handle(self):
        """
        Handle multiple requests - each expected to be a 4-byte length,
        followed by the LogRecord in pickle format. Logs the record
        according to whatever policy is configured locally.
        """
        while True:
            chunk = self.connection.recv(4)
            if len(chunk) < 4:
                break
            slen = struct.unpack('>L', chunk)[0]
            chunk = self.connection.recv(slen)
            while len(chunk) < slen:
                chunk = chunk + self.connection.recv(slen - len(chunk))
            obj = self.unPickle(chunk)
            record = logging.makeLogRecord(obj)
            self.handleLogRecord(record)

    def unPickle(self, data):
        return pickle.loads(data)

    def handleLogRecord(self, record):
        # if a name is specified, we use the named logger rather than the one
        # implied by the record.
        if self.server.logname is not None:
            name = self.server.logname
        else:
            name = record.name
        logger = logging.getLogger(name)
        # N.B. EVERY record gets logged. This is because Logger.handle
        # is normally called AFTER logger-level filtering. If you want
        # to do filtering, do it at the client end to save wasting
        # cycles and network bandwidth!
        logger.handle(record)

class LogRecordSocketReceiver(socketserver.ThreadingTCPServer):
    """
    Simple TCP socket-based logging receiver suitable for testing.
    """

    allow_reuse_address = True

    def __init__(self, host='localhost',
                 port=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
                 handler=LogRecordStreamHandler):
        socketserver.ThreadingTCPServer.__init__(self, (host, port), handler)
        self.abort = 0
        self.timeout = 1
        self.logname = None

    def serve_until_stopped(self):
        import select
        abort = 0
        while not abort:
            rd, wr, ex = select.select([self.socket.fileno()],
                                       [], [],
                                       self.timeout)
            if rd:
                self.handle_request()
            abort = self.abort

def main():
    logging.basicConfig(
        format='%(relativeCreated)5d %(name)-15s %(levelname)-8s %(message)s')
    tcpserver = LogRecordSocketReceiver()
    print('About to start TCP server...')
    tcpserver.serve_until_stopped()

if __name__ == '__main__':
    main()

先运行服务端,再运行客户端。客户端控制台不会显示什么信息;在服务端应该会看到如下内容:

About to start TCP server...
   59 root            INFO     Jackdaws love my big sphinx of quartz.
   59 myapp.area1     DEBUG    Quick zephyrs blow, vexing daft Jim.
   69 myapp.area1     INFO     How quickly daft jumping zebras vex.
   69 myapp.area2     WARNING  Jail zesty vixen who grabbed pay from quack.
   69 myapp.area2     ERROR    The five boxing wizards jump quickly.

请注意,某些时候 pickle 会存在一些安全问题。若有问题可换用自己的序列化方案,只要覆盖 makePickle() 方法即可,并调整上述脚本以采用自己的序列化方案。

轮换日志文件

有时,你希望当日志文件不断记录增长至一定大小时,打开一个新的文件接着记录。 你可能希望只保留一定数量的日志文件,当不断的创建文件到达该数量时,又覆盖掉最开始的文件形成循环。 对于这种使用场景,日志包提供了 RotatingFileHandler:

import glob
import logging
import logging.handlers

LOG_FILENAME = 'logging_rotatingfile_example.out'

# Set up a specific logger with our desired output level
my_logger = logging.getLogger('MyLogger')
my_logger.setLevel(logging.DEBUG)

# Add the log message handler to the logger
handler = logging.handlers.RotatingFileHandler(
              LOG_FILENAME, maxBytes=20, backupCount=5)

my_logger.addHandler(handler)

# Log some messages
for i in range(20):
    my_logger.debug('i = %d' % i)

# See what files are created
logfiles = glob.glob('%s*' % LOG_FILENAME)

for filename in logfiles:
    print(filename)

结果应该是6个单独的文件,每个文件都包含了应用程序的部分历史日志:

logging_rotatingfile_example.out
logging_rotatingfile_example.out.1
logging_rotatingfile_example.out.2
logging_rotatingfile_example.out.3
logging_rotatingfile_example.out.4
logging_rotatingfile_example.out.5

最新的文件始终是:file:logging_rotatingfile_example.out,每次到达大小限制时,都会使用后缀.1重命名。每个现有的备份文件都会被重命名并增加其后缀(例如.1 变为.2),而.6文件会被删除掉。

显然,这个例子将日志长度设置得太小,这是一个极端的例子。 你可能希望将 maxBytes 设置为一个合适的值。

使用上下文管理器的可选的日志记录

有时候,我们需要暂时更改日志配置,并在执行某些操作后将其还原。为此,上下文管理器是实现保存和恢复日志上下文的最明显的方式。这是一个关于上下文管理器的简单例子,它允许你在上下文管理器的作用域内更改日志记录等级以及增加日志处理器:

import logging
import sys

class LoggingContext:
    def __init__(self, logger, level=None, handler=None, close=True):
        self.logger = logger
        self.level = level
        self.handler = handler
        self.close = close

    def __enter__(self):
        if self.level is not None:
            self.old_level = self.logger.level
            self.logger.setLevel(self.level)
        if self.handler:
            self.logger.addHandler(self.handler)

    def __exit__(self, et, ev, tb):
        if self.level is not None:
            self.logger.setLevel(self.old_level)
        if self.handler:
            self.logger.removeHandler(self.handler)
        if self.handler and self.close:
            self.handler.close()
        # implicit return of None => don't swallow exceptions

如果指定上下文管理器的日志记录等级属性,则在上下文管理器的with语句所涵盖的代码中,日志记录器的记录等级将临时设置为上下文管理器所配置的日志记录等级。 如果指定上下文管理的日志处理器属性,则该句柄在进入上下文管理器的上下文时添加到记录器中,并在退出时被删除。 如果你再也不需要该日志处理器时,你可以让上下文管理器在退出上下文管理器的上下文时关闭它。

为了说明它是如何工作的,我们可以在上面添加以下代码块:

if __name__ == '__main__':
    logger = logging.getLogger('foo')
    logger.addHandler(logging.StreamHandler())
    logger.setLevel(logging.INFO)
    logger.info('1. This should appear just once on stderr.')
    logger.debug('2. This should not appear.')
    with LoggingContext(logger, level=logging.DEBUG):
        logger.debug('3. This should appear once on stderr.')
    logger.debug('4. This should not appear.')
    h = logging.StreamHandler(sys.stdout)
    with LoggingContext(logger, level=logging.DEBUG, handler=h, close=True):
        logger.debug('5. This should appear twice - once on stderr and once on stdout.')
    logger.info('6. This should appear just once on stderr.')
    logger.debug('7. This should not appear.')

我们最初设置日志记录器的消息等级为 INFO,因此消息#1出现,消息#2没有出现。在接下来的 with``代码块中我们暂时将消息等级变更为 ``DEBUG,从而消息 #3 出现。在这一代码块退出后,日志记录器的消息等级恢复为 INFO,从而消息 #4 没有出现。在下一个 with 代码块中,我们再一次将设置消息等级设置为 DEBUG,同时添加一个将消息写入 sys.stdout 的日志处理器。因此,消息#5在控制台出现两次 (分别通过 stderrstdout)。在 with 语句完成后,状态与之前一样,因此消息 #6 出现(类似消息 #1),而消息 #7 没有出现(类似消息 #2)。

如果我们运行生成的脚本,结果如下:

$ python logctx.py
1. This should appear just once on stderr.
3. This should appear once on stderr.
5. This should appear twice - once on stderr and once on stdout.
5. This should appear twice - once on stderr and once on stdout.
6. This should appear just once on stderr.

我们将stderr标准错误重定向到/dev/null,我再次运行生成的脚步,唯一被写入stdout标准输出的消息,即我们所能看见的消息,如下:

$ python logctx.py 2>/dev/null
5. This should appear twice - once on stderr and once on stdout.

再一次,将 stdout 标准输出重定向到 /dev/null,我获得如下结果:

$ python logctx.py >/dev/null
1. This should appear just once on stderr.
3. This should appear once on stderr.
5. This should appear twice - once on stderr and once on stdout.
6. This should appear just once on stderr.

在这种情况下,与预期一致,打印到 stdout 标准输出的消息#5不会出现。

当然,这里描述的方法可以被推广,例如临时附加日志记录过滤器。 请注意,上面的代码适用于Python 2以及Python 3。

标签:记录器,python,message,处理器,使用,logging,日志,logger
From: https://www.cnblogs.com/starkzz/p/17491612.html

相关文章

  • 记录 Windows 下绿色版 PostgreSQL 部署使用
    使用官方的安装包,可能会在最后的步骤遇到各种有关服务运行的问题,绿色版就非常简单了,记录一下绿色版的下载部署。1、下载地址:https://www.enterprisedb.com/download-postgresql-binaries2、将文件解压到想放置的目录3、进入pgsql\bin目录,打开命令提示符执行以下命令::初......
  • POSTGRESQL 设置hugepage 可以让系统使用内存更有效率,防止OOM
    https://www.percona.com/blog/why-linux-hugepages-are-super-important-for-database-servers-a-case-with-postgresql/https://bbs.huaweicloud.com/blogs/detail/156799Hugepage是什么,基于LINUX系统,大页面对虚拟内存管理是有必要的。除标准的4KB页面之外,还进行内存中的大页面......
  • ABAP READ_TEXT, SAVE_TEXT函数使用,物料,利润中心等长文本批量读取及维护
    一.READ_TEXT函数读取长文本本文以利润中心长文本读取及维护为例子,事务码:KE521.查看长文本参数,输入KE52事务码查看图片12.点击书写按钮后,输入需要维护的长文本语言。查看图片23.点击转到->表头,其中文本名,语言,ID和文本对象为调用函数需要的参数查看图片34.READ_T......
  • QT sqlite 使用
    非常适合QT本地的数据落地,基本语法和oraclemysql有一定的差异,建议本地安装sqlite客户端,一边操作,一边开发下载页:http://www.sqlite.org/download.html1。删除数据deletefromin_store_code;2.  删除表droptablein_store_code;3。创建表 createtablein_store_co......
  • 使用NamedParameterJdbcTemplate指定命名参数
    在本文中,我们将介绍如何在连接到后端Postgres数据库的Spring启动应用程序中使用NamedParameterJdbcTemplate。我们将使用NamedParameterJdbcTemplate从PostgresDB插入,更新和删除员工。为了保持设计的合理性,我将dao,service和controller分开了。服务只是本文的一个转折点。概观Named......
  • 了解如何使用 7 PM 框架和模板优化优先级
    需求的优先级是项目经理工作中常被提及的,每一种优先级决策技术都有利有弊,也有对应的应用场景,如何选择合适的优决策技术,是产品经理做好优先级管理的前提。那么在项目管理工作中,要如何去评估需求的优先级呢?本文将介绍7个PM优先级框架及模版,并详细阐述其应用场景及应用步骤,助力PM更快......
  • Maven_使用maven2 进行团队配置
    对于团队来说,建立统一的开发环境是必须的,而maven能很好帮助建立统一的环境。下面就介绍如何更有效的进行统一的配置。准备工作:  下载必须的软件:maven2:http://maven.apache.org/download.html最主要的maven-proxy:用来代理repository,使用代理来访问多个远程库        ......
  • python 类鸟群Boids
    importsys,argparseimportmathimportnumpyasnpimportmatplotlib.pyplotaspltimportmatplotlib.animationasanimationfromscipy.spatial.distanceimportsquareform,pdist,cdist#计算点之间的距离fromnumpy.linalgimportnormfrommatplotlib.colors......
  • Python3使用装饰器实现参数类型检查
    fromfunctoolsimportwrapsdefmerge_args(varnames:tuple,args:tuple,kwargs:dict)->dict:"""融合参数-将args参数都转为kwargs参数:paramvarnames:变量名列表:paramargs:args参数:paramkwargs:kwargs参数:return:"&......
  • 云上使用 Stable Diffusion ,模型数据如何共享和存储
    随着人工智能技术的爆发,内容生成式人工智能(AIGC)成为了当下热门领域。除了ChatGPT之外,文本生成图像技术更令人惊艳。StableDiffusion,是一款开源的深度学习模型。与Midjourney提供的直接将文本转化为图像的服务不同的是它允许用户自行搭配并训练自己的图像风格,这一特性吸引了......