Python 日志模块

logging in python

Logging是程序员非常有用的工具,它可以帮助你理解程序运行的流程,也可以存储信息,如:user信息或访问者的IP地址等。如果一个错误发生,它可以提供比堆栈追踪更多的信息,告诉你程序在到达错误行代码前程序的状态。

在对的位置记录有用的数据,你不仅可以更容易debug errrors,还可以分析程序的性能问题。

python的标准库提供了logging系统,所以可以在程序中快速使用logging。


python中logging模块

python中日志记录模块是一个易于使用且功能强大的模块,旨在满足初学者和企业团队的需求。大多数第三方库中皆使用它,所以你可以整合自己的日志和库的日志为同一种日志。

在程序中添加日志记录是很容易的:

import logging


导入模块后,你可以使用logger去记录你想要的信息。默认情况下,有5种标准的级别,表示事件的严重程度,每一种都有相应的方法。下面严重程度依次增加:

DEBUG 

INFO 

WARNING 

ERROR 

CRITICAL

logging模块提供了默认的logger,你可以在不需要任何额外的配置情况下去使用每一种级别的方法:

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')


上面程序输出:

WARNING:root:This is a warning message

ERROR:root:This is an error message

CRITICAL:root:This is a critical message


输出的形式是

日志严重程度级别:logger名称:信息


root是logging模块默认logger的名称。默认的输出形式还可以配置其它项如:时间、行号等。

注意看上面输出,debug和info方法的信息是没有输出的。这是因为,默认logging模块记录日志信息的级别是WARNING及更高,可以修改配置使logging模块记录你想要的日志级别。你也可以通过修改配置定义自己的日志级别,但通常不推荐这么做,因为这和容易与在使用的第三方库中的日志混淆。



基础配置

你可以使用basicConfig(**kwargs)方法同配置logging。

basicConfig一些常用的参数:

level:指定root logger的日志级别

filename:指定一个文件

filemode:如果filename被指定,则file将以此给定的模式打开,默认是a,追加模式

format:格式日志信息

通过使用level参数,你可以设置你想记录的日志级别信息,logging类中提供了常量参数,将会记录等于或高于参数级别的日志信息。

logging.basicConfig(level=logging.DEBUG)

logging.debug("This is a debug message")


DEBUG:root:This is a debug message


这表示将记录所有的日志信息。

类似的,将日志信息记录到文件而不是控制台,可以设置filename和filemode。想要定义日志输出格式使用format。

logging.basicConfig(filename="app.log", filemode='w', format='%(name)s - %(levelname)s - %(message)s')

logging.warning("This is get logged to a file")


root - WARNING - This is get logged to a file


这个信息被写到app.log文件中。这里指定文件模式为w,意味着每次调用basicConfig()方法会以写模式打开日志文件,并且每次运行程序,日志文件会覆盖重写。默认的文件模式是a,追加方式。

官网提供了更多的参数信息: https://docs.python.org/3/library/logging.html#logging.basicConfig

需要注意的是:使用basicConfig()配置root logger应该在没有配置root logger之前调用。也就是说,这个方法只能被调用生效一次。

logging.basicConfig(filename="app.log", filemode='w', format='%(name)s - %(levelname)s - %(message)s')

logging.basicConfig(filename="app2.log", filemode='w', format='%(name)s - %(levelname)s - %(message)s') # 无效

logging.warning("This is get logged to a file")


格式化输出

通过传递一些参数来记录你想要的日志信息,LogRecord中一些基本的元素都可以很轻易添加到输出格式中。如:进程id-日志级别-日志信息

logging.basicConfig(format='%(process)d-%(levelname)s-%(message)s')

logging.warning("formatting the output")


输出

5700-WARNING-formatting the output


format中你可以使用LogRecord的任何属性,属性列表: https://docs.python.org/3/library/logging.html#logrecord-attributes

logging.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)

logging.info('Admin logged in')


2020-01-25 23:35:02,643 - Admin logged in


%(asctime)s添加日志的创建时间,这个时间可以使用datefmt改变格式,它与datetime模块中格式参数相同,如:strftime()。

