diff --git a/astrbot/bootstrap.py b/astrbot/bootstrap.py index 5a8a4c837..574dd47b2 100644 --- a/astrbot/bootstrap.py +++ b/astrbot/bootstrap.py @@ -11,15 +11,14 @@ from model.platform.manager import PlatformManager from typing import Union from type.types import Context from type.config import VERSION -from SparkleLogging.utils.core import LogManager from logging import Logger from util.cmd_config import AstrBotConfig, try_migrate from util.metrics import MetricUploader from util.updator.astrbot_updator import AstrBotUpdator +from util.log import LogManager logger: Logger = LogManager.GetLogger(log_name='astrbot') - class AstrBotBootstrap(): def __init__(self) -> None: self.context = Context() @@ -28,7 +27,11 @@ class AstrBotBootstrap(): try_migrate() self.config_helper = AstrBotConfig() self.context.config_helper = self.config_helper + # set log queue handler + LogManager.set_queue_handler(logger, self.context._log_queue) logger.info("AstrBot v" + VERSION) + # set log level + logger.setLevel(self.config_helper.log_level) # apply proxy settings http_proxy = self.context.config_helper.http_proxy https_proxy = self.context.config_helper.https_proxy @@ -66,11 +69,11 @@ class AstrBotBootstrap(): self.context.message_handler = self.message_handler self.context.command_manager = self.command_manager - # load dashboard self.dashboard.run_http_server() - dashboard_task = asyncio.create_task(self.dashboard.ws_server(), name="dashboard") - + dashboard_ws_task = asyncio.create_task(self.dashboard.ws_server(), name="dashboard") + dashboard_log_task = asyncio.create_task(self.dashboard.log_consumer(), name="log") + if self.test_mode: return @@ -82,8 +85,8 @@ class AstrBotBootstrap(): platform_tasks = self.load_platform() # load metrics uploader metrics_upload_task = asyncio.create_task(self.metrics_uploader.upload_metrics(), name="metrics-uploader") - - tasks = [metrics_upload_task, dashboard_task, *platform_tasks, *self.context.ext_tasks] + + tasks = [metrics_upload_task, dashboard_ws_task, dashboard_log_task, *platform_tasks, *self.context.ext_tasks] tasks = [self.handle_task(task) for task in tasks] await asyncio.gather(*tasks) diff --git a/astrbot/message/handler.py b/astrbot/message/handler.py index 473ab4349..6c5bf1d8b 100644 --- a/astrbot/message/handler.py +++ b/astrbot/message/handler.py @@ -11,7 +11,7 @@ from model.command.manager import CommandManager from type.message_event import AstrMessageEvent, MessageResult from type.types import Context from type.command import CommandResult -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger from nakuru.entities.components import Image from util.agent.func_call import FuncCall diff --git a/dashboard/helper.py b/dashboard/helper.py index 506964615..250b838ee 100644 --- a/dashboard/helper.py +++ b/dashboard/helper.py @@ -2,7 +2,7 @@ from . import DashBoardData from util.cmd_config import AstrBotConfig from dataclasses import dataclass, asdict from util.plugin_dev.api.v1.config import update_config -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger from type.types import Context from type.config import CONFIG_METADATA_2 diff --git a/dashboard/server.py b/dashboard/server.py index 4736c5edb..da4d79c33 100644 --- a/dashboard/server.py +++ b/dashboard/server.py @@ -13,7 +13,7 @@ from werkzeug.serving import make_server from astrbot.persist.helper import dbConn from type.types import Context from typing import List -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger from dashboard.helper import DashBoardHelper from util.io import get_local_ip_addresses @@ -290,16 +290,6 @@ class AstrBotDashBoard(): data=None ).__dict__ - @self.dashboard_be.post("/api/log") - def log(): - for item in self.ws_clients: - try: - asyncio.run_coroutine_threadsafe( - self.ws_clients[item].send(request.data.decode()), self.loop).result() - except Exception as e: - pass - return 'ok' - @self.dashboard_be.get("/api/check_update") def get_update_info(): try: @@ -422,18 +412,22 @@ class AstrBotDashBoard(): async def get_log_history(self): try: - with open("logs/astrbot/astrbot.log", "r", encoding="utf-8") as f: - return f.readlines()[-100:] + dq = self.context._log_queue.get_cache() + ret = "" + for log in dq: + ret += log + "\n\r" + return ret except Exception as e: logger.warning(f"读取日志历史失败: {e.__str__()}") - return [] + return "" async def __handle_msg(self, websocket, path): address = websocket.remote_address self.ws_clients[address] = websocket - data = await self.get_log_history() - data = ''.join(data).replace('\n', '\r\n') - await websocket.send(data) + + # 发送日志历史 + await websocket.send(await self.get_log_history()) + while True: try: msg = await websocket.recv() @@ -450,6 +444,15 @@ class AstrBotDashBoard(): ws_server = websockets.serve(self.__handle_msg, "0.0.0.0", 6186) logger.info("WebSocket 服务器已启动。") await ws_server + + async def log_consumer(self): + while True: + log = await self.context._log_queue.get() + for ws in self.ws_clients.values(): + try: + await ws.send(log) + except Exception as e: + pass def http_server(self): http_server = make_server( diff --git a/main.py b/main.py index c334a9704..d99731085 100644 --- a/main.py +++ b/main.py @@ -6,8 +6,7 @@ import warnings import traceback import mimetypes from astrbot.bootstrap import AstrBotBootstrap -from SparkleLogging.utils.core import LogManager -from logging import Formatter +from util.log import LogManager warnings.filterwarnings("ignore") logo_tmpl = r""" @@ -54,10 +53,5 @@ def check_env(): if __name__ == "__main__": check_env() - - logger = LogManager.GetLogger( - log_name='astrbot', - out_to_console=True, - custom_formatter=Formatter('[%(asctime)s| %(name)s - %(levelname)s|%(filename)s:%(lineno)d]: %(message)s', datefmt="%H:%M:%S") - ) + logger = LogManager.GetLogger(log_name='astrbot') main() diff --git a/model/command/internal_handler.py b/model/command/internal_handler.py index 35a4a756d..e90068476 100644 --- a/model/command/internal_handler.py +++ b/model/command/internal_handler.py @@ -6,7 +6,7 @@ from type.message_event import AstrMessageEvent from type.command import CommandResult from type.types import Context from type.config import VERSION -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger from util.agent.web_searcher import search_from_bing, fetch_website_content diff --git a/model/command/manager.py b/model/command/manager.py index 063f1514a..0f249a108 100644 --- a/model/command/manager.py +++ b/model/command/manager.py @@ -9,7 +9,7 @@ from type.command import CommandResult from type.register import RegisteredPlugins from model.command.parser import CommandParser from model.plugin.command import PluginCommandBridge -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger from dataclasses import dataclass diff --git a/model/command/openai_official_handler.py b/model/command/openai_official_handler.py index 1f7836105..318c0527e 100644 --- a/model/command/openai_official_handler.py +++ b/model/command/openai_official_handler.py @@ -2,7 +2,7 @@ from model.command.manager import CommandManager from type.message_event import AstrMessageEvent from type.command import CommandResult from type.types import Context -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger from nakuru.entities.components import Image from model.provider.openai_official import ProviderOpenAIOfficial, MODELS diff --git a/model/platform/__init__.py b/model/platform/__init__.py index 7003af473..d1d12a92c 100644 --- a/model/platform/__init__.py +++ b/model/platform/__init__.py @@ -40,14 +40,18 @@ class Platform(): ''' pass - def parse_message_outline(self, message: AstrBotMessage) -> str: + def parse_message_outline(self, message: Union[AstrBotMessage, list]) -> str: ''' 将消息解析成大纲消息形式,如: xxxxx[图片]xxxxx。用于输出日志等。 ''' - if isinstance(message, str): - return message ret = '' - parsed = message if isinstance(message, list) else message.message + if isinstance(message, list): + parsed = message + elif isinstance(message, AstrBotMessage): + parsed = message.message + elif isinstance(message, str): + return message + try: for node in parsed: if isinstance(node, Plain): diff --git a/model/platform/manager.py b/model/platform/manager.py index 028a91e8f..51391ec7a 100644 --- a/model/platform/manager.py +++ b/model/platform/manager.py @@ -3,7 +3,7 @@ import asyncio from util.io import port_checker from type.register import RegisteredPlatform from type.types import Context -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger from astrbot.message.handler import MessageHandler from util.cmd_config import ( diff --git a/model/platform/qq_aiocqhttp.py b/model/platform/qq_aiocqhttp.py index 9961bf911..fad17f5d5 100644 --- a/model/platform/qq_aiocqhttp.py +++ b/model/platform/qq_aiocqhttp.py @@ -10,7 +10,7 @@ from type.message_event import * from type.command import * from typing import Union, List, Dict from nakuru.entities.components import * -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger from astrbot.message.handler import MessageHandler from util.cmd_config import PlatformConfig, AiocqhttpPlatformConfig @@ -209,7 +209,7 @@ class AIOCQHTTP(Platform): if isinstance(message, AstrBotMessage): logger.info( - f"{message.sender.user_id} <- {self.parse_message_outline(message)}") + f"{message.sender.nickname}/{message.sender.user_id} <- {self.parse_message_outline(message_chain)}") else: logger.info(f"回复消息: {message_chain}") diff --git a/model/platform/qq_nakuru.py b/model/platform/qq_nakuru.py index c5e877799..2fd647b8b 100644 --- a/model/platform/qq_nakuru.py +++ b/model/platform/qq_nakuru.py @@ -15,7 +15,7 @@ from . import Platform from type.astrbot_message import * from type.message_event import * from type.command import * -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger from astrbot.message.handler import MessageHandler from util.cmd_config import PlatformConfig, NakuruPlatformConfig @@ -171,7 +171,7 @@ class QQNakuru(Platform): (GroupMessage, FriendMessage, GuildMessage)) logger.info( - f"{source.user_id} <- {self.parse_message_outline(res)}") + f"{message.sender.nickname}/{message.sender.user_id} <- {self.parse_message_outline(res)}") if isinstance(res, str): res = [Plain(text=res), ] diff --git a/model/platform/qq_official.py b/model/platform/qq_official.py index f3b97dcb4..b1f17398b 100644 --- a/model/platform/qq_official.py +++ b/model/platform/qq_official.py @@ -16,7 +16,7 @@ from type.message_event import * from type.command import * from typing import Union, List, Dict from nakuru.entities.components import * -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger from astrbot.message.handler import MessageHandler from util.cmd_config import PlatformConfig, QQOfficialPlatformConfig diff --git a/model/plugin/command.py b/model/plugin/command.py index 747d01eb7..5104a4e6e 100644 --- a/model/plugin/command.py +++ b/model/plugin/command.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from type.register import RegisteredPlugins from typing import List, Union, Callable -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger logger: Logger = LogManager.GetLogger(log_name='astrbot') diff --git a/model/plugin/manager.py b/model/plugin/manager.py index 952ebbdc2..7fa567138 100644 --- a/model/plugin/manager.py +++ b/model/plugin/manager.py @@ -13,7 +13,7 @@ from types import ModuleType from type.types import Context from type.plugin import * from type.register import * -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger logger: Logger = LogManager.GetLogger(log_name='astrbot') diff --git a/model/provider/openai_official.py b/model/provider/openai_official.py index 2b6e08544..e5c5248a7 100644 --- a/model/provider/openai_official.py +++ b/model/provider/openai_official.py @@ -15,7 +15,7 @@ from util.io import download_image_by_url from astrbot.persist.helper import dbConn from model.provider.provider import Provider from util.cmd_config import LLMConfig -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger from typing import List, Dict diff --git a/requirements.txt b/requirements.txt index 79529299d..e49d3fb88 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,5 +13,5 @@ websockets flask psutil lxml_html_clean -SparkleLogging +colorlog aiocqhttp diff --git a/tests/test_message.py b/tests/test_message.py index cc7764d4f..52c06c252 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -11,16 +11,11 @@ from model.platform.qq_aiocqhttp import AIOCQHTTP from model.provider.openai_official import ProviderOpenAIOfficial from type.astrbot_message import * from type.message_event import * -from SparkleLogging.utils.core import LogManager -from logging import Formatter +from util.log import LogManager from util.cmd_config import QQOfficialPlatformConfig, AiocqhttpPlatformConfig -logger = LogManager.GetLogger( -log_name='astrbot', - out_to_console=True, - custom_formatter=Formatter('[%(asctime)s| %(name)s - %(levelname)s|%(filename)s:%(lineno)d]: %(message)s', datefmt="%H:%M:%S") -) +logger = LogManager.GetLogger(log_name='astrbot') pytest_plugins = ('pytest_asyncio',) os.environ['TEST_MODE'] = 'on' diff --git a/type/cached_queue.py b/type/cached_queue.py new file mode 100644 index 000000000..3d8381efe --- /dev/null +++ b/type/cached_queue.py @@ -0,0 +1,28 @@ +from asyncio import Queue +from collections import deque +from typing import Deque + +class CachedQueue(Queue): + def __init__(self, maxsize: int = 0, cachesize: int = 200): + super().__init__(maxsize) + self.cache = deque(maxlen=cachesize) + + def put_nowait(self, item): + self.cache.append(item) + super().put_nowait(item) + + def get_nowait(self): + item = super().get_nowait() + return item + + def get(self): + item = super().get() + return item + + def clear(self): + self.cache.clear() + with self.mutex: + self._queue.clear() + + def get_cache(self) -> Deque: + return self.cache \ No newline at end of file diff --git a/type/config.py b/type/config.py index 639e75ac0..2105642a5 100644 --- a/type/config.py +++ b/type/config.py @@ -169,6 +169,7 @@ DEFAULT_CONFIG_VERSION_2 = { "username": "", "password": "", }, + "log_level": "INFO", } # 这个是用于迁移旧版本配置文件的映射表 @@ -350,4 +351,5 @@ CONFIG_METADATA_2 = { "password": {"description": "密码", "type": "string"}, } }, + "log_level": {"description": "控制台日志级别(DEBUG, INFO, WARNING, ERROR)", "type": "string"}, } diff --git a/type/types.py b/type/types.py index 7116afbac..eb92d5549 100644 --- a/type/types.py +++ b/type/types.py @@ -13,7 +13,7 @@ from type.middleware import Middleware from type.astrbot_message import MessageType from model.plugin.command import PluginCommandBridge from model.provider.provider import Provider -from util.agent.func_call import FuncCall +from type.cached_queue import CachedQueue class Context: @@ -49,6 +49,8 @@ class Context: self.command_manager = None self.running = True + self._log_queue = CachedQueue() + # useless # self.reply_prefix = "" diff --git a/util/agent/web_searcher.py b/util/agent/web_searcher.py index d9b384314..5809c99c6 100644 --- a/util/agent/web_searcher.py +++ b/util/agent/web_searcher.py @@ -12,7 +12,7 @@ from util.websearch.bing import Bing from util.websearch.sogo import Sogo from util.websearch.google import Google from model.provider.provider import Provider -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger from type.types import Context from type.message_event import AstrMessageEvent diff --git a/util/cmd_config.py b/util/cmd_config.py index bdb2d239a..6f9a6d60f 100644 --- a/util/cmd_config.py +++ b/util/cmd_config.py @@ -133,6 +133,7 @@ class AstrBotConfig(): dashboard: DashboardConfig = field(default_factory=DashboardConfig) platform: List[PlatformConfig] = field(default_factory=list) wake_prefix: List[str] = field(default_factory=list) + log_level: str = "INFO" def __init__(self) -> None: self.init_configs() @@ -174,6 +175,7 @@ class AstrBotConfig(): self.http_proxy=data.get("http_proxy", "") self.dashboard=DashboardConfig(**data.get("dashboard", {})) self.wake_prefix=data.get("wake_prefix", []) + self.log_level=data.get("log_level", "INFO") def migrate_config_1_2(self, old: dict) -> dict: '''将配置文件从版本 1 迁移至版本 2''' diff --git a/util/io.py b/util/io.py index d2ad3312c..3bbaf9f5c 100644 --- a/util/io.py +++ b/util/io.py @@ -6,7 +6,7 @@ import time import aiohttp from PIL import Image -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger logger: Logger = LogManager.GetLogger(log_name='astrbot') diff --git a/util/log.py b/util/log.py new file mode 100644 index 000000000..aca6f1ef2 --- /dev/null +++ b/util/log.py @@ -0,0 +1,51 @@ +import logging, asyncio, colorlog +from type.cached_queue import CachedQueue + +log_color_config = { + 'DEBUG': 'bold_blue', 'INFO': 'bold_cyan', + 'WARNING': 'bold_yellow', 'ERROR': 'red', + 'CRITICAL': 'bold_red', 'RESET': 'reset', + 'asctime': 'green' +} + +class LogQueueHandler(logging.Handler): + def __init__(self, log_queue: CachedQueue): + super().__init__() + self.log_queue = log_queue + + def emit(self, record): + log_entry = self.format(record) + try: + self.log_queue.put_nowait(log_entry) + except Exception: + pass + +class LogManager: + + @classmethod + def GetLogger(cls, log_name: str = 'default'): + logger = logging.getLogger(log_name) + if logger.hasHandlers(): + return logger + console_handler = logging.StreamHandler() + console_handler.setLevel(logging.DEBUG) + console_formatter = colorlog.ColoredFormatter( + fmt='%(log_color)s [%(asctime)s| %(levelname)s] [%(funcName)s|%(filename)s:%(lineno)d]: %(message)s %(reset)s', + datefmt='%H:%M:%S', + log_colors=log_color_config + ) + console_handler.setFormatter(console_formatter) + logger.setLevel(logging.DEBUG) + logger.addHandler(console_handler) + + return logger + + @classmethod + def set_queue_handler(cls, logger: logging.Logger, log_queue: CachedQueue): + handler = LogQueueHandler(log_queue) + handler.setLevel(logging.DEBUG) + if logger.handlers: + handler.setFormatter(logger.handlers[0].formatter) + else: + handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) + logger.addHandler(handler) \ No newline at end of file diff --git a/util/t2i/renderer.py b/util/t2i/renderer.py index d4711a795..ab8d9c820 100644 --- a/util/t2i/renderer.py +++ b/util/t2i/renderer.py @@ -1,7 +1,7 @@ from util.t2i.strategies.local_strategy import LocalRenderStrategy from util.t2i.strategies.network_strategy import NetworkRenderStrategy from util.t2i.context import RenderContext -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger logger: Logger = LogManager.GetLogger(log_name='astrbot') diff --git a/util/updator/astrbot_updator.py b/util/updator/astrbot_updator.py index 1ac1b2f86..35ebac7ce 100644 --- a/util/updator/astrbot_updator.py +++ b/util/updator/astrbot_updator.py @@ -1,6 +1,6 @@ import os, psutil, sys, time from util.updator.zip_updator import ReleaseInfo, RepoZipUpdator -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger from type.config import VERSION from util.io import on_error, download_file diff --git a/util/updator/plugin_updator.py b/util/updator/plugin_updator.py index ac9a5d17a..291faa943 100644 --- a/util/updator/plugin_updator.py +++ b/util/updator/plugin_updator.py @@ -4,7 +4,7 @@ from util.updator.zip_updator import RepoZipUpdator from util.io import remove_dir from type.register import RegisteredPlugin from typing import Union -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger from util.io import on_error diff --git a/util/updator/zip_updator.py b/util/updator/zip_updator.py index e6745bd3b..8b8f174bc 100644 --- a/util/updator/zip_updator.py +++ b/util/updator/zip_updator.py @@ -1,5 +1,5 @@ import aiohttp, os, zipfile, shutil -from SparkleLogging.utils.core import LogManager +from util.log import LogManager from logging import Logger from util.io import on_error, download_file