geekdoc-python-zh/docs/pythonlibrary/python-101-an-intro-to-logg...

12 KiB
Raw Permalink Blame History

Python 101:日志介绍

原文:https://www.blog.pythonlibrary.org/2012/08/02/python-101-an-intro-to-logging/

Python 在其标准库中提供了一个非常强大的日志库。很多程序员使用 print 语句进行调试(包括我自己),但是你也可以使用日志来做这件事。使用日志记录实际上更干净,因为您不必遍历所有代码来删除 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、警告、错误或关键消息。部分仅仅意味着这个日志消息来自根日志记录器或主日志记录器。我们将在下一节中探讨如何改变这一点,以便更好地描述。如果不使用 basicConfig ,那么日志模块将输出到 console / stdout。

日志模块还可以将一些异常记录到文件中,或者记录到您配置的任何地方。这里有一个例子:

import logging

logging.basicConfig(filename="sample.log", level=logging.INFO)
log = logging.getLogger("ex")

try:
    raise RuntimeError
except Exception, err:
    log.exception("Error!")

这将把整个回溯记录到文件中,这在调试时非常方便。

如何从多个模块登录(以及格式化!)

您编写的代码越多,您就越有可能创建一组共同工作的定制模块。如果您希望它们都登录到同一个位置,那么您来对地方了。我们将看一下简单的方法,然后展示一个更复杂的方法,它也更具可定制性。这里有一个简单的方法:

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”的记录器实例。我们设置它的日志级别创建一个日志文件处理程序对象和一个日志格式化程序对象。文件处理程序必须将 formatter 对象设置为其格式化程序,然后文件处理程序必须添加到 logger 实例中。中的其余代码大部分都是一样的。请注意它不是“logging.info ”,而是“logger.info”或任何您称之为 logger 变量的东西。以下是更新后的 otherMod2 代码:

# 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!

你会注意到,我们不再有任何参考根已被删除。相反,它使用我们的 Formatter 对象,该对象表示我们应该获得人类可读的时间、记录器名称、日志记录级别,然后是消息。这些实际上被称为日志记录属性。关于日志记录属性的完整列表,请参见文档,因为这里列出的太多了。

为工作和娱乐配置日志

日志模块可以通过 3 种不同的方式进行配置。您可以像我们在本文前面所做的那样,使用方法(记录器、格式化器、处理程序)来配置它;您可以使用一个配置文件,并将其传递给 file config();或者您可以创建一个配置信息字典,并将其传递给 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。无论什么原因都需要“root”。如果不包含它Python 会从 config.py 的 _install_loggers 函数中抛出一个 ValueError该函数是日志模块的一部分。如果您将 root 的处理程序设置为 fileHandler ,那么您将导致日志输出加倍,因此为了防止这种情况发生,我们将它发送到控制台。仔细研究这个例子。前三个部分中的每个键都需要一个部分。现在让我们看看如何在代码中加载它:

# 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 . file config。您还会注意到,我们不再需要所有的设置代码,因为它们都在配置文件中。我们也可以直接导入 otherMod2 模块,不做任何改动。无论如何,如果您运行以上内容,您的日志文件中应该会出现以下内容:

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!

您可能已经猜到了,它与另一个示例非常相似。现在我们将转到另一个配置方法。直到 Python 2.7 才添加了字典配置方法(dictConfig ),所以请确保您拥有该方法或更好的方法,否则您将无法跟上。没有很好地记录这是如何工作的。事实上,出于某种原因,文档中的示例显示了 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()

如果您运行这段代码,您将得到与前一个方法相同的输出。注意,当您使用字典配置时,您不需要“根”记录器。

包扎

至此,您应该知道如何开始使用记录器,以及如何以几种不同的方式配置它们。您还应该已经了解了如何使用 Formatter 对象修改输出。如果你真的想了解输出,我建议你查看下面的一些链接。

附加阅读

源代码