logging.basicConfig(format='%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')

logging.warning("formatting the output")


2020-01-25 23:42:40 - formatting the output


时间格式化参数:https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior


记录变量数据

在大多数情况下,需要保存动态信息到日志中。我们已经知道logging的方法是需要接收一个字符串作为参数的,所以很自然的使用format将动态参数拼接。但在logging方法可以直接做到这一步:

import logging

name = "John"

logging.error("%s raised an error", name)


ERROR:root:John raised an error


你还可以使用其它的格式化风格,如Python3.6中引进的f-strings,它使格式化代码更短更易读

name = "John"

logging.error(f"{name} raised an error")


捕获堆栈信息

logging模块允许捕获程序完整的堆栈信息,设置exc_info为True,异常信息就会被捕获。

import logging

a = 5

b = 0

try:

    c = a / b

except Exception as e:

    logging.error("Exception occurred", exc_info=True)


ERROR:root:Exception occurred

Traceback (most recent call last):

  File "D:/python/realpython/logging_tutorial.py", line 7, in <module>

    c = a / b

ZeroDivisionError: division by zero


如果exc_info没有设置为True,则不会输出任何关于异常的信息。

ERROR:root:Exception occurred


有一个快速的技巧:如果你要记录一个错误信息,你可以使用logging.exception()方法,它记录的日志信息是ERROR级别,并且添加异常的信息到日志中。调用logging.exception()就等于调用logging.error(exc_info=True)。

a = 5

b = 0

try:

    c = a / b

except Exception as e:

    logging.exception("Exception occurred")


ERROR:root:Exception occurred

Traceback (most recent call last):

  File "D:/python/realpython/logging_tutorial.py", line 7, in <module>

    c = a / b

ZeroDivisionError: division by zero


其它方法从debug()到critical()你都可以设置exc_info参数。


logging模块中的类和方法

我们已经知道默认loggerroot,在logging模块中它的方法可以直接调用,如:logging.debug()。你也可以通过创建Logger类定义自己的logger,如果你的项目有多个模块,这特别有用。

logging模块中最常用的类:

Logger:这个类的对象可以直接调用日志记录的方法。

LogRecord:Logger会自动创建LogRecord对象,对象所关联的日志信息将会被记录,如:logger的名称,行号,信息等。

Handler:Handler确定LogRecord的输出目的地,如控制台或文件。Handler有一些特定意义的子类,如:StreamHandler,FileHandler,SMTPHandler,HTTPHandler等,它们表示将logging的输出信息输送到相应的目的地,如 sys.stdout、文件、邮件、网络等。

Formatter:指定格式字符串,列出你想在输出的日志信息中包含的属性。

大多数情况下,使用模块方法logging.getLogger(name)初始化Logger类。使用相同的name多次调用getLogger()返回的是相同的Logger对象,这避免了传递logger对象到每个我们需要的部分。

import logging

logger = logging.getLogger("example_logger")

logger.warning("This is a warning")


This is a warning


上面代码创建了名为example_logger的logger,不像root logger,自定义的logger名称不是默认输出格式的一部分,要想如root logger输出的格式,必须添加配置。

WARNING:example_logger:This is a warning


自定义的logger配置不能使用basicConfig(),你必须使用Handler和Formatter进行配置。

官方文档推荐使用__name__作为getLogger()的参数去创建logger对象,__name__是python中内置变量,其结果为当前模块的名称,这样logger的名称可以直接告诉我们事件的发生位置。


使用Handler

当你创建自己的logger,并想将日志信息发送到多个位置时,Handler可以轻易实现。Handler配置日志的输出目的地,如:标准输出流,文件,HTTP或通过SMTP发送邮件。

一个logger可以创建一个或多个handler,这样你既可以保存信息到日志文件,也可以发送email。

同样,一个logger也可以在多个handler中设置不同的日志级别。例如:你想记录WARNING及以上的日志到控制台,而任何ERROR及以上的日志保存到文件。

import logging

logger = logging.getLogger(__name__)

# 日志信息输出到控制台

