Posted on:
Last modified:
应该用日志讲一个故事,关于程序执行过程中每一步都在做什么的故事。不要把解释性的东西放在 注释里,日志是动态的注释,通过阅读日志上下文,应该可以明白程序在做什么。要不要打日志, 以及打哪些日志的检验标准只有一个,即这条日志是不是故事的必备材料。
在 docker 和 k8s 的时代,最佳实践是把日志直接打到 stderr, 再也不需要打到文件或者 socket 中了,所以标准库的 logging 模块已经显得有些过于复杂了。如果要使用 logging 模块,还得理解 什么 handler, config, formatter 之类的概念,而使用第三方库 loguru 则简单多了,直接像 print 一样用就好了。
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 还可以打结构化的日志,这样相对于纯文本日志方便于解析和索引:
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 模块提供了四种不同的模块
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})
库只应该定义自己的日志的格式,而不应该定义自己的日志如何输出,日志输出应该由最终的使用 程序来定义。
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.
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
logger.debug("")
logging.basicConfig() # seems like logging.start... must be called to enable logging
logging.getLogger("requests").setLevel(logging.WARNING)
© 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.
友情链接: MySQL 教程站