nonebot / nonebot2

跨平台 Python 异步聊天机器人框架 / Asynchronous multi-platform chatbot framework written in Python
https://nonebot.dev
MIT License
5.97k stars 555 forks source link

Feature: 关于Nonebot2日志管理 #2270

Closed fu050409 closed 1 year ago

fu050409 commented 1 year ago

希望能解决的问题

Nonebot2内置了日志系统,看起来曾经使用的是Python自带的logging,后来更改为loguru

之前有位老哥在我发布插件的时候提醒我,可以使用Nonebot2自带的logger,这可以的确可以统一日志管理。

loguru是使用非常广泛的日志系统(主要是比原版好看),但时候如果某个插件包含的外置依赖包中使用了loguru、作者不怎么喜欢Nonebot2中loguru的输入格式、作者希望仅仅更改在他的插件中日志的level, 又或者作者的plugin或某些外置依赖包中可能存在这样的代码:

from loguru import logger
import sys
logger.remove()
logger.add(
    sys.stderr,
    level="CRITICAL",
)

而这这种情况下,这一段代码会篡改loguru在Nonebot2中的设置,变成这样:

08-14 15:25:43 [SUCCESS] nonebot | NoneBot is initializing...
08-14 15:25:43 [INFO] nonebot | Current Env: prod
08-14 15:25:43 [SUCCESS] nonebot | Succeeded to load plugin "echo" from "nonebot.plugins.echo"
2023-08-14 15:25:44.004 | SUCCESS  | nonebot:load_plugin:159 - Succeeded to load plugin "loggertest" from "src.loggertest"
2023-08-14 15:25:44.005 | SUCCESS  | nonebot:run:332 - Running NoneBot...
2023-08-14 15:25:44.006 | DEBUG    | nonebot:run:93 - Loaded adapters: OneBot V11
2023-08-14 15:25:44.039 | INFO     | uvicorn:serve:76 - Started server process [3472]
2023-08-14 15:25:44.039 | INFO     | uvicorn:startup:46 - Waiting for application startup.
2023-08-14 15:25:44.040 | INFO     | uvicorn:startup:60 - Application startup complete.
2023-08-14 15:25:44.041 | INFO     | uvicorn:_log_started_message:218 - Uvicorn running on http://127.0.0.1:8080 (Press CTRL+C to quit)

这就非常难受了。

描述所需要的功能

我的意见是可以将Nonebot2的日志独立出来,使得如果存在自定义logger的情况下与Nonebot2互不干扰,如果作者希望在插件中使用Nonebot2logger,那么他可以直接:

from nonebot import logger

我在之前研究loguru的时候,一直没找到怎么独立一个不相干的logger出来的方案(大概因为我太菜了?),不管怎么样,都告诉我拿logger.add(),好家伙一下子一个日志输出两行,本来就是为了美观,这下子可是把美观给整明白了。

最后我找到loguru的源码,找到了解决方案:

from loguru._logger import Logger, Core
from typing import Union
import sys
import re

def multilogger(
        sink = sys.stdout,
        name: str = "这个是日志输出中的名字",
        payload: str = "",
        format: str = "<!time>[<level>{level}</level>] <cyan><!name></cyan> | <!payload><!module><level>{message}</level>",
        colorize: bool = True,
        level: str = "INFO",
        notime: bool = False,
        *args,
        **kwargs
) -> Logger:
    module = "" if level != "DEBUG" else "<cyan>{module}</cyan>.<cyan>{name}</cyan>:{line} | "
    payload = f"<red>{payload}</red> | " if payload else ""
    time = "<green>{time:YYYY-MM-DD HH:mm:ss}</green> " if not notime else ""

    for match in re.findall(r"(<!.*?>)", format):
        value = re.match(r"^<!(.*?)>$", match)[1]
        format = re.sub(match, eval(value), format)

    logger_instance = Logger(
        core=Core(),
        exception=None,
        depth=0,
        record=False,
        lazy=False,
        colors=False,
        raw=False,
        capture=True,
        patchers=[],
        extra={},
    )
    logger_instance.configure(handlers=[
        {
            "sink": sink,
            "format": format,
            "colorize": colorize,
            "level": level,
        },
    ])
    return logger_instance

if __name__ == "__main__":
    log = multilogger()
    log.info("第一个模式的logger")
    log2 = multilogger(payload="某个包中用于区分不同的日志", notime=True)#notime不输出时间
    log2.info("第二个logger的输出模式")
    log.info("第一个logger的输出模式")

一个项目中可能有很多不同的部分,就拿Nonebot2来举例子,它包含adapterplugin等等的多个部分,同时它的插件众多,我们无法确认什么时候就会出现日志管理冲突的情况,直接运行以上代码,我们得到:

2023-08-14 15:39:41 [INFO] 这个是日志输出中的名字 | 第一个模式的logger
[INFO] 这个是日志输出中的名字 | 某个包中用于区分不同的日志 | 第二个logger的输出模式
2023-08-14 15:39:41 [INFO] 这个是日志输出中的名字 | 第一个logger的输出模式

这样的情况下,我觉得如果Nonebot2没有统一日志输出需求的情况下,可以试试我的方案。

yanyongyu commented 1 year ago

根据loguru文档,logger是可以copy的。另外loguru官方是不推荐独立复制logger,这个feature并没有很高的优先级,可能会在未来被实现。

fu050409 commented 1 year ago

根据loguru文档,logger是可以copy的。

多谢提醒,这个我后来也查到了,它是用copy.deepcopy方法来实现复制一个Logger类出来,确实可以实现我的需求。

不过我的意思是可以把这个方法内置到Nonebot2里面,这样可以方便第三方插件的日志管理,我这个其实和官方说的那个原理一样,我这个纯粹是重新声明了一个Logger

不过这也只是一个建议,具体做不做还是看你们的需求。

yanyongyu commented 1 year ago

从使用角度来说,这个问题你应该去错误修改logger的库提出,而不是在这里。loguru的全局logger设计是为了在app或框架层面进行的logger自定义,而作为一个依赖包却修改全局logger的行为是不正确的。

fu050409 commented 1 year ago

从使用角度来说,这个问题你应该去错误修改logger的库提出,而不是在这里。loguru的全局logger设计是为了在app或框架层面进行的logger自定义,而作为一个依赖包却修改全局logger的行为是不正确的。

根据loguru文档,logger是可以copy的。另外loguru官方是不推荐独立复制logger,这个feature并没有很高的优先级,可能会在未来被实现。

我理解了你的意思了。

说实话我其实不是非常理解loguru官方为什么不建议,难道是终端标准输出的问题?似乎在线程中loguru不同的Logger会争抢输出,但是除此之外我没有遇到问题。

fu050409 commented 1 year ago

如果不需要的话,老哥可以close这个issue了,打扰你的时间了。

yanyongyu commented 1 year ago

loguru需要通过enqueue来进行多logger的同步协同输出以保证输出的正确显示,会增加很多开销。