c_handler = logging.StreamHandler()

# 日志信息输出到文件

f_handler = logging.FileHandler("file.log")

# WARNING及以上到控制台

c_handler.setLevel(logging.WARNING)

# ERROR及以上到文件

f_handler.setLevel(logging.ERROR)

# 控制台日志显示格式

c_format = logging.Formatter("%(name)s - %(levelname)s - %(message)s")

# 文件日志显示格式

f_format = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")

c_handler.setFormatter(c_format)

f_handler.setFormatter(f_format)

# 为logger添加多个handler

logger.addHandler(c_handler)

logger.addHandler(f_handler)

# 会自动创建相关LogRecord

logger.warning("This is a warning")

logger.error("This is an error")


控制台

__main__ - WARNING - This is a warning

__main__ - ERROR - This is an error


file.log文件

2020-02-08 21:43:17,509 - __main__ - ERROR - This is an error


logger的名称指定为__name__,此时值为__main__。这个名称是执行开始的模块的名称,如果从其它模块引用,这个__name__的值将会是文件名。

import logging_tutorial


我上面代码是在logging_tutorial.py文件中。

logging_tutorial - WARNING - This is a warning

logging_tutorial - ERROR - This is an error


其它配置方式

你可以像上面那样logging,也可以创建一个配置文件或使用一个字典,然后分别使用fileConfig()或dictConfig()加载。这在已运行的服务中特别有用。

[loggers]

keys=root,sampleLogger



[handlers]

keys=consoleHandler



[formatters]

keys=sampleFormatter



[logger_root]

level=DEBUG

handlers=consoleHandler



[logger_sampleLogger]

level=DEBUG

handlers=consoleHandler

qualname=sampleLogger

propagate=0



[handler_consoleHandler]

class=StreamHandler

level=DEBUG

formatter=sampleFormatter

args=(sys.stdout,)



[formatter_sampleFormatter]

format=%(asctime)s - %(name)s - %(levelname)s - %(message)s


上面配置文件,定义了2个logger(root和sampleLogger),一个handler(consoleHandler)和一个formatter(sampleFormatter)。定义是使用下划线来定义的,logger_后面跟名称代表定义了该名称的logger。如logger_root,logger_sampleLogger表示定义了名为root和名为sampleLogger的logger。同理,handler_consoleHandler表示定义了名为consoleHandler的Handler,formatter_sampleFormatter表示定义了名为sampleFormatter的Formatter。在[loggers],[handlers],[formatters]中可以使用这些定义的内容。

使用fileConfig()加载这个配置文件

import logging.config

logging.config.fileConfig(fname="logging.conf", disable_existing_loggers=False)

logger = logging.getLogger(__name__)

logger.debug("This is a debug message")


2020-02-08 22:29:01,880 - __main__ - DEBUG - This is a debug message


配置文件的路径作为参数传递给fileConfig()方法,并且disable_existing_loggers参数用于保留或禁用调用该函数时存在的记录器,默认为True。


另一种是YAML格式配置文件作为字典加载

version: 1formatters:

  simple:

    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'

handlers:

  console:

    class: logging.StreamHandler

    level: DEBUG

    formatter: simple

    stream: ext://sys.stdout

loggers:

  sampleLogger:

    level: DEBUG

    handlers: [console]

    propagate: no

root:

  level: DEBUG

  handlers: [console]


以字典方式加载yaml配置文件

import logging

import logging.config

import yaml

with open('config.yaml', 'r') as f:

    config = yaml.safe_load(f.read())

    logging.config.dictConfig(config)

logger = logging.getLogger(__name__)

logger.debug('This is a debug message')


2020-02-08 22:29:01,880 - __main__ - DEBUG - This is a debug message


总结:

        内置的logging模块已经设置得非常灵活了,开箱即用。你可以直接使用基本的日志功能在小的项目中,也可以完全根据需要创建自己的日志类在大的项目中。

        如果你还没有在你的项目中使用日志功能,现在可以试着开始使用。正确的日志记录可以帮你快速解决日常程序中的问题,也可以提升自己的能力。

  

展开阅读全文