$ ls ~yifei/notes/

在 Python 中打印日志

Posted on:

Last modified:

日志的作用

  • 记录程序的运行情况,比如是否发生错误等。
  • 记录业务相关的信息,可以用于后续分析。

2021 更新--使用 loguru

在 docker 和 k8s 的时代,最佳实践是把日志直接打到 stderr, 再也不需要打到文件或者 socket 中了,所以标准库的 logging 模块已经显得有些过于复杂了。 如果要使用 logging 模块,还得理解什么 handler, config, fomatter 之类的概念,而使用第三方库 loguru 则简单多了,直接像 print 一样用就好了。

loguru

loguru 中直接先定了,只有一个 logger,所以也就不用自己去实例化了,直接 import 进来用就好了。

from loguru import logger
logger.debug("Hello, loguru")

如果需要设置 format 等等的话,可以使用 add 函数:

logger.add(sys.stderr, format="{time} {level} {message}", filter="my_module", level="INFO")

在 loguru 中,推荐使用 str.format 方法进行格式化,这样的好处是使用具名参数比较方便:

logger.info("foo is {foo}", foo=3.6)  # 还是要注意不要直接格式化,避免性能损失

原生 logging 在子线程中的异常经常捕获不到,通过 logger.catch 装饰器,可以保证捕获到子线程和 main 种的任何异常。

@logger.catch
def worker():
    1 / 0

loguru 还支持打印彩色的日志:

logger.add(sys.stderr, colorize=True, format="<green>{time}</green> <level>message</level>")

loguru 还使用了 better_exceptions, 打印的日志更加容易理解。

loguru 还可以打结构化的日志,这样相对于纯文本日志方便于解析和索引:

```py
logger.add(sys.stderr, serialize=True)

------以下为标准库 logging 的教程------

基础用法

日志应该看作是事件流,不要自己管理日志,把日志输出到 stdout。如果使用 systemd 来运行程序的话,直接把所有日志打印到 stdout 就可以了。

logging 模块是 thread safe 的,至少理论上来说是的。..

直接使用 logging 的方法来打印日志

# basicConfig 只能在主线程中调用一次
logging.basicConfig(level=level, format=fmt_string, filename=xxx, datefmt=datefmt)

logging.debug("%string", args)  # send to format string as message

生成 logger 再打日志

import logging

log = logging.getLogger(__name__)

log.setLevel(logging.INFO)  # 设置 logger 的级别

handler = logging.NullHandler()
handler.setLevel(logging.INFO)  # 设置 handler 的级别

formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)

log.addHandler(handler)

if __name__ == "__main__":
    main()

如果需要打印异常的话,一定要用 logging.exception,不要用 logging.error,这样还可以打印出异常堆栈。

进阶教程

logging 模块提供了四种不同的模块

  • Loggers 打日志
  • Handlers 把 logger 的日志发送到该处理的地方
  • Filters filter 过滤出需要的日志
  • Formatters 确定需要打印的日志的格式

logger 是一棵树,默认的 logger 是 root logger,logging 模块的方法也都是在调用这个 logger

在日志中添加额外信息

可以使用 logging.LoggerAdapter

class CustomAdapter(logging.LoggerAdapter):
    """
    This example adapter expects the passed in dict-like object to have a
    "connid" key, whose value in brackets is prepended to the log message.
    """
    def process(self, msg, kwargs):
        return "[%s] %s" % (self.extra["connid"], msg), kwargs

logger = logging.getLogger(__name__)
adapter = CustomAdapter(logger, {"connid": some_conn_id})

在编写的库中打印日志

库只应该定义自己的日志的格式,而不应该定义自己的日志如何输出,日志输出应该由最终的使用程序来定义。

get Logger

You should use the factory method logging.getLogger(name) to instaniate a logger object, the name is supposed to be foo.bar.baz, so the recommnend value is __name__,That’s because in a module, __name__ is the module’s name in the Python package namespace.

in lib.py

logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())

logger.debug("")

in app.py

logging.basicConfig()  # seems like logging.start... must be called to enable logging

关闭第三方日志

logging.getLogger("requests").setLevel(logging.WARNING)

参考

  1. http://docs.python-guide.org/en/latest/writing/logging/
  2. https://docs.python.org/3/howto/logging-cookbook.html#using-loggeradapters-to-impart-contextual-information
  3. https://github.com/Delgan/loguru

© 2016-2022 Yifei Kong. Powered by ynotes

All contents are under the CC-BY-NC-SA license, if not otherwise specified.

Opinions expressed here are solely my own and do not express the views or opinions of my employer.