Skip to content

Commit 57d67e5

Browse files
committed
✨ logger
1 parent ad4213a commit 57d67e5

File tree

8 files changed

+162
-26
lines changed

8 files changed

+162
-26
lines changed

Diff for: arclet/entari/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,5 @@
5858

5959
WS = WebsocketsInfo
6060
WH = WebhookInfo
61+
62+
__version__ = "0.8.2"

Diff for: arclet/entari/builtins/auto_reload.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
from launart import Launart, Service, any_completed
66
from launart.status import Phase
7-
from loguru import logger
87
from watchfiles import PythonFilter, awatch
98

109
from arclet.entari import Plugin, metadata
10+
from arclet.entari.logger import log
1111
from arclet.entari.plugin import dispose, find_plugin_by_file, load_plugin
1212

1313
metadata(
@@ -17,6 +17,9 @@
1717
)
1818

1919

20+
logger = log.wrapper("[AutoReload]")
21+
22+
2023
class Watcher(Service):
2124
id = "watcher"
2225

@@ -36,14 +39,14 @@ async def watch(self):
3639
async for event in awatch(*self.dirs, watch_filter=PythonFilter()):
3740
for change in event:
3841
if plugin := find_plugin_by_file(change[1]):
39-
logger.info(f"[AutoReload] Detected change in {plugin.id}, reloading...")
42+
logger("INFO", f"Detected change in {plugin.id}, reloading...")
4043
pid = plugin.id
4144
del plugin
4245
dispose(pid)
4346
if plugin := load_plugin(pid):
44-
logger.info(f"[AutoReload] Reloaded {plugin.id}")
47+
logger("INFO", f"Reloaded {plugin.id}")
4548
else:
46-
logger.error(f"[AutoReload] Failed to reload {pid}")
49+
logger("ERROR",f"Failed to reload {pid}")
4750

4851
async def launch(self, manager: Launart):
4952
async with self.stage("blocking"):

Diff for: arclet/entari/core.py

+19-10
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from arclet.letoderea import BaseAuxiliary, Contexts, Param, Provider, ProviderFactory, es, global_providers
88
from creart import it
99
from launart import Launart, Service
10-
from loguru import logger
1110
from satori import LoginStatus
1211
from satori.client import App
1312
from satori.client.account import Account
@@ -23,6 +22,7 @@
2322
from .plugin import load_plugin
2423
from .plugin.service import plugin_service
2524
from .session import Session, EntariProtocol
25+
from .logger import log
2626

2727

2828
class ApiProtocolProvider(Provider[ApiProtocol]):
@@ -62,29 +62,38 @@ def from_config(cls, config: EntariConfig | None = None):
6262
if not config:
6363
config = EntariConfig.instance
6464
ignore_self_message = config.basic.get("ignore_self_message", True)
65+
log_level = config.basic.get("log_level", "INFO")
6566
configs = []
6667
for conf in config.basic.get("network", []):
6768
if conf["type"] in ("websocket", "websockets", "ws"):
6869
configs.append(WebsocketsInfo(**{k: v for k, v in conf.items() if k != "type"}))
6970
elif conf["type"] in ("webhook", "wh", "http"):
7071
configs.append(WebhookInfo(**{k: v for k, v in conf.items() if k != "type"}))
71-
for plug in config.plugin:
72-
load_plugin(plug)
73-
return cls(*configs, ignore_self_message=ignore_self_message)
72+
return cls(*configs, log_level=log_level, ignore_self_message=ignore_self_message)
7473

75-
def __init__(self, *configs: Config, ignore_self_message: bool = True):
74+
def __init__(
75+
self,
76+
*configs: Config,
77+
log_level: str | int = "INFO",
78+
ignore_self_message: bool = True
79+
):
80+
from . import __version__
81+
log.core.opt(colors=True).info(f"Entari <b><c>version {__version__}</c></b>")
7682
super().__init__(*configs, default_api_cls=EntariProtocol)
7783
if not hasattr(EntariConfig, "instance"):
7884
EntariConfig.load()
85+
log.set_level(log_level)
86+
for plug in EntariConfig.instance.plugin:
87+
load_plugin(plug)
7988
self.ignore_self_message = ignore_self_message
8089
es.register(_commands.publisher)
8190
self.register(self.handle_event)
8291
self.lifecycle(self.account_hook)
8392
self._ref_tasks = set()
8493

8594
@self.on_message(priority=0)
86-
def log(event: MessageCreatedEvent):
87-
logger.info(
95+
def log_msg(event: MessageCreatedEvent):
96+
log.message.info(
8897
f"[{event.channel.name or event.channel.id}] "
8998
f"{event.member.nick if event.member else (event.user.name or event.user.id)}"
9099
f"({event.user.id}) -> {event.message.content!r}"
@@ -93,11 +102,11 @@ def log(event: MessageCreatedEvent):
93102
@es.use(SendResponse.__disp_name__)
94103
async def log_send(event: SendResponse):
95104
if event.session:
96-
logger.info(
105+
log.message.info(
97106
f"[{event.session.channel.name or event.session.channel.id}] <- {event.message!r}"
98107
)
99108
else:
100-
logger.info(
109+
log.message.info(
101110
f"[{event.channel}] <- {event.message!r}"
102111
)
103112

@@ -130,7 +139,7 @@ async def handle_event(self, account: Account, event: Event):
130139
es.publish(ev)
131140
return
132141

133-
logger.warning(f"received unsupported event {event.type}: {event}")
142+
log.core.warning(f"received unsupported event {event.type}: {event}")
134143

135144
async def account_hook(self, account: Account, state: LoginStatus):
136145
_connected = []

Diff for: arclet/entari/logger.py

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import inspect
2+
import logging
3+
import sys
4+
from typing import Union, TYPE_CHECKING, Optional
5+
6+
from loguru import logger
7+
8+
if TYPE_CHECKING:
9+
from loguru import Logger
10+
11+
12+
class LoggerManager:
13+
def __init__(self):
14+
self.loggers: dict[str, "Logger"] = {}
15+
self.fork("[core]")
16+
self.fork("[plugin]")
17+
self.fork("[message]")
18+
19+
def fork(self, child_name: str):
20+
patched = logger.patch(lambda r: r.update(name=child_name))
21+
patched = patched.bind(name=child_name)
22+
self.loggers[child_name] = patched
23+
return patched
24+
25+
@property
26+
def core(self):
27+
return self.loggers["[core]"]
28+
29+
@property
30+
def plugin(self):
31+
return self.loggers["[plugin]"]
32+
33+
@property
34+
def message(self):
35+
return self.loggers["[message]"]
36+
37+
def wrapper(self, name: str, color: str = "blue"):
38+
patched = logger.patch(lambda r: r.update(name="entari"))
39+
patched = patched.bind(name=f"plugins.{name}")
40+
self.loggers[f"plugin.{name}"] = patched
41+
42+
def _log(level: str, message: str, exception: Optional[Exception] = None):
43+
patched.opt(colors=True, exception=exception).log(
44+
level, f"| <{color}>{name}</{color}> {message}"
45+
)
46+
47+
return _log
48+
49+
@staticmethod
50+
def set_level(level: Union[str, int]):
51+
if isinstance(level, str):
52+
level = level.upper()
53+
logging.basicConfig(
54+
handlers=[LoguruHandler()],
55+
level=level,
56+
format="%(asctime)s | %(name)s[%(levelname)s]: %(message)s",
57+
)
58+
logger.configure(extra={"entari_log_level": level}, patcher=_hidden_upsteam)
59+
60+
61+
class LoguruHandler(logging.Handler): # pragma: no cover
62+
"""logging 与 loguru 之间的桥梁,将 logging 的日志转发到 loguru。"""
63+
64+
def emit(self, record: logging.LogRecord):
65+
try:
66+
level = logger.level(record.levelname).name
67+
except ValueError:
68+
level = record.levelno
69+
70+
frame, depth = inspect.currentframe(), 0
71+
while frame and (
72+
depth == 0 or frame.f_code.co_filename == logging.__file__
73+
):
74+
frame = frame.f_back
75+
depth += 1
76+
77+
logger.opt(depth=depth, exception=record.exc_info).log(
78+
level, record.getMessage()
79+
)
80+
81+
82+
logging.basicConfig(
83+
handlers=[LoguruHandler()],
84+
level="INFO",
85+
format="%(asctime)s | %(name)s[%(levelname)s]: %(message)s",
86+
)
87+
88+
89+
def default_filter(record):
90+
log_level = record["extra"].get("entari_log_level", "INFO")
91+
levelno = (
92+
logger.level(log_level).no if isinstance(log_level, str) else log_level
93+
)
94+
return record["level"].no >= levelno
95+
96+
97+
logger.remove()
98+
logger.add(
99+
sys.stdout,
100+
level=0,
101+
diagnose=True,
102+
backtrace=True,
103+
colorize=True,
104+
filter=default_filter,
105+
format="<lk>{time:YYYY-MM-DD HH:mm:ss}</lk> <lvl>{level:8}</lvl> | <m><u>{name}</u></m> <lvl>{message}</lvl>",
106+
)
107+
108+
109+
def _hidden_upsteam(record):
110+
if record["name"].startswith("satori"):
111+
record["name"] = "satori"
112+
if record["name"].startswith("launart"):
113+
record["name"] = "launart"
114+
115+
116+
logger.configure(patcher=_hidden_upsteam)
117+
log = LoggerManager()
118+
119+
120+
__all__ = ["log"]

Diff for: arclet/entari/plugin/__init__.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
from pathlib import Path
55
from typing import TYPE_CHECKING, Callable
66

7-
from loguru import logger
87
from tarina import init_spec
98

109
from ..config import Config
10+
from ..logger import log
1111
from .model import Plugin
1212
from .model import PluginMetadata as PluginMetadata
1313
from .model import RegisterNotInPluginError, _current_plugin
@@ -47,9 +47,9 @@ def load_plugin(path: str, config: dict | None = None, recursive_guard: set[str]
4747
try:
4848
mod = import_plugin(path, config=config)
4949
if not mod:
50-
logger.error(f"cannot found plugin {path!r}")
50+
log.plugin.opt(colors=True).error(f"cannot found plugin <blue>{path!r}</blue>")
5151
return
52-
logger.success(f"loaded plugin {path!r}")
52+
log.plugin.opt(colors=True).success(f"loaded plugin <blue>{path!r}</blue>")
5353
if mod.__name__ in plugin_service._unloaded:
5454
if mod.__name__ in plugin_service._referents and plugin_service._referents[mod.__name__]:
5555
referents = plugin_service._referents[mod.__name__].copy()
@@ -58,7 +58,7 @@ def load_plugin(path: str, config: dict | None = None, recursive_guard: set[str]
5858
if referent in recursive_guard:
5959
continue
6060
if referent in plugin_service.plugins:
61-
logger.debug(f"reloading {mod.__name__}'s referent {referent!r}")
61+
log.plugin.opt(colors=True).debug(f"reloading <y>{mod.__name__}</y>'s referent <y>{referent!r}</y>")
6262
dispose(referent)
6363
if not load_plugin(referent):
6464
plugin_service._referents[mod.__name__].add(referent)
@@ -67,9 +67,9 @@ def load_plugin(path: str, config: dict | None = None, recursive_guard: set[str]
6767
plugin_service._unloaded.discard(mod.__name__)
6868
return mod.__plugin__
6969
except RegisterNotInPluginError as e:
70-
logger.exception(f"{e.args[0]}", exc_info=e)
70+
log.plugin.opt(colors=True).error(f"{e.args[0]}")
7171
except Exception as e:
72-
logger.exception(f"failed to load plugin {path!r} caused by {e!r}", exc_info=e)
72+
log.plugin.opt(colors=True).exception(f"failed to load plugin <blue>{path!r}</blue> caused by {e!r}", exc_info=e)
7373

7474

7575
def load_plugins(dir_: str | PathLike | Path):

Diff for: arclet/entari/plugin/model.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
from arclet.letoderea.typing import TTarget
1414
from creart import it
1515
from launart import Launart, Service
16-
from loguru import logger
1716
from satori.client import Account
1817

18+
from ..logger import log
1919
from .service import PluginLifecycleService, plugin_service
2020

2121
if TYPE_CHECKING:
@@ -216,14 +216,14 @@ def dispose(self):
216216
if self.subplugins:
217217
subplugs = [i.removeprefix(self.id)[1:] for i in self.subplugins]
218218
subplugs = (subplugs[:3] + ["..."]) if len(subplugs) > 3 else subplugs
219-
logger.debug(f"disposing sub-plugin {', '.join(subplugs)} of {self.id}")
219+
log.plugin.opt(colors=True).debug(f"disposing sub-plugin {', '.join(subplugs)} of <y>{self.id}</y>")
220220
for subplug in self.subplugins:
221221
if subplug not in plugin_service.plugins:
222222
continue
223223
try:
224224
plugin_service.plugins[subplug].dispose()
225225
except Exception as e:
226-
logger.error(f"failed to dispose sub-plugin {subplug} caused by {e!r}")
226+
log.plugin.opt(colors=True).error(f"failed to dispose sub-plugin <y>{subplug}</y> caused by {e!r}")
227227
plugin_service.plugins.pop(subplug, None)
228228
self.subplugins.clear()
229229
for disp in self.dispatchers.values():

Diff for: arclet/entari/plugin/service.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
from launart import Launart, Service, any_completed
55
from launart.status import Phase
6-
from loguru import logger
6+
7+
from ..logger import log
78

89
if TYPE_CHECKING:
910
from .model import KeepingVariable, Plugin
@@ -118,11 +119,11 @@ async def launch(self, manager: Launart):
118119
ids = [k for k in self.plugins.keys() if k not in self._subplugined]
119120
for plug_id in ids:
120121
plug = self.plugins[plug_id]
121-
logger.debug(f"disposing plugin {plug.id}")
122+
log.plugin.opt(colors=True).debug(f"disposing plugin <y>{plug.id}</y>")
122123
try:
123124
plug.dispose()
124125
except Exception as e:
125-
logger.error(f"failed to dispose plugin {plug.id} caused by {e!r}")
126+
log.plugin.opt(colors=True).error(f"failed to dispose plugin <y>{plug.id}</y> caused by {e!r}")
126127
self.plugins.pop(plug_id, None)
127128
for values in self._keep_values.values():
128129
for value in values.values():

Diff for: example.yml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ basic:
55
port: 5140
66
path: "satori"
77
ignore_self_message: true
8+
log_level: "info"
89
plugins:
910
::auto_reload:
1011
watch_dirs: ["."]

0 commit comments

Comments
 (0)