diff --git a/addons/dashboard/helper.py b/addons/dashboard/helper.py index 6a7d2b7a8..771612d62 100644 --- a/addons/dashboard/helper.py +++ b/addons/dashboard/helper.py @@ -11,6 +11,10 @@ import threading import time import asyncio from util.plugin_dev.api.v1.config import update_config +from SparkleLogging.utils.core import LogManager +from logging import Logger + +logger: Logger = LogManager.GetLogger(log_name='astrbot-core') @dataclass @@ -28,7 +32,6 @@ class DashBoardHelper(): def __init__(self, global_object, config: dict): self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) - self.logger = global_object.logger dashboard_data = global_object.dashboard_data dashboard_data.configs = { "data": [] @@ -42,7 +45,6 @@ class DashBoardHelper(): @self.dashboard.register("post_configs") def on_post_configs(post_configs: dict): try: - # self.logger.log(f"收到配置更新请求", gu.LEVEL_INFO, tag="可视化面板") if 'base_config' in post_configs: self.save_config( post_configs['base_config'], namespace='') # 基础配置 @@ -54,7 +56,6 @@ class DashBoardHelper(): threading.Thread(target=self.dashboard.shutdown_bot, args=(2,), daemon=True).start() except Exception as e: - # self.logger.log(f"在保存配置时发生错误:{e}", gu.LEVEL_ERROR, tag="可视化面板") raise e # 将 config.yaml、 中的配置解析到 dashboard_data.configs 中 @@ -470,7 +471,7 @@ class DashBoardHelper(): ] except Exception as e: - self.logger.log(f"配置文件解析错误:{e}", gu.LEVEL_ERROR) + logger.error(f"配置文件解析错误:{e}") raise e def save_config(self, post_config: list, namespace: str): diff --git a/addons/dashboard/server.py b/addons/dashboard/server.py index f53602c89..2bb775aec 100644 --- a/addons/dashboard/server.py +++ b/addons/dashboard/server.py @@ -1,13 +1,3 @@ -from flask import Flask, request -from flask.logging import default_handler -from werkzeug.serving import make_server -from util import general_utils as gu -from dataclasses import dataclass -import logging -from cores.database.conn import dbConn -from util.cmd_config import CmdConfig -from util.updator import check_update, update_project, request_release_info -from cores.astrbot.types import * import util.plugin_util as putil import websockets import json @@ -17,6 +7,18 @@ import os import sys import time +from flask import Flask, request +from flask.logging import default_handler +from werkzeug.serving import make_server +from util import general_utils as gu +from dataclasses import dataclass +from cores.database.conn import dbConn +from util.cmd_config import CmdConfig +from util.updator import check_update, update_project, request_release_info +from cores.astrbot.types import * +from SparkleLogging.utils.core import LogManager +from logging import Logger +logger: Logger = LogManager.GetLogger(log_name='astrbot-core') @dataclass class DashBoardData(): @@ -41,11 +43,8 @@ class AstrBotDashBoard(): self.dashboard_data: DashBoardData = global_object.dashboard_data self.dashboard_be = Flask( __name__, static_folder="dist", static_url_path="/") - log = logging.getLogger('werkzeug') - log.setLevel(logging.ERROR) self.funcs = {} self.cc = CmdConfig() - self.logger = global_object.logger self.ws_clients = {} # remote_ip: ws # 启动 websocket 服务器 self.ws_server = websockets.serve(self.__handle_msg, "0.0.0.0", 6186) @@ -55,6 +54,22 @@ class AstrBotDashBoard(): # 返回页面 return self.dashboard_be.send_static_file("index.html") + @self.dashboard_be.get("/config") + def rt_config(): + return self.dashboard_be.send_static_file("index.html") + + @self.dashboard_be.get("/logs") + def rt_logs(): + return self.dashboard_be.send_static_file("index.html") + + @self.dashboard_be.get("/extension") + def rt_extension(): + return self.dashboard_be.send_static_file("index.html") + + @self.dashboard_be.get("/dashboard/default") + def rt_dashboard(): + return self.dashboard_be.send_static_file("index.html") + @self.dashboard_be.post("/api/authenticate") def authenticate(): username = self.cc.get("dashboard_username", "") @@ -179,9 +194,9 @@ class AstrBotDashBoard(): post_data = request.json repo_url = post_data["url"] try: - self.logger.log(f"正在安装插件 {repo_url}", tag="可视化面板") + logger.info(f"正在安装插件 {repo_url}") putil.install_plugin(repo_url, self.dashboard_data.plugins) - self.logger.log(f"安装插件 {repo_url} 成功", tag="可视化面板") + logger.info(f"安装插件 {repo_url} 成功") return Response( status="success", message="安装成功~", @@ -199,10 +214,10 @@ class AstrBotDashBoard(): post_data = request.json plugin_name = post_data["name"] try: - self.logger.log(f"正在卸载插件 {plugin_name}", tag="可视化面板") + logger.info(f"正在卸载插件 {plugin_name}") putil.uninstall_plugin( plugin_name, self.dashboard_data.plugins) - self.logger.log(f"卸载插件 {plugin_name} 成功", tag="可视化面板") + logger.info(f"卸载插件 {plugin_name} 成功") return Response( status="success", message="卸载成功~", @@ -220,9 +235,9 @@ class AstrBotDashBoard(): post_data = request.json plugin_name = post_data["name"] try: - self.logger.log(f"正在更新插件 {plugin_name}", tag="可视化面板") + logger.info(f"正在更新插件 {plugin_name}") putil.update_plugin(plugin_name, self.dashboard_data.plugins) - self.logger.log(f"更新插件 {plugin_name} 成功", tag="可视化面板") + logger.info(f"更新插件 {plugin_name} 成功") return Response( status="success", message="更新成功~", @@ -416,21 +431,29 @@ class AstrBotDashBoard(): return func return decorator + async def get_log_history(self): + try: + with open("logs/astrbot-core/astrbot-core.log", "r", encoding="utf-8") as f: + return f.readlines()[-100:] + except Exception as e: + logger.warning(f"读取日志历史失败: {e.__str__()}") + return [] + async def __handle_msg(self, websocket, path): address = websocket.remote_address - # self.logger.log(f"和 {address} 建立了 websocket 连接", tag="可视化面板") self.ws_clients[address] = websocket - data = ''.join(self.logger.history).replace('\n', '\r\n') + data = await self.get_log_history() + data = ''.join(data).replace('\n', '\r\n') await websocket.send(data) while True: try: msg = await websocket.recv() except websockets.exceptions.ConnectionClosedError: - # self.logger.log(f"和 {address} 的 websocket 连接已断开", tag="可视化面板") + # logger.info(f"和 {address} 的 websocket 连接已断开") del self.ws_clients[address] break except Exception as e: - # self.logger.log(f"和 {path} 的 websocket 连接发生了错误: {e.__str__()}", tag="可视化面板") + # logger.info(f"和 {path} 的 websocket 连接发生了错误: {e.__str__()}") del self.ws_clients[address] break @@ -441,11 +464,12 @@ class AstrBotDashBoard(): def run(self): threading.Thread(target=self.run_ws_server, args=(self.loop,)).start() - self.logger.log("已启动 websocket 服务器", tag="可视化面板") + logger.info("已启动 websocket 服务器") ip_address = gu.get_local_ip_addresses() ip_str = f"http://{ip_address}:6185\n\thttp://localhost:6185" - self.logger.log( - f"\n==================\n您可访问:\n\n\t{ip_str}\n\n来登录可视化面板,默认账号密码为空。\n注意: 所有配置项现已全量迁移至 cmd_config.json 文件下,可登录可视化面板在线修改配置。\n==================\n", tag="可视化面板") + logger.info( + f"\n==================\n您可访问:\n\n\t{ip_str}\n\n来登录可视化面板,默认账号密码为空。\n注意: 所有配置项现已全量迁移至 cmd_config.json 文件下,可登录可视化面板在线修改配置。\n==================\n") + http_server = make_server( '0.0.0.0', 6185, self.dashboard_be, threaded=True) http_server.serve_forever() diff --git a/cores/astrbot/core.py b/cores/astrbot/core.py index 6b5b4b174..d58a2160b 100644 --- a/cores/astrbot/core.py +++ b/cores/astrbot/core.py @@ -19,7 +19,7 @@ from addons.baidu_aip_judge import BaiduJudge from model.provider.provider import Provider from model.command.command import Command from util import general_utils as gu -from util.general_utils import Logger, upload, run_monitor +from util.general_utils import upload, run_monitor from util.cmd_config import CmdConfig as cc from util.cmd_config import init_astrbot_config_items from .types import * @@ -27,7 +27,10 @@ from addons.dashboard.helper import DashBoardHelper from addons.dashboard.server import DashBoardData from cores.database.conn import dbConn from model.platform._message_result import MessageResult +from SparkleLogging.utils.core import LogManager +from logging import Logger +logger: Logger = LogManager.GetLogger(log_name='astrbot-core') # 用户发言频率 user_frequency = {} @@ -58,7 +61,6 @@ init_astrbot_config_items() # 全局对象 _global_object: GlobalObject = None -logger: Logger = Logger() # 语言模型选择 @@ -80,7 +82,6 @@ def init(cfg): global baidu_judge, chosen_provider global frequency_count, frequency_time global _global_object - global logger # 迁移旧配置 gu.try_migrate_config(cfg) @@ -94,8 +95,7 @@ def init(cfg): _global_object = GlobalObject() _global_object.version = version _global_object.base_config = cfg - _global_object.logger = logger - logger.log("AstrBot v"+version, gu.LEVEL_INFO) + logger.info("AstrBot v"+version) if 'reply_prefix' in cfg: # 适配旧版配置 @@ -107,10 +107,10 @@ def init(cfg): _global_object.reply_prefix = cfg['reply_prefix'] # 语言模型提供商 - logger.log("正在载入语言模型...", gu.LEVEL_INFO) + logger.info("正在载入语言模型...") prov = privider_chooser(cfg) if OPENAI_OFFICIAL in prov: - logger.log("初始化:OpenAI官方", gu.LEVEL_INFO) + logger.info("初始化:OpenAI官方") if cfg['openai']['key'] is not None and cfg['openai']['key'] != [None]: from model.provider.openai_official import ProviderOpenAIOfficial from model.command.openai_official import CommandOpenAIOfficial @@ -131,9 +131,9 @@ def init(cfg): if 'baidu_aip' in cfg and 'enable' in cfg['baidu_aip'] and cfg['baidu_aip']['enable']: try: baidu_judge = BaiduJudge(cfg['baidu_aip']) - logger.log("百度内容审核初始化成功", gu.LEVEL_INFO) + logger.info("百度内容审核初始化成功") except BaseException as e: - logger.log("百度内容审核初始化失败", gu.LEVEL_ERROR) + logger.info("百度内容审核初始化失败") threading.Thread(target=upload, args=( _global_object, ), daemon=True).start() @@ -151,7 +151,7 @@ def init(cfg): else: _global_object.unique_session = False except BaseException as e: - logger.log("独立会话配置错误: "+str(e), gu.LEVEL_ERROR) + logger.info("独立会话配置错误: "+str(e)) nick_qq = cc.get("nick_qq", None) if nick_qq == None: @@ -166,33 +166,33 @@ def init(cfg): global llm_wake_prefix llm_wake_prefix = cc.get("llm_wake_prefix", "") - logger.log("正在载入插件...", gu.LEVEL_INFO) + logger.info("正在载入插件...") # 加载插件 _command = Command(None, _global_object) ok, err = putil.plugin_reload(_global_object.cached_plugins) if ok: - logger.log( - f"成功载入 {len(_global_object.cached_plugins)} 个插件", gu.LEVEL_INFO) + logger.info( + f"成功载入 {len(_global_object.cached_plugins)} 个插件") else: - logger.log(err, gu.LEVEL_ERROR) + logger.info(err) if chosen_provider is None: llm_command_instance[NONE_LLM] = _command chosen_provider = NONE_LLM - logger.log("正在载入机器人消息平台", gu.LEVEL_INFO) - # logger.log("提示:需要添加管理员 ID 才能使用 update/plugin 等指令),可在可视化面板添加。(如已添加可忽略)", gu.LEVEL_WARNING) + logger.info("正在载入机器人消息平台") + # logger.info("提示:需要添加管理员 ID 才能使用 update/plugin 等指令),可在可视化面板添加。(如已添加可忽略)") platform_str = "" # GOCQ if 'gocqbot' in cfg and cfg['gocqbot']['enable']: - logger.log("启用 QQ_GOCQ 机器人消息平台", gu.LEVEL_INFO) + logger.info("启用 QQ_GOCQ 机器人消息平台") threading.Thread(target=run_gocq_bot, args=( cfg, _global_object), daemon=True).start() platform_str += "QQ_GOCQ," # QQ频道 if 'qqbot' in cfg and cfg['qqbot']['enable'] and cfg['qqbot']['appid'] != None: - logger.log("启用 QQ_OFFICIAL 机器人消息平台", gu.LEVEL_INFO) + logger.info("启用 QQ_OFFICIAL 机器人消息平台") threading.Thread(target=run_qqchan_bot, args=( cfg, _global_object), daemon=True).start() platform_str += "QQ_OFFICIAL," @@ -221,12 +221,12 @@ def init(cfg): threading.Thread(target=run_monitor, args=( _global_object,), daemon=True).start() - logger.log( - "如果有任何问题, 请在 https://github.com/Soulter/AstrBot 上提交 issue 或加群 322154837。", gu.LEVEL_INFO) - logger.log("请给 https://github.com/Soulter/AstrBot 点个 star。", gu.LEVEL_INFO) + logger.info( + "如果有任何问题, 请在 https://github.com/Soulter/AstrBot 上提交 issue 或加群 322154837。") + logger.info("请给 https://github.com/Soulter/AstrBot 点个 star。") if platform_str == '': platform_str = "(未启动任何平台,请前往面板添加)" - logger.log(f"🎉 项目启动完成") + logger.info(f"🎉 项目启动完成") dashboard_thread.join() @@ -245,10 +245,8 @@ def run_qqchan_bot(cfg: dict, global_object: GlobalObject): platform_name="qqchan", platform_instance=qqchannel_bot, origin="internal")) qqchannel_bot.run() except BaseException as e: - logger.log("启动QQ频道机器人时出现错误, 原因如下: " + str(e), - gu.LEVEL_CRITICAL, tag="QQ频道") - logger.log(r"如果您是初次启动,请前往可视化面板填写配置。详情请看:https://astrbot.soulter.top/center/。" + - str(e), gu.LEVEL_CRITICAL) + logger.error("启动 QQ 频道机器人时出现错误, 原因如下: " + str(e)) + logger.error(r"如果您是初次启动,请前往可视化面板填写配置。详情请看:https://astrbot.soulter.top/center/。") ''' @@ -263,17 +261,17 @@ def run_gocq_bot(cfg: dict, _global_object: GlobalObject): host = cc.get("gocq_host", "127.0.0.1") port = cc.get("gocq_websocket_port", 6700) http_port = cc.get("gocq_http_port", 5700) - logger.log( + logger.info( f"正在检查连接...host: {host}, ws port: {port}, http port: {http_port}", tag="QQ") while True: if not gu.port_checker(port=port, host=host) or not gu.port_checker(port=http_port, host=host): if not noticed: noticed = True - logger.log( + logger.info( f"连接到{host}:{port}(或{http_port})失败。程序会每隔 5s 自动重试。", gu.LEVEL_CRITICAL, tag="QQ") time.sleep(5) else: - logger.log("检查完毕,未发现问题。", tag="QQ") + logger.info("检查完毕,未发现问题。", tag="QQ") break try: qq_gocq = QQGOCQ(cfg=cfg, message_handler=oper_msg, @@ -345,7 +343,6 @@ async def oper_msg(message: AstrBotMessage, reg_platform = p break if not reg_platform: - _global_object.logger.log(f"未找到平台 {platform} 的实例。", gu.LEVEL_ERROR) raise Exception(f"未找到平台 {platform} 的实例。") # 统计数据,如频道消息量 @@ -401,7 +398,7 @@ async def oper_msg(message: AstrBotMessage, if not check: return MessageResult(f"你的提问得到的回复未通过【百度AI内容审核】服务, 不予回复。\n\n{msg}") if chosen_provider == NONE_LLM: - logger.log("一条消息由于 Bot 未启动任何语言模型并且未触发指令而将被忽略。", gu.LEVEL_WARNING) + logger.info("一条消息由于 Bot 未启动任何语言模型并且未触发指令而将被忽略。") return try: if llm_wake_prefix != "" and not message_str.startswith(llm_wake_prefix): @@ -432,7 +429,7 @@ async def oper_msg(message: AstrBotMessage, llm_result_str = _global_object.reply_prefix + llm_result_str except BaseException as e: - logger.log(f"调用异常:{traceback.format_exc()}", gu.LEVEL_ERROR) + logger.info(f"调用异常:{traceback.format_exc()}") return MessageResult(f"调用语言模型例程时出现异常。原因: {str(e)}") # 切换回原来的语言模型 @@ -485,4 +482,4 @@ async def oper_msg(message: AstrBotMessage, try: return MessageResult(llm_result_str) except BaseException as e: - logger.log("回复消息错误: \n"+str(e), gu.LEVEL_ERROR) + logger.info("回复消息错误: \n"+str(e)) diff --git a/cores/astrbot/types.py b/cores/astrbot/types.py index 3f56947db..f44318fa1 100644 --- a/cores/astrbot/types.py +++ b/cores/astrbot/types.py @@ -123,7 +123,6 @@ class GlobalObject: cnt_total: int # 总消息数 default_personality: dict dashboard_data = None - logger: None def __init__(self): self.nick = None # gocq 的昵称 diff --git a/main.py b/main.py index f2abb3c0d..a680dd67d 100644 --- a/main.py +++ b/main.py @@ -1,31 +1,57 @@ + import os import sys -from pip._internal import main as pipmain import warnings import traceback import threading +from SparkleLogging.utils.core import LogManager +from logging import Formatter, Logger warnings.filterwarnings("ignore") abs_path = os.path.dirname(os.path.realpath(sys.argv[0])) + '/' +logger: Logger = None + +logo_tmpl = """ + ___ _______.___________..______ .______ ______ .___________. + / \ / | || _ \ | _ \ / __ \ | | + / ^ \ | (----`---| |----`| |_) | | |_) | | | | | `---| |----` + / /_\ \ \ \ | | | / | _ < | | | | | | + / _____ \ .----) | | | | |\ \----.| |_) | | `--' | | | +/__/ \__\ |_______/ |__| | _| `._____||______/ \______/ |__| + +""" + def make_necessary_dirs(): os.makedirs("data/config", exist_ok=True) os.makedirs("temp", exist_ok=True) def main(): + logger = LogManager.GetLogger( + log_name='astrbot-core', + out_to_console=True, + # HTTPpost_url='http://localhost:6185/api/log', + # http_mode = True, + custom_formatter=Formatter('[%(asctime)s| %(name)s - %(levelname)s|%(filename)s:%(lineno)d]: %(message)s', datefmt="%H:%M:%S") + ) + logger.info(logo_tmpl) # config.yaml 配置文件加载和环境确认 try: + import botpy, logging, yaml import cores.astrbot.core as qqBot - import yaml + # delete qqbotpy's logger + for handler in logging.root.handlers[:]: + logging.root.removeHandler(handler) ymlfile = open(abs_path+"configs/config.yaml", 'r', encoding='utf-8') cfg = yaml.safe_load(ymlfile) except ImportError as import_error: - traceback.print_exc() - print(import_error) - input("第三方库未完全安装完毕,请退出程序重试。") + logger.error(import_error) + input("检测到一些依赖库没有安装。请先安装,然后重试。") + exit() except FileNotFoundError as file_not_found: - print(file_not_found) + logger.error(file_not_found) input("配置文件不存在,请检查是否已经下载配置文件。") + exit() except BaseException as e: raise e @@ -34,70 +60,26 @@ def main(): os.environ['HTTP_PROXY'] = cfg['http_proxy'] if 'https_proxy' in cfg and cfg['https_proxy'] != '': os.environ['HTTPS_PROXY'] = cfg['https_proxy'] - os.environ['NO_PROXY'] = 'https://api.sgroup.qq.com' make_necessary_dirs() - + # 启动主程序(cores/qqbot/core.py) qqBot.init(cfg) -def check_env(ch_mirror=False): +def check_env(): if not (sys.version_info.major == 3 and sys.version_info.minor >= 9): - print("请使用Python3.9+运行本项目") - input("按任意键退出...") + logger.error("请使用 Python3.9+ 运行本项目。按任意键退出。") + input("") exit() - if os.path.exists('requirements.txt'): - pth = 'requirements.txt' - else: - pth = 'QQChannelChatGPT' + os.sep + 'requirements.txt' - print("正在检查或下载第三方库,请耐心等待...") - try: - if ch_mirror: - print("使用阿里云镜像") - pipmain(['install', '-r', pth, '-i', - 'https://mirrors.aliyun.com/pypi/simple/']) - else: - pipmain(['install', '-r', pth]) - except BaseException as e: - print(e) - while True: - res = input( - "安装失败。\n如报错ValueError: check_hostname requires server_hostname,请尝试先关闭代理后重试。\n1.输入y回车重试\n2. 输入c回车使用国内镜像源下载\n3. 输入其他按键回车继续往下执行。") - if res == "y": - try: - pipmain(['install', '-r', pth]) - break - except BaseException as e: - print(e) - continue - elif res == "c": - try: - pipmain(['install', '-r', pth, '-i', - 'https://mirrors.aliyun.com/pypi/simple/']) - break - except BaseException as e: - print(e) - continue - else: - break - print("第三方库检查完毕。") - - if __name__ == "__main__": - args = sys.argv - - if '-cn' in args: - check_env(True) - else: - check_env() - + check_env() t = threading.Thread(target=main, daemon=True) t.start() try: t.join() except KeyboardInterrupt as e: - print("退出 AstrBot。") + logger.info("退出 AstrBot。") exit() diff --git a/model/command/command.py b/model/command/command.py index 55e8843ec..a25e10d6c 100644 --- a/model/command/command.py +++ b/model/command/command.py @@ -13,7 +13,6 @@ from nakuru.entities.components import ( from util import general_utils as gu from model.provider.provider import Provider from util.cmd_config import CmdConfig as cc -from util.general_utils import Logger from cores.astrbot.types import ( GlobalObject, AstrMessageEvent, @@ -24,6 +23,10 @@ from cores.astrbot.types import ( ) from typing import List, Tuple +from SparkleLogging.utils.core import LogManager +from logging import Logger + +logger: Logger = LogManager.GetLogger(log_name='astrbot-core') PLATFORM_QQCHAN = 'qqchan' PLATFORM_GOCQ = 'gocq' @@ -35,7 +38,6 @@ class Command: def __init__(self, provider: Provider, global_object: GlobalObject = None): self.provider = provider self.global_object = global_object - self.logger: Logger = global_object.logger async def check_command(self, message, @@ -85,11 +87,11 @@ class Command: if hit: return True, res except BaseException as e: - self.logger.log( - f"{plugin.metadata.plugin_name} 插件异常,原因: {str(e)}\n如果你没有相关装插件的想法, 请直接忽略此报错, 不影响其他功能的运行。", level=gu.LEVEL_WARNING) + logger.error( + f"{plugin.metadata.plugin_name} 插件异常,原因: {str(e)}\n如果你没有相关装插件的想法, 请直接忽略此报错, 不影响其他功能的运行。") except BaseException as e: - self.logger.log( - f"{plugin.metadata.plugin_name} 插件异常,原因: {str(e)}\n如果你没有相关装插件的想法, 请直接忽略此报错, 不影响其他功能的运行。", level=gu.LEVEL_WARNING) + logger.error( + f"{plugin.metadata.plugin_name} 插件异常,原因: {str(e)}\n如果你没有相关装插件的想法, 请直接忽略此报错, 不影响其他功能的运行。") if self.command_start_with(message, "nick"): return True, self.set_nick(message, platform, role) @@ -248,7 +250,7 @@ class Command: p = gu.create_markdown_image(msg) return [Image.fromFileSystem(p),] except BaseException as e: - self.logger.log(str(e)) + logger.error(str(e)) return msg def command_start_with(self, message: str, *args): diff --git a/model/platform/qq_gocq.py b/model/platform/qq_gocq.py index 0ca3c77bc..20955b101 100644 --- a/model/platform/qq_gocq.py +++ b/model/platform/qq_gocq.py @@ -16,6 +16,10 @@ import time from ._platfrom import Platform from ._message_parse import nakuru_message_parse_rev from cores.astrbot.types import MessageType, AstrBotMessage, MessageMember +from SparkleLogging.utils.core import LogManager +from logging import Logger + +logger: Logger = LogManager.GetLogger(log_name='astrbot-core') class FakeSource: @@ -34,7 +38,6 @@ class QQGOCQ(Platform): self.waiting = {} self.cc = CmdConfig() self.cfg = cfg - self.logger: gu.Logger = global_object.logger try: self.nick_qq = cfg['nick_qq'] @@ -106,8 +109,8 @@ class QQGOCQ(Platform): self.client.run() async def handle_msg(self, message: AstrBotMessage): - self.logger.log( - f"{message.sender.nickname}/{message.sender.user_id} -> {self.parse_message_outline(message)}", tag="QQ_GOCQ") + logger.info( + f"{message.sender.nickname}/{message.sender.user_id} -> {self.parse_message_outline(message)}") assert isinstance(message.raw_message, (GroupMessage, FriendMessage, GuildMessage)) @@ -188,8 +191,8 @@ class QQGOCQ(Platform): res = result_message - self.logger.log( - f"{source.user_id} <- {self.parse_message_outline(res)}", tag="QQ_GOCQ") + logger.info( + f"{source.user_id} <- {self.parse_message_outline(res)}") if isinstance(source, int): source = FakeSource("GroupMessage", source) diff --git a/model/platform/qq_official.py b/model/platform/qq_official.py index b3e6d1bdf..520d3d320 100644 --- a/model/platform/qq_official.py +++ b/model/platform/qq_official.py @@ -18,10 +18,12 @@ from ._message_parse import ( from cores.astrbot.types import MessageType, AstrBotMessage, MessageMember from typing import Union, List from nakuru.entities.components import BaseMessageComponent +from SparkleLogging.utils.core import LogManager +from logging import Logger + +logger: Logger = LogManager.GetLogger(log_name='astrbot-core') # QQ 机器人官方框架 - - class botClient(Client): def set_platform(self, platform: 'QQOfficial'): self.platform = platform @@ -59,7 +61,6 @@ class QQOfficial(Platform): self.token = cfg['qqbot']['token'] self.secret = cfg['qqbot_secret'] self.unique_session = cfg['uniqueSessionMode'] - self.logger: gu.Logger = global_object.logger qq_group = cfg['qqofficial_enable_group_message'] if qq_group: @@ -104,8 +105,8 @@ class QQOfficial(Platform): is_group = message.type != MessageType.FRIEND_MESSAGE _t = "/私聊" if not is_group else "" - self.logger.log( - f"{message.sender.nickname}({message.sender.user_id}{_t}) -> {self.parse_message_outline(message)}", tag="QQ_OFFICIAL") + logger.info( + f"{message.sender.nickname}({message.sender.user_id}{_t}) -> {self.parse_message_outline(message)}") # 解析出 session_id if self.unique_session or not is_group: @@ -157,8 +158,8 @@ class QQOfficial(Platform): source = message assert isinstance(source, (botpy.message.Message, botpy.message.GroupMessage, botpy.message.DirectMessage)) - self.logger.log( - f"{message.sender.nickname}({message.sender.user_id}) <- {self.parse_message_outline(res)}", tag="QQ_OFFICIAL") + logger.info( + f"{message.sender.nickname}({message.sender.user_id}) <- {self.parse_message_outline(res)}") plain_text = '' image_path = '' diff --git a/model/provider/openai_official.py b/model/provider/openai_official.py index 20b6a5c6f..cc1220f7b 100644 --- a/model/provider/openai_official.py +++ b/model/provider/openai_official.py @@ -14,7 +14,10 @@ from cores.database.conn import dbConn from model.provider.provider import Provider from util import general_utils as gu from util.cmd_config import CmdConfig -from util.general_utils import Logger +from SparkleLogging.utils.core import LogManager +from logging import Logger + +logger: Logger = LogManager.GetLogger(log_name='astrbot-core') abs_path = os.path.dirname(os.path.realpath(sys.argv[0])) + '/' @@ -23,7 +26,6 @@ abs_path = os.path.dirname(os.path.realpath(sys.argv[0])) + '/' class ProviderOpenAIOfficial(Provider): def __init__(self, cfg): self.cc = CmdConfig() - self.logger = Logger() self.key_list = [] # 如果 cfg['key'] 中有长度为 1 的字符串,那么是格式错误,直接报错 @@ -43,7 +45,7 @@ class ProviderOpenAIOfficial(Provider): self.api_base = None if 'api_base' in cfg and cfg['api_base'] != 'none' and cfg['api_base'] != '': self.api_base = cfg['api_base'] - self.logger.log(f"设置 api_base 为: {self.api_base}", tag="OpenAI") + logger.info(f"设置 api_base 为: {self.api_base}") # 创建 OpenAI Client self.client = AsyncOpenAI( @@ -52,8 +54,6 @@ class ProviderOpenAIOfficial(Provider): ) self.openai_model_configs: dict = cfg['chatGPTConfigs'] - self.logger.log( - f'加载 OpenAI Chat Configs: {self.openai_model_configs}', tag="OpenAI") self.openai_configs = cfg # 会话缓存 self.session_dict = {} @@ -69,10 +69,9 @@ class ProviderOpenAIOfficial(Provider): db1 = dbConn() for session in db1.get_all_session(): self.session_dict[session[0]] = json.loads(session[1])['data'] - self.logger.log("读取历史记录成功。", tag="OpenAI") + logger.info("读取历史记录成功。") except BaseException as e: - self.logger.log("读取历史记录失败,但不影响使用。", - level=gu.LEVEL_ERROR, tag="OpenAI") + logger.info("读取历史记录失败,但不影响使用。") # 创建转储定时器线程 threading.Thread(target=self.dump_history, daemon=True).start() @@ -143,15 +142,12 @@ class ProviderOpenAIOfficial(Provider): if self.openai_model_configs['max_tokens'] < len(_encoded_prompt): prompt = self.enc.decode(_encoded_prompt[:int( self.openai_model_configs['max_tokens']*0.80)]) - self.logger.log(f"注意,有一部分 prompt 文本由于超出 token 限制而被截断。", - level=gu.LEVEL_WARNING, tag="OpenAI") + logger.info(f"注意,有一部分 prompt 文本由于超出 token 限制而被截断。") cache_data_list, new_record, req = self.wrap( prompt, session_id, image_url) - self.logger.log(f"cache: {str(cache_data_list)}", - level=gu.LEVEL_DEBUG, tag="OpenAI") - self.logger.log(f"request: {str(req)}", - level=gu.LEVEL_DEBUG, tag="OpenAI") + logger.debug(f"cache: {str(cache_data_list)}") + logger.debug(f"request: {str(req)}") retry = 0 response = None err = '' @@ -194,15 +190,15 @@ class ProviderOpenAIOfficial(Provider): if 'Invalid content type. image_url is only supported by certain models.' in str(e): raise e if 'You exceeded' in str(e) or 'Billing hard limit has been reached' in str(e) or 'No API key provided' in str(e) or 'Incorrect API key provided' in str(e): - self.logger.log("当前 Key 已超额或异常, 正在切换", - level=gu.LEVEL_WARNING, tag="OpenAI") + logger.info("当前 Key 已超额或异常, 正在切换", + ) self.key_stat[self.client.api_key]['exceed'] = True is_switched = self.handle_switch_key() if not is_switched: raise e retry -= 1 elif 'maximum context length' in str(e): - self.logger.log("token 超限, 清空对应缓存,并进行消息截断", tag="OpenAI") + logger.info("token 超限, 清空对应缓存,并进行消息截断") self.session_dict[session_id] = [] prompt = prompt[:int(len(prompt)*truncate_rate)] truncate_rate -= 0.05 @@ -213,17 +209,17 @@ class ProviderOpenAIOfficial(Provider): time.sleep(30) continue else: - self.logger.log(str(e), level=gu.LEVEL_ERROR, tag="OpenAI") + logger.error(str(e)) time.sleep(2) err = str(e) retry += 1 if retry >= 10: - self.logger.log( - r"如果报错, 且您的机器在中国大陆内, 请确保您的电脑已经设置好代理软件(梯子), 并在配置文件设置了系统代理地址。详见 https://github.com/Soulter/QQChannelChatGPT/wiki", tag="OpenAI") + logger.warning( + r"如果报错, 且您的机器在中国大陆内, 请确保您的电脑已经设置好代理软件(梯子), 并在配置文件设置了系统代理地址。详见 https://github.com/Soulter/QQChannelChatGPT/wiki") raise BaseException("连接出错: "+str(err)) assert isinstance(response, ChatCompletion) - self.logger.log( - f"OPENAI RESPONSE: {response.usage}", level=gu.LEVEL_DEBUG, tag="OpenAI") + logger.debug( + f"OPENAI RESPONSE: {response.usage}") # 结果分类 choice = response.choices[0] @@ -288,18 +284,16 @@ class ProviderOpenAIOfficial(Provider): image_url.append(response.data[i].url) break except Exception as e: - self.logger.log(str(e), level=gu.LEVEL_ERROR) + logger.warning(str(e)) if 'You exceeded' in str(e) or 'Billing hard limit has been reached' in str( e) or 'No API key provided' in str(e) or 'Incorrect API key provided' in str(e): - self.logger.log("当前 Key 已超额或者不正常, 正在切换", - level=gu.LEVEL_WARNING, tag="OpenAI") + logger.warning("当前 Key 已超额或者不正常, 正在切换") self.key_stat[self.client.api_key]['exceed'] = True is_switched = self.handle_switch_key() if not is_switched: raise e elif 'Your request was rejected as a result of our safety system.' in str(e): - self.logger.log("您的请求被 OpenAI 安全系统拒绝, 请稍后再试", - level=gu.LEVEL_WARNING, tag="OpenAI") + logger.warning("您的请求被 OpenAI 安全系统拒绝, 请稍后再试") raise e else: retry += 1 @@ -378,12 +372,12 @@ class ProviderOpenAIOfficial(Provider): continue is_all_exceed = False self.client.api_key = key - self.logger.log( - f"切换到 Key: {key}(已使用 token: {self.key_stat[key]['used']})", level=gu.LEVEL_INFO, tag="OpenAI") + logger.warning( + f"切换到 Key: {key}(已使用 token: {self.key_stat[key]['used']})") break if is_all_exceed: - self.logger.log( - "所有 Key 已超额", level=gu.LEVEL_CRITICAL, tag="OpenAI") + logger.warning( + "所有 Key 已超额") return False return True diff --git a/requirements.txt b/requirements.txt index 8f4a5e040..d4085a7bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,3 +16,4 @@ websockets flask psutil lxml_html_clean +SparkleLogging diff --git a/util/general_utils.py b/util/general_utils.py index 5b4e072e9..7467342eb 100644 --- a/util/general_utils.py +++ b/util/general_utils.py @@ -14,132 +14,6 @@ import json import sys import psutil -PLATFORM_GOCQ = 'gocq' -PLATFORM_QQCHAN = 'qqchan' - -FG_COLORS = { - "black": "30", - "red": "31", - "green": "32", - "yellow": "33", - "blue": "34", - "purple": "35", - "cyan": "36", - "white": "37", - "default": "39", -} - -BG_COLORS = { - "black": "40", - "red": "41", - "green": "42", - "yellow": "43", - "blue": "44", - "purple": "45", - "cyan": "46", - "white": "47", - "default": "49", -} - -LEVEL_DEBUG = "DEBUG" -LEVEL_INFO = "INFO" -LEVEL_WARNING = "WARN" -LEVEL_ERROR = "ERROR" -LEVEL_CRITICAL = "CRITICAL" - -# 为了兼容旧版 -level_codes = { - LEVEL_DEBUG: logging.DEBUG, - LEVEL_INFO: logging.INFO, - LEVEL_WARNING: logging.WARNING, - LEVEL_ERROR: logging.ERROR, - LEVEL_CRITICAL: logging.CRITICAL, -} - -level_colors = { - "INFO": "green", - "WARN": "yellow", - "ERROR": "red", - "CRITICAL": "purple", -} - - -class Logger: - def __init__(self) -> None: - self.history = [] - - def log( - self, - msg: str, - level: str = "INFO", - tag: str = "System", - fg: str = None, - bg: str = None, - max_len: int = 50000, - err: Exception = None,): - """ - 日志打印函数 - """ - _set_level_code = level_codes[LEVEL_INFO] - if 'LOG_LEVEL' in os.environ and os.environ['LOG_LEVEL'] in level_codes: - _set_level_code = level_codes[os.environ['LOG_LEVEL']] - - if level in level_codes and level_codes[level] < _set_level_code: - return - - if err is not None: - msg += "\n异常原因: " + str(err) - level = LEVEL_ERROR - - if len(msg) > max_len: - msg = msg[:max_len] + "..." - now = datetime.datetime.now().strftime("%H:%M:%S") - - pres = [] - for line in msg.split("\n"): - if line == "\n": - pres.append("") - else: - pres.append(f"[{now}] [{tag}/{level}] {line}") - - if level == "INFO": - if fg is None: - fg = FG_COLORS["green"] - if bg is None: - bg = BG_COLORS["default"] - elif level == "WARN": - if fg is None: - fg = FG_COLORS["yellow"] - if bg is None: - bg = BG_COLORS["default"] - elif level == "ERROR": - if fg is None: - fg = FG_COLORS["red"] - if bg is None: - bg = BG_COLORS["default"] - elif level == "CRITICAL": - if fg is None: - fg = FG_COLORS["purple"] - if bg is None: - bg = BG_COLORS["default"] - - ret = "" - for line in pres: - ret += f"\033[{fg};{bg}m{line}\033[0m\n" - try: - requests.post("http://localhost:6185/api/log", - data=ret[:-1].encode(), timeout=1) - except BaseException as e: - pass - self.history.append(ret) - if len(self.history) > 100: - self.history = self.history[-100:] - print(ret[:-1]) - - -log = Logger().log - - def port_checker(port: int, host: str = "localhost"): sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sk.settimeout(1) @@ -477,7 +351,7 @@ def save_temp_img(img: Image) -> str: if time.time() - ctime > 3600: os.remove(path) except Exception as e: - print(f"清除临时文件失败: {e}", level=LEVEL_WARNING, tag="GeneralUtils") + print(f"清除临时文件失败: {e}") # 获得时间戳 timestamp = int(time.time()) diff --git a/util/plugin_util.py b/util/plugin_util.py index a9b80bf10..1045c108a 100644 --- a/util/plugin_util.py +++ b/util/plugin_util.py @@ -206,7 +206,8 @@ def update_plugin(plugin_name: str, cached_plugins: RegisteredPlugins): repo.remotes.origin.pull() # 读取插件的requirements.txt if os.path.exists(os.path.join(plugin_path, "requirements.txt")): - if pipmain(['install', '-r', os.path.join(plugin_path, "requirements.txt"), '--quiet']) != 0: + print("正在安装插件依赖...") + if pipmain(['install', '-r', os.path.join(plugin_path, "requirements.txt")]) != 0: raise Exception("插件依赖安装失败, 需要您手动pip安装对应插件的依赖。") ok, err = plugin_reload(cached_plugins) if not ok: