Why
- 更简单的日志:不再是散乱的记录而是以发生在上下文中的事件为依据。
- 数据绑定:在 structlog 中 log 记录其实是字典,所以可以绑定和重绑定键值对到你的 loggers 中。
- 强大的管道功能:提供简单而且强大的数据处理能力。
- 格式化:本地开发时的键值对形式高亮显示;容易解析的 JSON 格式;或者其他的一些格式。
- 输出:内置高亮打印;与标准库灵活结合;无需格式化为 string。
Installation
可以通过 pip 命令快速地安装 structlog:
1 | $ pip install structlog |
如果想要获得彩色的输出效果,使用以下命令安装:
1 | $ pip install structlog colorama |
First Log Entry
一个最简单的用例如下:
1 | import structlog |
上述例子可以充分展现 structlog 的优点:
- 输出是标准化的,而不是直接抛出到用户面前。
- 所有的 keyword 都使用了
structlog.dev.ConsoleRenderer
格式化。反之,使用 repr() 去把所有的 value 序列化成 strings。因此,很容易给日志添加对自定义对象的支持。
Building a Context
假设有一个 web 应用想要使用上面的 API 打印出相关的数据:
1 | from structlog import get_logger |
这些调用本身是漂亮而且直至目的的,但是每一次调用都在写重复的代码(参数 user_agent, peer_ip)。为了减少重复代码,你可能想到如下的闭包:
1 | def log_closure(event): |
在 view() 中添加了该函数后问题是否解决了呢?如果参数是逐步增加的呢?你又是否真的想要在你的每一个 view() 中都包含一个闭包?
structlog 提供了一个更好的方法:
1 | from structlog import get_logger |
对 structlog 来说,一条 log 记录就是一个称为 event dict[ionary] 的字典:
- 你可以预建这个字典的一部分。这些预先保存的 value 称为 context。
- 在打印一个 event 时,它会与 context 合并然后打印出。
- 如果你不喜欢预建 context 的概念,不去使用就可以了。基础的键值对 logging 也足够的方便。
- 推荐的使用值绑定(binding values)方式:为每一个新的 context 创建一个新的 logger。
Manipulating Log Entries in Flight
由于在 structlog 中 log event 是字典,比起简单的 string 也更容易操作。
structlog 中有 processor chains 的概念。一个 processor 就像一个接受 event 字典和两个其他参数然后返回一个新的 event 字典的函数。链(chain)中的下一个 processor 接受这个返回的字典而不是原始的那个字典。
假如你想要在每个 event dict 中添加一个时间戳。它的 processor 如下:
1 | import datetime |
接下来你需要配置它:
1 | structlog.configure(processors=[timestamper, structlog.processors.KeyValueRenderer()]) |
Rendering
和前面章节你注意到的一样,renderer 也是 processor。值得注意的是,你不一定总是要把 event 字典转换为 string。
假设你想要遵循最佳实践,把 event 字典转换成 JSON。structlog 提供内置的 JSONRenderer:
1 | structlog.configure(processors=[structlog.processors.JSONRenderer()]) |
structlog and Standard Library’s logging
structlog 的应用目的不是打印日志,而是为了包装原本已存在的 loggers 并且添加结构和增量上下文(incremental context building)到其中。所以你可以在任何你喜欢的 logger 中使用 structlog。
原本已存在的 logger 最典型的例子无疑是标准库的 logging 模块。structlog 也提供了一些工具去使得这个最普遍的例子尽量简单:
1 | import logging |
换句话说,你告诉 structlog 你将使用标准库 logger factory,然后像之前一样调用 get_logger() 。