demo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from loguru import logger

# 添加log
logger.debug("That's it, beautiful and simple logging!")

# handler, 设置处理方式
logger.add(sys.stderr, format="{time} {level} {message}", filter="my_module", level="INFO")
logger.add("file_{time}.log")
# 写入文件方式
logger.add("file_1.log", rotation="500 MB")    # Automatically rotate too big file
logger.add("file_2.log", rotation="12:00")     # New file is created each day at noon
logger.add("file_3.log", rotation="1 week")    # Once the file is too old, it's rotated

logger.add("file_X.log", retention="10 days")  # Cleanup after some time

logger.add("file_Y.log", compression="zip")    # Save some loved space

# 类似 format 的格式
logger.info("If you're using Python {}, prefer {feature} of course!", 3.6, feature="f-strings")

# context 形式捕捉 log
@logger.catch
def my_function(x, y, z):
    # An error? It's caught anyway!
    return 1 / (x + y + z)

# 支持 html 标签格式化
logger.add(sys.stdout, colorize=True, format="<green>{time}</green> <level>{message}</level>")

# 为 backtrace 异常调用栈,单独设置 log handler
logger.add("out.log", backtrace=True, diagnose=True)  # Caution, may leak sensitive data in prod

概念

Sink

参考:

解释:

The sink handles incoming log messages and proceed to their writing somewhere and somehow. A sink can take many forms:

  • 管理进来的 log
  • 处理 log,如何写,如何处理

特点,功能

提交 log

不需要指定 logger 名称

  • 直接使用 logger.info(), logger.debug() 等搞定
1
2
3
4
5
from loguru import logger

def greet():
  logger.info("Called greet")
  print("Hey there!")

str.format 格式(f-string)提交 log

1
logger.info("If you're using Python {}, prefer {feature} of course!", 3.6, feature="f-strings")

exception 异常自动捕获

方法: 通过 decorator 以 contextmanager 方式实现

1
2
3
4
@logger.catch
def my_function(x, y, z):
    # An error? It's caught anyway!
    return 1 / (x + y + z)

sink (handler)

不需要 handler, formatter, filter

  • 直接使用 logger.add() 函数搞定
1
logger.add(sys.stderr, format="{time} {level} {message}", filter="my_module", level="INFO")
  • 删除 handler, logger.remove()

日志文件处理 rotation, retention, compression

参考:

相关参数:

  • first position argument
  • rotation

    • 日志轮转规则
    • 取值:

      • int: 文件大小(bytes)
      • datetime.timedelta: 时间间隔
      • datetime.time: 每日轮转的具体时刻
      • str: human-readable, eg: '1 week 3 days', '18:00'
      • callable: 回调函数
  • retention

    • 保留日志文件的规则
    • 取值:

      • int: 保留几个
      • datetime.timedelta: 最大保留时长(多久后删除)
      • str: human readable 文字指定时长

        • eg: '2 months'
      • callable: 回调函数判定
  • compression

    • 日志轮转时,执行压缩使用的格式
    • 取值:

      • str: eg: gz, bz2, xz, …

formatter 颜色设置

参考:

demo:

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

变量:

  • 时间: {time}
  • level: {level}
  • 消息: {message}

颜色标签:

  • 前景色 foregroud color

    • <red>, <r>
  • 背景色 background color

    • <RED>, <R>
  • 16 进制颜色

    • foregroud: <fg #0000ff>
    • background: <bg #ffee00>

强调标签:

  • Bold: <bold>, <b>
  • Underline: <underline>, <u>

多进程和多线程 multiprocessing and threading

注意:loguru 默认特征

  • 线程安全
  • 进程不安全

多进程支持

使用 logger.add() 中的 enqueue=True 参数

demo:

1
logger.add("somefile.log", enqueue=True)

异常栈支持

参考:

