第15章-日志记录¶
Python在其标准库中提供了非常强大的日志记录库。很多程序员使用print语句进行调试(包括我自己),但您也可以使用logging进行调试。使用日志记录实际上更干净,因为您不必通过所有代码来删除print语句。在本章中,我们将讨论以下主题:
创建一个简单的记录器
如何从多个模块登录
日志格式
日志配置
在本章结束时,您应该能够自信地为应用程序创建自己的日志。我们开始吧!
创建一个简单的记录器¶
使用日志模块创建日志非常简单和直接。简单地看一段代码然后解释它是最容易的,所以这里有一些代码供您阅读:
import logging
# add filemode="w" to overwrite
logging.basicConfig(filename="sample.log", level=logging.INFO)
logging.debug("This is a debug message")
logging.info("Informational message")
logging.error("An error has happened!")
如您所料,要访问日志模块,必须首先导入它。创建日志的最简单方法是使用日志模块的basicconfig函数并传递一些关键字参数。它接受以下内容:文件名、文件模式、格式、日期格式、级别和流。在我们的示例中,我们将文件名和日志级别传递给它,并将其设置为info。日志记录有五个级别(按升序排列):调试、信息、警告、错误和关键。默认情况下,如果您多次运行此代码,那么如果日志已经存在,它将追加到日志中。如果您希望让日志记录器覆盖日志,则传入 filemode="w" 如代码注释所述。说到运行代码,如果您运行一次代码,应该得到以下结果:
INFO:root:Informational message
ERROR:root:An error has happened!
请注意,调试消息不在输出中。这是因为我们将级别设置为INFO,所以只有当日志是INFO、WARNING、ERROR或CRITICAL消息时,日志记录器才会记录日志。根部分仅仅意味着这个日志消息来自根日志记录器或主日志记录器。我们将了解如何更改,以便在下一节中更具描述性。如果你不使用 基本配置 ,然后日志记录模块将输出到控制台/stdout。
日志模块还可以将一些异常记录到文件或配置为要登录到的任何位置。下面是一个例子:
import logging
logging.basicConfig(filename="sample.log", level=logging.INFO)
log = logging.getLogger("ex")
try:
raise RuntimeError
except RuntimeError:
log.exception("Error!")
让我们把这个分解一下。这里我们用 登录中 模块的 获取记录器 方法返回已命名的记录器对象 ex .当您在一个应用程序中有多个记录器时,这很方便,因为它允许您识别来自每个记录器的消息。此示例将强制 RuntimeError 要引发此问题,请捕获错误并将整个跟踪记录到文件中,这在调试时非常方便。
如何从多个模块登录(以及格式化!)¶
编码越多,最终创建一组共同工作的自定义模块的频率就越高。如果你想让他们都登录到同一个地方,那么你就到了正确的地方。我们将介绍一种简单的方法,然后展示一种更复杂的方法,这种方法也更具可定制性。以下是一个简单的方法:
import logging
import otherMod
def main():
"""
The main entry point of the application
"""
logging.basicConfig(filename="mySnake.log", level=logging.INFO)
logging.info("Program started")
result = otherMod.add(7, 8)
logging.info("Done!")
if __name__ == "__main__":
main()
这里我们导入日志和我们自己创建的模块(“othermod”)。然后我们像以前一样创建日志文件。另一个模块如下所示:
# otherMod.py
import logging
def add(x, y):
""""""
logging.info("added %s and %s to get %s" % (x, y, x+y))
return x+y
如果您运行主代码,那么您应该得到一个包含以下内容的日志:
INFO:root:Program started
INFO:root:added 7 and 8 to get 15
INFO:root:Done!
你看到这样做的问题了吗?您不能很容易地分辨出日志消息来自何处。这只会使写入此日志的模块越多,就越容易混淆。所以我们需要解决这个问题。这使我们了解了创建记录器的复杂方法。让我们看一看不同的实现:
import logging
import otherMod2
def main():
"""
The main entry point of the application
"""
logger = logging.getLogger("exampleApp")
logger.setLevel(logging.INFO)
# create the logging file handler
fh = logging.FileHandler("new_snake.log")
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
# add handler to logger object
logger.addHandler(fh)
logger.info("Program started")
result = otherMod2.add(7, 8)
logger.info("Done!")
if __name__ == "__main__":
main()
这里我们创建一个名为“exampleapp”的记录器实例。我们设置了它的日志级别,创建了一个日志文件处理程序对象和一个日志格式化程序对象。文件处理程序必须将格式化程序对象设置为其格式化程序,然后必须将文件处理程序添加到记录器实例中。其余的代码基本上是相同的。只需注意,它不是“logging.info”,而是“logger.info”或您所调用的logger变量。这是最新的 其他模式2 代码:
# otherMod2.py
import logging
module_logger = logging.getLogger("exampleApp.otherMod2")
def add(x, y):
""""""
logger = logging.getLogger("exampleApp.otherMod2.add")
logger.info("added %s and %s to get %s" % (x, y, x+y))
return x+y
注意,这里定义了两个记录器。在本例中,我们对模块记录器不做任何操作,但我们确实使用了另一个。如果运行主脚本,则应在文件中看到以下输出:
2012-08-02 15:37:40,592 - exampleApp - INFO - Program started
2012-08-02 15:37:40,592 - exampleApp.otherMod2.add - INFO - added 7 and 8 to get 15
2012-08-02 15:37:40,592 - exampleApp - INFO - Done!
您将注意到所有对根目录的引用都已被删除。而是使用我们的 格式化程序 对象,它表示我们应该获得一个人类可读的时间、记录器名称、日志级别,然后是消息。这些实际上被称为 LogRecord 属性。完整的列表 LogRecord 属性, see the documentation 因为这里有太多的东西要列出。
为工作和娱乐配置日志¶
日志模块可以配置3种不同的方式。您可以使用本文前面介绍的方法(记录器、格式化程序、处理程序)对其进行配置;您可以使用配置文件并将其传递给fileconfig();也可以创建配置信息字典并将其传递给dictconfig()函数。我们先创建一个配置文件,然后看看如何用Python执行它。下面是一个配置文件示例:
[loggers]
keys=root,exampleApp
[handlers]
keys=fileHandler, consoleHandler
[formatters]
keys=myFormatter
[logger_root]
level=CRITICAL
handlers=consoleHandler
[logger_exampleApp]
level=INFO
handlers=fileHandler
qualname=exampleApp
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=myFormatter
args=(sys.stdout,)
[handler_fileHandler]
class=FileHandler
formatter=myFormatter
args=("config.log",)
[formatter_myFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=
您会注意到我们指定了两个记录器:root和exampleapp。无论出于什么原因,“根”都是必需的。如果不包括它,python将 ValueError 来自配置PY _install_loggers 函数,它是日志模块的一部分。如果将根的处理程序设置为 文件处理程序 ,然后您将得到双倍的日志输出,因此为了避免这种情况发生,我们将把它发送到控制台。仔细研究这个例子。前三个部分中的每个键都需要一个部分。现在让我们看看如何在代码中加载它:
# log_with_config.py
import logging
import logging.config
import otherMod2
def main():
"""
Based on http://docs.python.org/howto/logging.html#configuring-logging
"""
logging.config.fileConfig('logging.conf')
logger = logging.getLogger("exampleApp")
logger.info("Program started")
result = otherMod2.add(7, 8)
logger.info("Done!")
if __name__ == "__main__":
main()
如您所见,您只需将配置文件路径传递给 logging.config.fileConfig .您还会注意到,我们不再需要所有的设置代码,因为这些都在配置文件中。我们也可以进口 其他模式2 模块无变化。无论如何,如果您运行上述命令,您的日志文件中应该包含以下内容:
2012-08-02 18:23:33,338 - exampleApp - INFO - Program started
2012-08-02 18:23:33,338 - exampleApp.otherMod2.add - INFO - added 7 and 8 to get 15
2012-08-02 18:23:33,338 - exampleApp - INFO - Done!
正如您可能已经猜到的,它与另一个例子非常相似。现在我们将转到另一个配置方法。字典配置方法(dictconfig)直到python 2.7才被添加,因此请确保您拥有它或更高版本,否则您将无法继续使用。它并没有很好地记录这是如何工作的。事实上,文档中的示例显示 YAML 出于某种原因。无论如何,这里有一些工作代码供您查看:
# log_with_config.py
import logging
import logging.config
import otherMod2
def main():
"""
Based on http://docs.python.org/howto/logging.html#configuring-logging
"""
dictLogConfig = {
"version":1,
"handlers":{
"fileHandler":{
"class":"logging.FileHandler",
"formatter":"myFormatter",
"filename":"config2.log"
}
},
"loggers":{
"exampleApp":{
"handlers":["fileHandler"],
"level":"INFO",
}
},
"formatters":{
"myFormatter":{
"format":"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
}
}
}
logging.config.dictConfig(dictLogConfig)
logger = logging.getLogger("exampleApp")
logger.info("Program started")
result = otherMod2.add(7, 8)
logger.info("Done!")
if __name__ == "__main__":
main()
如果运行此代码,最终将得到与前一个方法相同的输出。请注意,使用字典配置时不需要“根”记录器。
总结¶
此时,您应该知道如何开始使用记录器,以及如何以几种不同的方式配置它们。您还应该了解如何使用格式化程序对象修改输出。日志模块对于解决应用程序中的问题非常方便。在编写大型应用程序之前,一定要花一些时间来练习这个模块。
在下一章中,我们将讨论如何使用 os 模块。