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模块中的类和方法
我们已经知道默认logger是root,在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模块已经设置得非常灵活了,开箱即用。你可以直接使用基本的日志功能在小的项目中,也可以完全根据需要创建自己的日志类在大的项目中。
如果你还没有在你的项目中使用日志功能,现在可以试着开始使用。正确的日志记录可以帮你快速解决日常程序中的问题,也可以提升自己的能力。