# Python 日志教程

> 原文： [http://zetcode.com/python/logging/](http://zetcode.com/python/logging/)

Python 日志教程展示了如何使用日志模块在 Python 中进行日志。

## 日志

日志是将信息写入日志文件的过程。 日志文件包含有关在操作系统，软件或通信中发生的各种事件的信息。

## 记录目的

完成记录是出于以下目的：

*   信息收集
*   故障排除
*   产生统计数据
*   审计
*   性能分析

记录不仅限于识别软件开发中的错误。 它还可用于检测安全事件，监视策略违规，在出现问题时提供信息，查找应用瓶颈或生成使用情况数据。

## 要记录哪些事件

应记录的事件包括输入验证失败，认证和授权失败，应用错误，配置更改以及应用启动和关闭。

## 哪些事件不记录

不应记录的事件包括应用源代码，会话标识值，访问令牌，敏感的个人数据，密码，数据库连接字符串，加密键，银行帐户和持卡人数据。

## 记录最佳做法

以下是进行日志的一些最佳做法：

*   日志应该有意义。
*   日志应包含上下文。
*   日志应在不同级别进行结构化和完成。
*   日志应保持平衡； 它不应包含过多或过多的信息。
*   记录消息应该是人类可以理解的，并且可以被机器解析。
*   记录更复杂的应用应产生几个日志文件。
*   日志应适应开发和生产。

## 日志模块

Python 日志模块定义了实现用于应用和库的灵活事件日志系统的函数和类。

## 日志模块组件

日志模块具有四个主要组件：记录器，处理器，过滤器和格式化程序。 记录器公开了应用代码直接使用的接口。 处理器将日志（由记录器创建）发送到适当的目的地。 过滤器提供了更细粒度的功能，用于确定要输出的日志。 格式化程序在最终输出中指定日志的布局。

## Python 日志层次结构

Python 记录器形成一个层次结构。 名为`main`的记录器是`main.new`的父级。

子记录器将消息传播到与其祖先记录器关联的处理器。 因此，不必为应用中的所有记录器定义和配置处理器。 为顶级记录器配置处理器并根据需要创建子记录器就足够了。

## Python 日志级别

级别用于标识事件的严重性。 有六个日志级别：

*   `CRITICAL`
*   `ERROR`
*   `WARNING`
*   `INFO`
*   `DEBUG`
*   `NOTSET`

如果日志级别设置为`WARNING`，则所有`WARNING`，`ERROR`和`CRITICAL`消息都将写入日志文件或控制台。 如果将其设置为`ERROR`，则仅记录`ERROR`和`CRITICAL`消息。

记录器的概念是有效级别。 如果未在记录器上显式设置级别，则将其父级别用作其有效级别。 如果父级没有显式设置的级别，则检查其父级，依此类推-搜索所有祖先，直到找到显式设置的级别。

使用`getLogger()`创建记录器时，级别设置为`NOTSET`。 如果未使用`setLevel()`显式设置日志级别，则消息将传播到记录器父级。 遍历记录器的祖先记录器链，直到找到具有`NOTSET`以外级别的祖先或到达根。 根记录器具有默认的`WARNING`级别设置。

## 根记录器

所有记录器都是根记录器的后代。 每个记录器将日志消息传递到其父级。 使用`getLogger(name)`方法创建新的记录器。 调用不带名称的函数（`getLogger()`）将返回根记录器。

根记录器始终具有显式级别集，默认情况下为`WARNING`。

根发信人位于层次结构的顶部，即使未配置，也始终存在。 通常，程序或库不应直接登录到根记录器。 而是应配置该程序的特定记录器。 根记录器可用于轻松打开和关闭所有库中的所有记录器。

## Python 日志简单示例

`logging`模块具有简单的方法，可以立即使用而无需任何配置。 这可以用于简单的日志。

`simple.py`

```py
#!/usr/bin/env python

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

```

该示例调用`logging`模块的五个方法。 消息将写入控制台。

```py
$ simple.py
WARNING:root:This is a warning message
ERROR:root:This is an error message
CRITICAL:root:This is a critical message

```

请注意，使用了根记录器，并且只写入了三则消息。 这是因为默认情况下，仅写入具有级别警告和更高级别的消息。

## Python 设置日志级别

记录级别由`setLevel()`设置。 它将此记录器的阈值设置为`lvl`。 严重性不及`lvl`的日志消息将被忽略。

`set_level.py`

```py
#!/usr/bin/env python

import logging

logger = logging.getLogger('dev')
logger.setLevel(logging.DEBUG)

logger.debug('This is a debug message')
logger.info('This is an info message')
logger.warning('This is a warning message')
logger.error('This is an error message')
logger.critical('This is a critical message')

```

在示例中，我们将日志级别更改为`DEBUG`。

```py
logger = logging.getLogger('dev')

```

`getLogger()`返回具有指定名称的记录器。 如果名称为`None`，则返回根记录器。 名称可以是点分隔的字符串，用于定义日志层次结构。 例如`"a"`，`"a.b"`或`"a.b.c"`。 请注意，有一个隐式根名，未显示。

```py
$ set_level.py
This is a warning message
This is an error message
This is a critical message

```

现在，所有消息均已写入。

## Python 有效日志级别

有效日志级别是显式设置的级别或由记录器父级确定的级别。

`effective_level.py`

```py
#!/usr/bin/env python

import logging

main_logger = logging.getLogger('main')
main_logger.setLevel(5)

dev_logger = logging.getLogger('main.dev')

print(main_logger.getEffectiveLevel())
print(dev_logger.getEffectiveLevel())

```

在示例中，我们检查了两个记录器的有效记录级别。

```py
dev_logger = logging.getLogger('main.dev')

```

未设置`dev_logger`的电平； 然后使用其父级。

```py
$ effective_level.py
5
5

```

这是输出。

## Python 日志处理器

处理器是一个对象，负责将适当的日志消息（基于日志消息的严重性）调度到处理器的指定目标。

处理器像级别一样传播。 如果记录器未设置处理器，则其祖先链将搜索处理器。

`handlers.py`

```py
#!/usr/bin/env python

import logging

logger = logging.getLogger('dev')
logger.setLevel(logging.INFO)

fileHandler = logging.FileHandler('test.log')
fileHandler.setLevel(logging.INFO)

consoleHandler = logging.StreamHandler()
consoleHandler.setLevel(logging.INFO)

logger.addHandler(fileHandler)
logger.addHandler(consoleHandler)

logger.info('information message')

```

该示例为记录器创建两个处理器：文件处理器和控制台处理器。

```py
fileHandler = logging.FileHandler('test.log')

```

`FileHandler`将日志发送到`test.log`文件。

```py
consoleHandler = logging.StreamHandler()

```

`StreamHandler`将日志发送到流。 如果未指定流，则使用`sys.stderr`。

```py
logger.addHandler(fileHandler)

```

该处理器将通过`addHandler()`添加到记录器。

## Python 日志格式化程序

格式化程序是一个对象，用于配置日志的最终顺序，结构和内容。 除消息字符串外，日志还包括日期和时间，日志名称和日志级别严重性。

`formatter.py`

```py
#!/usr/bin/env python

import logging

logger = logging.getLogger('dev')
logger.setLevel(logging.INFO)

consoleHandler = logging.StreamHandler()
consoleHandler.setLevel(logging.INFO)

logger.addHandler(consoleHandler)

formatter = logging.Formatter('%(asctime)s  %(name)s  %(levelname)s: %(message)s')
consoleHandler.setFormatter(formatter)

logger.info('information message')

```

该示例创建一个控制台记录器，并将格式化程序添加到其处理器。

```py
formatter = logging.Formatter('%(asctime)s  %(name)s  %(levelname)s: %(message)s')

```

格式化程序已创建。 它包括日期时间，记录器名称，记录级别名称和记录消息。

```py
consoleHandler.setFormatter(formatter)

```

格式化程序通过`setFormatter()`设置为处理器。

```py
$ formatter.py
2019-03-28 14:53:27,446  dev  INFO: information message

```

具有定义格式的消息显示在控制台中。

## Python 日志`basicConfig`

`basicConfig()`配置根记录器。 它通过使用默认格式化程序创建流处理器来为日志系统进行基本配置。 如果没有为根记录器定义处理器，则`debug()`，`info()`，`warning()`，`error()`和`critical()`自动调用`basicConfig()`。

`basic_config.py`

```py
#!/usr/bin/env python

import logging

logging.basicConfig(filename='test.log', format='%(filename)s: %(message)s',
                    level=logging.DEBUG)

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

```

该示例使用`basicConfig`配置根记录器。

```py
logging.basicConfig(filename='test.log', format='%(filename)s: %(message)s',
    level=logging.DEBUG)

```

使用`filename`，我们设置要写入日志消息的文件。 `format`确定将什么内容记录到文件中； 我们有文件名和消息。 使用`level`，设置记录阈值。

```py
$ basic_config.py
$ cat test.log
basic_config.py: This is a debug message
basic_config.py: This is an info message
basic_config.py: This is a warning message
basic_config.py: This is an error message
basic_config.py: This is a critical message

```

运行该程序后，我们将五条消息写入`test.log`文件。

## Python 日志文件配置

`fileConfig()`从`configparser`格式文件中读取日志配置。

`log.conf`

```py
[loggers]
keys=root,dev

[handlers]
keys=consoleHandler

[formatters]
keys=extend,simple

[logger_root]
level=INFO
handlers=consoleHandler

[logger_dev]
level=INFO
handlers=consoleHandler
qualname=dev
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=extend
args=(sys.stdout,)

[formatter_extend]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s

[formatter_simple]
format=%(asctime)s - %(message)s

```

`log.conf`定义了记录器，处理器和格式化程序。

`file_config.py`

```py
#!/usr/bin/env python

import logging
import logging.config

logging.config.fileConfig(fname='log.conf')

logger = logging.getLogger('dev')
logger.info('This is an information message')

```

该示例从`log.conf`读取日志配置文件。

```py
$ file_config.py
2019-03-28 15:26:31,137 - dev - INFO - This is an information message

```

这是输出。

## Python 日志变量

通过使用字符串格式记录动态数据。

`log_variable.py`

```py
#!/usr/bin/env python

import logging

root = logging.getLogger()
root.setLevel(logging.INFO)

log_format = '%(asctime)s %(filename)s: %(message)s'
logging.basicConfig(filename="test.log", format=log_format)

# incident happens

error_message = 'authentication failed'

root.error(f'error: {error_message}')

```

该示例将自定义数据写入日志消息。

```py
2019-03-21 14:17:23,196 log_variable.py: error: authentication failed

```

这是日志消息。

## Python 日志格式化日期时间

日期时间包含在`asctime`日志的日志消息中。 使用`datefmt`配置选项，我们可以格式化日期时间字符串。

`date_time.py`

```py
#!/usr/bin/env python

import logging

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

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

logger.info("information message")

```

该示例格式化日志消息的日期时间。

```py
log_format = '%(asctime)s %(filename)s: %(message)s'

```

我们将日期时间字符串包含在`asctime`中。

```py
logging.basicConfig(filename="test.log", format=log_format,
                    datefmt='%Y-%m-%d %H:%M:%S')

```

`datefmt`选项格式化日期时间字符串。

```py
2019-03-21 14:17:23,196 log_variable.py: error: authentication failed
2019-03-21 14:23:33 date_time.py: information message

```

注意日期时间字符串格式的不同。

## Python 日志栈跟踪

栈跟踪是调用函数的栈，这些函数一直运行到引发异常时为止。 栈跟踪包含在`exc_info`选项中。

`stack_trace.py`

```py
#!/usr/bin/env python

import logging

log_format = '%(asctime)s %(filename)s: %(message)s'
logging.basicConfig(filename="test.log", format=log_format)

vals = [1, 2]

try:
    print(vals[4])

except Exception as e:
    logging.error("exception occurred", exc_info=True)

```

在该示例中，我们记录了尝试访问不存在的列表索引时引发的异常。

```py
logging.error("exception occurred", exc_info=True)

```

通过将`exc_info`设置为`True`，栈跟踪将包含在日志中。

```py
2019-03-21 14:56:21,313 stack_trace.py: exception occurred
Traceback (most recent call last):
  File "C:\Users\Jano\Documents\pyprogs\pylog\stack_trace.py", line 11, in <module>
    print(vals[4])
IndexError: list index out of range

```

栈跟踪包含在日志中。

## Python 日志`getLogger`

`getLogger()`返回具有指定名称的记录器。 如果未指定名称，则返回根记录器。 在`__name__`中放置模块名称是一种常见的做法。

使用给定名称对该函数的所有调用均返回相同的记录器实例。 这意味着记录器实例永远不需要在应用的不同部分之间传递。

`get_logger.py`

```py
#!/usr/bin/env python

import logging
import sys

main = logging.getLogger('main')
main.setLevel(logging.DEBUG)

handler = logging.FileHandler('my.log')

format = logging.Formatter('%(asctime)s  %(name)s
    %(levelname)s: %(message)s')
handler.setFormatter(format)

main.addHandler(handler)

main.info('info message')
main.critical('critical message')
main.debug('debug message')
main.warning('warning message')
main.error('error message')

```

该示例使用`getLogger()`创建一个新的记录器。 它有一个文件处理器和一个格式化程序。

```py
main = logging.getLogger('main')
main.setLevel(logging.DEBUG)

```

创建了一个名为`main`的记录器； 我们将日志级别设置为`DEBUG`。

```py
handler = logging.FileHandler('my.log')

```

创建一个文件处理器。 消息将被写入`my.log`文件。

```py
format = logging.Formatter('%(asctime)s  %(name)s
    %(levelname)s: %(message)s')
handler.setFormatter(format)

```

格式化程序已创建。 它包括时间，记录器名称，记录级别以及要记录的消息。 格式化程序通过`setFormatter()`设置为处理器。

```py
main.addHandler(handler)

```

该处理器将通过`addHandler()`添加到记录器。

```py
$ cat my.log
2019-03-21 14:15:45,439  main  INFO: info message
2019-03-21 14:15:45,439  main  CRITICAL: critical message
2019-03-21 14:15:45,439  main  DEBUG: debug message
2019-03-21 14:15:45,439  main  WARNING: warning message
2019-03-21 14:15:45,439  main  ERROR: error message

```

这些是书面的日志消息。

## Python 日志 YAML 配置

日志详细信息可以在 YAML 配置文件中定义。 YAML 是一种人类可读的数据序列化语言。 它通常用于配置文件。

```py
$ pip install pyyaml

```

我们需要安装`pyyaml`模块。

`config.yaml`

```py
version: 1

formatters:
  simple:
    format: "%(asctime)s %(name)s: %(message)s"
  extended:
    format: "%(asctime)s %(name)s %(levelname)s: %(message)s"

handlers:
  console:
    class: logging.StreamHandler
    level: INFO
    formatter: simple

  file_handler:
    class: logging.FileHandler
    level: INFO
    filename: test.log
    formatter: extended
    propagate: false

loggers:
  dev:
    handlers: [console, file_handler]
  test:
    handlers: [file_handler]
root:
  handlers: [file_handler]

```

在配置文件中，我们定义了各种格式化程序，处理器和记录器。 `propagate`选项可防止将日志消息传播到父级记录器。 就我们而言，是根记录器。 否则，消息将被复制。

`log_yaml.py`

```py
#!/usr/bin/env python

import logging
import logging.config
import yaml

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

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

logging.config.dictConfig(log_cfg)

logger = logging.getLogger('dev')
logger.setLevel(logging.INFO)

logger.info('This is an info message')
logger.error('This is an error message')

```

在示例中，我们读取配置文件并使用`dev`记录器。

```py
$ log_yaml.py
2019-03-28 11:36:54,854 dev: This is an info message
2019-03-28 11:36:54,855 dev: This is an error message

```

当我们运行程序时，控制台上有两条消息。 控制台处理器使用带有较少信息的简单格式化程序。

```py
...
2019-03-28 11:36:54,854 dev INFO: This is an info message
2019-03-28 11:36:54,855 dev ERROR: This is an error message

```

`test.log`文件中有日志消息。 它们由扩展的格式化程序提供，具有更多信息。

在本教程中，我们使用了 Python 日志库。 您可能也对相关教程感兴趣： [Python Jinja 教程](/python/jinja/)， [Bottle 教程](/python/bottle/)， [Python 教程](/lang/python/)或列表 [Python 教程](/all/#python)。