Loguru的说明书

本文是Loguru的doc的阅读笔记,原文链接为:原文链接

Loguru 是一个替代 Python logging 的第三方库:简单易用且功能强大。

Loguru 仅使用一个全局 logger 实例

  • 在整个进程中,无需创建多个 logger 实例,而是使用一个预配置的单一 logger,这与Python标准的 logging 库的使用方式不同。
  • 使用一个全局的 logger 可以简化配置,方便使用,您可以在任何地方导入 logger 以直接使用,同时确保日志的格式和处理方法在整个进程中保持一致。
  • 尽管只有一个全局的 logger 对象,但 Loguru 允许用户使用 bind 来添加自定义的上下文属性,并提供了 filter 来区分不同的日志源,并进行不同的处理。

add() 代替了 handlersformattersfilters

add() 方法允许进行以下设置:

  • 输出目标(sink):可以是文件名、文件对象、标准输出、自定义流、远程服务器或数据库。
  • 日志格式:可以自定义日志的显示格式。
  • 日志级别:您可以全局设置,或为每个输出目标设置单独的级别。
  • 过滤器:可以是一个函数或条件表达式,用于决定是否输出日志。
  • 文件的压缩和大小限制:根据时间和文件大小设置自动覆盖轮转,并能自动压缩日志文件。
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

加强的文本格式化

  • 可以直接在日志字符串中使用 {} 来插入变量或者表达式。
  • 日志格式支持富文本颜色编码
from loguru import logger
score = 100.0
user = {'name': 'Ben', 'age': 18}
logger.info("{}, {s}, {name} and {age}", score, s=score, **user)
logger.info("If you're using Python {}, prefer {feature} of course!", 3.9, feature="f-strings")

# Pretty logging with color
logger.add(sys.stdout, colorize=True, format="<green>{time}</green> <level>{message}</level>")

  • 消息的文本和值紧密相连,便于阅读。
  • 花括号内支持表达式,具有灵活性。
  • Loguru 对表达式的求值采用延迟计算,若不满足记录条件,则不进行求值。

用 catch 记录未捕获的异常

  • Loguru 中,catch 是一个装饰器,它可以应用于一个函数上,用以捕获该函数抛出的异常。
  • Loguru 中,catch 可以作为上下文管理器,与 with 一起用于捕获抛出的异常。
from loguru import logger
@logger.catch
def thread_function_with_exeption():
    raise ValueError('An Exeption!')

def foo():
    with logger.catch():
        raise ValueError('An Exception!')

线程安全、多进程、异步日志记录

  • Loguru 中,sink 是线程安全的。
  • 在多进程环境中,如果多个进程尝试写入同一个日志文件,可能会导致数据损坏。为了避免这种情况,可以在使用 add 方法时加上 enqueue=True 参数,这将使得日志消息通过消息队列进行管理。
  • 在高性能应用中,使用 enqueue=True 可以让消息处理在后台线程中执行,从而减少对主应用性能的影响。
  • 如果你的 sink 是一个协程函数,例如,你添加了一个协程来将日志保存到远程服务或数据库,记得在最后调用 loguru.complete() 以确保所有的异步日志任务已完成。
import asyncio
from loguru import logger

async def async_sink(message):
    await async_operation(message)

logger.add(async_sink)

async def main():
    logger.info('my message')
    await logger.complete()

asyncio.run(main())

完整的异常描述

  • 能够完整地记录异常信息,包括堆栈跟踪(stack trace)和触发异常时的变量值
# Caution, "diagnose=True" is the default and may leak sensitive data in prod
logger.add("out.log", backtrace=True, diagnose=True)

def func(a, b):
    return a / b

def nested(c):
    try:
        func(5, c)
    except ZeroDivisionError:
        logger.exception("What?!")

nested(0)

> 2018-07-17 01:38:43.975 | ERROR    | __main__:nested:10 - What?!
> Traceback (most recent call last):
>
>   File "test.py", line 12, in <module>
>     nested(0)
><function nested at 0x7f5c755322f0>
>
> > File "test.py", line 8, in nested
>     func(5, c)
>     │       └ 0
><function func at 0x7f5c79fc2e18>
>
>   File "test.py", line 4, in func
>     return a / b
>            │   └ 0
>5
>
> ZeroDivisionError: division by zero

结构化和序列化

  • 将日志记录序列化为结构化的JSON,以便传递给其他模块进行解析。
logger.add(sys.stdout, serialize=True)
logger.info('Logger initialized')

> {
>   "text": "2024-04-28 15:13:22.256 | INFO     | __main__:<module>:32 - Logger initialized\\n",
>   "record": {
>     "elapsed": {
>       "repr": "0:00:00.244809",
>       "seconds": 0.244809
>     },
>     "exception": null,
>     "extra": {},
>     "file": {
>       "name": "my_test.py",
>       "path": ".../my_test.py"
>     },
>     "function": "<module>",
>     "level": {
>       "icon": "ℹ️",
>       "name": "INFO",
>       "no": 20
>     },
>     "line": 32,
>     "message": "Logger initialized",
>     "module": "my_test",
>     "name": "__main__",
>     "process": {
>       "id": 66324,
>       "name": "MainProcess"
>     },
>     "thread": {
>       "id": 7993007168,
>       "name": "MainThread"
>     },
>     "time": {
>       "repr": "2024-04-28 15:13:22.256298+08:00",
>       "timestamp": 1714288402.256298
>     }
>   }
> }

logger 的上下文管理

  • 可以为日志消息添加额外的上下文信息,这等同于为每一条相关的日志消息增加了一些 Record 属性,这些属性会与日志一起被记录。
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")
  • 通过结合使用 bind()filter,您可以更精确地控制日志输出。
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!")
  • 使用 patch() 方法在写日志之前动态添加属性。
logger.add(sys.stderr, format="{extra[utc]} {message}")
logger = logger.patch(lambda record: record["extra"].update(utc=datetime.utcnow()))
  • 使用 contextualize() 方法临时修改上下文
with logger.contextualize(task=task_id):
	...
    logger.info("End of task")``

👉🏻 注释:在日志 message 中引用上下文数据
需要在自定义格式中,暴露 extra 的字段

# 定义一个函数来创建格式化字符串,需要包含特定的 extra 字段
def custom_format(record):
    task_value = record["extra"].get("task", "N/A")  # 从extra获取task字段,如果没有则返回"N/A"
    return f"{record['time']} {record['level']} {record['message']} | task={task_value}"

logger.add(sink_obj, format=custom_format, level="INFO")

task_id = ...

with logger.contextualize(task=task_id):
    logger.info("End of task")

logger.opt() 方法

  • logger.opt(lazy=True):只有 message 需要输出的时候才去对其求值,这样可以避免一些计算代价比较高的运算。
  • logger.opt(exception=True):在日志消息中添加异常堆栈跟踪。
  • logger.opt(depth=1):改变堆栈跟踪深度
  • logger.opt(color=True):在日志消息中使用颜色。
  • logger.opt(record=True):在日志消息记录线程 ID 等各种属性。
  • logger.opt(raw=True):绕过日志的格式化器直接输出。
  • logger.opt(capture=False):是否将关键字参数自动添加到日志记录的 extra

👉🏻 注释:opt depth=1 的用法
假设有一个装饰器,它用于记录函数的执行时间,并且我们想在日志中记录被装饰函数的名称而不是装饰器内部的函数名称。如下面的例子:如果不用 depth=1,message 的 model 就是 main:wrapper,实际上 wrapper 是返回到 main,由 main 调用。

def my_decorator(func):
    def wrapper(*args, **kwargs):
        logger.info(f"Calling function: {func.__name__}")
        logger.opt(depth=1).info(f"Calling function: {func.__name__}")
        result = func(*args, **kwargs)
        return result
    return wrapper

@my_decorator
def some_function():
    logger.info("Inside some_function")

some_function()

2024-05-06 15:06:25.984 | INFO     | __main__:wrapper:5 - Calling function: some_function
2024-05-06 15:06:25.984 | INFO     | __main__:<module>:15 - Calling function: some_function
2024-05-06 15:06:25.985 | INFO     | __main__:some_function:13 - Inside some_function

Loguru 自定义日志级别

  • 创建一个新的日志级别:new_level = logger.level("SNAKY", no=38, color="<yellow>", icon="🐍")
    • "SNAKY": 新日志级别的名称。
    • no=38: 日志级别的数值。日志级别的数值很重要,因为它决定了日志消息的重要性。数值越低,级别越高。例如,标准的 DEBUG 级别是 10,而 CRITICAL 是 50。
    • color="<yellow>": 在支持颜色的终端中,此级别的日志消息将以黄色显示。
    • icon="🐍": 这个级别的日志消息将以蛇形图标(🐍)作为前缀。
  • 使用自定义的日志级别:logger.log("SNAKY", "Here we go!")
  • Loguru 增加了两个非标准的级别:tracesuccess
    • trace 级别在日志级别层次中处于最低端,低于 DEBUG 级别,用于输出极其详细的执行信息的情况,比如复杂的问题排查(troubleshooting)和调试,可以使用它来记录函数的详细调用参数、返回值或是中间计算的状态。
    • success 级别用于记录操作成功完成的情况,高于 INFO 级别,但低于 WARNING 级别,为成功事件提供了一个明确的记录点。例如,在一个长时间运行的数据处理任务成功完成后,或是在用户完成关键的账户设置步骤后。

日期的简化和优化

  • 在传统的 Python logging 模块中,处理日期和时间往往需要手动设置多个参数,如 datefmt、在格式字符串中使用 %(asctime)s%(created)s 等,而且默认、间戳不带时区信息(naive datetimes)。
  • Loguru 可以直接在日志格式字符串中指定时间的格式 logger.add("file.log", format="{time:YYYY-MM-DD at HH:mm:ss} | {level} | {message}")
  • Loguru 默认支持包含时区信息的日期和时间

通过 configure() 配置 Loguru

  • 通过脚本配置 Loguru
import sys
from loguru import logger

config = {
    "handlers": [
        {"sink": sys.stdout, "format": "{time} - {message}"},
        {"sink": "file.log", "serialize": True},
    ],
    "extra": {"user": "someone"}
}
logger.configure(**config)

  • 通过 loguru-config 库加在配置文件
    • https://github.com/erezinman/loguru-config
  • 禁用/启用 Python 库中的 Loguru
    • Library 应该先调用 configure(),岁后 disable() loguru
# For libraries, should be your library's `__name__`
import mylib

my_lib_name = mylib.__name__

logger.disable(my_lib_name)
logger.info("No matter added sinks, this message is not displayed")

# In your application, enable the logger in the `library`
logger.enable(my_lib_name)
logger.info("This message however is propagated to the sinks")

通过环境变量修改默认配置

# Linux / OSX
export LOGURU_FORMAT="{time} | <lvl>{message}</lvl>"

# Windows
setx LOGURU_DEBUG_COLOR "<green>"

Loguru 自带的 parse

  • loguru 提供了内置 parse() 用来解析日志文件,获取信息
# time: 匹配任何字符,直到遇到 " - "
# level: 匹配一个或多个数字
# message: 匹配任何字符直到行末
pattern = r"(?P<time>.*) - (?P<level>[0-9]+) - (?P<message>.*)"
# 定义从字符串到对应数据类型的转换器
caster_dict = dict(time=dateutil.parser.parse, level=int)

for groups in logger.parse("file.log", pattern, cast=caster_dict):
    print("Parsed:", groups)
    # {"level": 30, "message": "Log example", "time": datetime(2018, 12, 09, 11, 23, 55)}

Loguru 和内置 logging 完全兼容

  • 可以直接 add 内置的 handler
import logging
from loguru import logger

handler = logging.handlers.SysLogHandler(address=('localhost', 514))
logger.add(handler)
  • Loguru 消息传递到内置 logging
import logging
from loguru import logger

class PropagateHandler(logging.Handler):
    def emit(self, record: logging.LogRecord) -> None:
        logging.getLogger(record.name).handle(record)

logger.add(PropagateHandler(), format="{message}")
  • 拦截 内置 logging 消息到 Loguru
class InterceptHandler(logging.Handler):
    def emit(self, record: logging.LogRecord) -> None:
        level: str | int
        # 尝试获取与标准 logging 等级相对应的 Loguru 日志等级
        try:
            level = logger.level(record.levelname).name
        except ValueError:
		    # 如果找不到对应的 Loguru 等级,则使用原始的数字等级
            level = record.levelno

        # 探测调用日志的代码位置
        frame, depth = inspect.currentframe(), 0
        while frame and (depth == 0 or frame.f_code.co_filename == logging.__file__):
            frame = frame.f_back
            depth += 1
		# 使用 Loguru 记录日志信息,保持调用栈的深度和异常信息
        logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())

logging.basicConfig(handlers=[InterceptHandler()], level=0, force=True)

👉🏻 代码注释
这段代码需要注释的地方比较多:

  • Loguru 拦截 logging 的消息需要的信息包括 levelframe 深度、消息记录。
  • level: str | int 是类型提示,字符串或者整数
  • 先试图将 level name 转为对应的 level,如果失败则使用 int 型的 level 数值
  • 从当前的 frame 通过 f_back 追溯到调用 logging 的具体代码位置
  • opt() 函数设置 Loguru
  • basicConfig 设置全局转发
  • level 为 0 会转发所有 level 的消息
  • force 确保无论之前日志系统如何配置,都将应用我们的配置,使得 InterceptHandler 生效

notifiers 库结合使用

  • notifiers 库需要事先单独安装
import notifiers

params = {
    "username": "you@gmail.com",
    "password": "abc123",
    "to": "dest@gmail.com"
}

# Send a single notification
notifier = notifiers.get_notifier("gmail")
notifier.notify(message="The application is running!", **params)

# Be alerted on each error message
from notifiers.logging import NotificationHandler

handler = NotificationHandler("gmail", defaults=params)
logger.add(handler, level="ERROR")

  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Python Loguru 是一个易于使用的日志库,它提供了简洁的语法和强大的功能,可以帮助开发者在应用程序中记录和管理日志。 Loguru 具有以下特点: 1. 简洁的语法:Loguru 提供了简单直观的 API,使得记录日志变得轻而易举。开发者可以使用类似于 print 函数的语法来记录日志,而无需担心繁琐的配置。 2. 强大的功能:Loguru 支持将日志输出到控制台、文件、网络和其他自定义目标。开发者可以根据自己的需求配置不同的输出方式和格式。 3. 自动回滚:Loguru 具备自动回滚功能,可以根据配置的大小或时间进行日志文件的分割和归档,避免日志文件过大或过长。 4. 异常追踪:Loguru 提供了异常追踪功能,可以方便地记录和追踪应用程序中的异常信息,帮助开发者快速定位和修复问题。 5. 上下文管理:Loguru 支持上下文管理,可以在日志中添加上下文信息,如请求 ID、用户 ID 等,方便开发者跟踪和调试应用程序。 使用 Loguru 非常简单,只需要在代码中导入 loguru 模块,并使用 loguru.logloguru.logger 对象来记录日志即可。 下面是一个使用 Loguru 记录日志的示例: ```python import loguru loguru.logger.add("file.log") # 将日志输出到文件 loguru.logger.info("This is an info message") # 记录一条信息日志 loguru.logger.warning("This is a warning message") # 记录一条警告日志 loguru.logger.error("This is an error message") # 记录一条错误日志 ``` 以上是 Loguru 的一些基本用法,你还可以通过配置文件或函数参数来自定义日志记录的行为。详细的用法和更多功能请参考 Loguru 官方文档。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值