方法:

  1. logger.exception()

    • 类似 logging.exception
  2. 通过 logger.add() 中的参数 diagnose=True

    • 通过 better_exceptions 模块实现
    • 对 debug 更有利,打印异常发生时的变量数值
    • demo:

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      
      logger.add("out.log", backtrace=True, diagnose=True)  # Caution, may leak sensitive data in prod
      
      def func(a, b):
          return a / b
      
      def nested(c):
          try:
              func(5, c)
          except ZeroDivisionError:
              logger.exception("What?!")
      
      nested(0)

如何便于解析

参考:

json 序列化

解释:

  • 序列化 message 内容成为 json

方法:

  • 通过 logger.add() 参数 serialize=True
1
logger.add(custom_sink_function, serialize=True)

设置上下文变量

解释:

  • 向 logger 传递自定义的上下文变量

方法:

  • 设置变量:

    • 通过 logger.bind() 设置变量
    • 通过 logger.bind().info() ,即时设置,即时使用变量
    • 使用 logger.contextualize(var=value)

      • eg:

        1
        2
        3
        
        with logger.contextualize(task=task_id):
            do_something()
            logger.info("End of task")
    • 通过 logger.patch() 修改 record['extra']

      1
      2
      
      logger.add(sys.stderr, format="{extra[utc]} {message}")
      logger = logger.patch(lambda record: record["extra"].update(utc=datetime.utcnow()))
  • 接收变量:

    • 通过 logger.add() 设置 format, 接收变量
    • 通过 logger.bind().info() 接收临时的变量
  • 变量的提取格式

    • '{extra[my_var]}'
    • record['extra']

      • 用于 filter
      • eg:

        1
        2
        3
        
        logger.add("special.log", filter=lambda record: "special" in record["extra"])
        logger.debug("This message is not logged to the file")
        logger.bind(special=True).info("This message, though, is logged to the file!")

demo:

1
2
3
4
5
6
logger.add("file.log", format="{extra[ip]} {extra[user]} {message}")

context_logger = logger.bind(ip="192.168.0.1", user="someone")
context_logger.info("Contextualize your logger easily")
context_logger.bind(user="someone_else").info("Inline binding of extra attribute")
context_logger.info("Use kwargs to add context during formatting: {user}", user="anybody")

执行耗时函数

参考:

eg:

1
logger.opt(lazy=True).debug("If sink level <= DEBUG: {x}", x=lambda: expensive_function(2**64))

通过 python dict 配置

参考:

方法:

  • 通过 logger.configure() 实现

eg:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# For scripts
config = {
    "handlers": [
        {"sink": sys.stdout, "format": "{time} - {message}"},
        {"sink": "file.log", "serialize": True},
    ],
    "extra": {"user": "someone"}
}
logger.configure(**config)

# For libraries
logger.disable("my_library")
logger.info("No matter added sinks, this message is not displayed")
logger.enable("my_library")
logger.info("This message however is propagated to the sinks")

与 python logging 兼容

参考:

logging 为主

1
2
3
4
5
class PropagateHandler(logging.Handler):
    def emit(self, record):
        logging.getLogger(record.name).handle(record)

logger.add(PropagateHandler(), format="{message}")
  • 把 log 从 loguru 引流到 logging

loguru 为主

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class InterceptHandler(logging.Handler):
    def emit(self, record):
        # Get corresponding Loguru level if it exists
        try:
            level = logger.level(record.levelname).name
        except ValueError:
            level = record.levelno

        # Find caller from where originated the logged message
        frame, depth = logging.currentframe(), 2
        while frame.f_code.co_filename == logging.__file__:
            frame = frame.f_back
            depth += 1

        logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())

logging.basicConfig(handlers=[InterceptHandler()], level=0)
  • 把 logging 中的 log, 引流到 loguru 中

logging 只是作为 loguru 的一个 sink

1
2
handler = logging.handlers.SysLogHandler(address=('localhost', 514))
logger.add(handler)

基础功能

levels, Log 等级

参考:

levels:

  • trace
  • debug
  • info
  • success
  • warning
  • error
  • critical

自定义 level

参考

1
2
3
new_level = logger.level("SNAKY", no=38, color="<yellow>", icon="🐍")

logger.log("SNAKY", "Here we go!")

record dict, Log 单条记录

参考: