From 2acee60d7361f100622e4dbc25a99b7bdb0b810d Mon Sep 17 00:00:00 2001 From: kalicyh <34980061+kaliCYH@users.noreply.github.com> Date: Tue, 18 Feb 2025 00:07:19 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=A6=84=20refactor(log):=20colorful=20log?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ZhiKongTaiWeb/package.json | 1 + ZhiKongTaiWeb/yarn.lock | 160 ++++++++++++++++++++++++++++ app.py | 8 +- config/logger.py | 33 +++--- config/private_config.py | 28 ++--- core/auth.py | 10 +- core/connection.py | 58 +++++----- core/handle/abortHandle.py | 9 +- core/handle/audioHandle.py | 14 +-- core/handle/helloHandle.py | 4 +- core/handle/textHandle.py | 9 +- core/providers/llm/coze/coze.py | 17 +-- core/providers/llm/dify/dify.py | 8 +- core/providers/llm/gemini/gemini.py | 11 +- core/providers/llm/ollama/ollama.py | 7 +- core/providers/llm/openai/openai.py | 9 +- core/providers/tts/base.py | 11 +- core/providers/tts/gpt_sovits_v2.py | 8 +- core/utils/asr.py | 18 ++-- core/utils/llm.py | 4 +- core/utils/lock_manager.py | 11 +- core/utils/tts.py | 4 +- core/utils/vad.py | 12 +-- core/websocket_server.py | 10 +- manager/api/config.py | 23 ++-- manager/api/login.py | 11 +- manager/api/prompt.py | 8 +- manager/api/register.py | 14 +-- manager/api/user_manager.py | 29 ++--- manager/http_server.py | 14 ++- poetry.lock | 49 ++++++++- pyproject.toml | 1 + 32 files changed, 423 insertions(+), 190 deletions(-) diff --git a/ZhiKongTaiWeb/package.json b/ZhiKongTaiWeb/package.json index f67f2ffe..7b208585 100644 --- a/ZhiKongTaiWeb/package.json +++ b/ZhiKongTaiWeb/package.json @@ -9,6 +9,7 @@ "preview": "vite preview" }, "dependencies": { + "axios": "^1.7.9", "vue": "^3.5.10", "vue-router": "^4.0.0" }, diff --git a/ZhiKongTaiWeb/yarn.lock b/ZhiKongTaiWeb/yarn.lock index 593b431c..32182378 100644 --- a/ZhiKongTaiWeb/yarn.lock +++ b/ZhiKongTaiWeb/yarn.lock @@ -337,16 +337,86 @@ resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.13.tgz#87b309a6379c22b926e696893237826f64339b6f" integrity sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@^1.7.9: + version "1.7.9" + resolved "https://registry.npmmirror.com/axios/-/axios-1.7.9.tgz#d7d071380c132a24accda1b2cfc1535b79ec650a" + integrity sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +call-bind-apply-helpers@^1.0.1: + version "1.0.2" + resolved "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + csstype@^3.1.3: version "3.1.3" resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + entities@^4.5.0: version "4.5.0" resolved "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0: + version "1.1.1" + resolved "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + esbuild@^0.21.3: version "0.21.5" resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" @@ -381,11 +451,79 @@ estree-walker@^2.0.2: resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + +form-data@^4.0.0: + version "4.0.2" + resolved "https://registry.npmmirror.com/form-data/-/form-data-4.0.2.tgz#35cabbdd30c3ce73deb2c42d3c8d3ed9ca51794c" + integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + mime-types "^2.1.12" + fsevents@~2.3.2, fsevents@~2.3.3: version "2.3.3" resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-intrinsic@^1.2.6: + version "1.2.7" + resolved "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.7.tgz#dcfcb33d3272e15f445d15124bc0a216189b9044" + integrity sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + function-bind "^1.1.2" + get-proto "^1.0.0" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.0: + version "1.0.1" + resolved "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + magic-string@^0.30.11: version "0.30.17" resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" @@ -393,6 +531,23 @@ magic-string@^0.30.11: dependencies: "@jridgewell/sourcemap-codec" "^1.5.0" +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + nanoid@^3.3.8: version "3.3.8" resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" @@ -412,6 +567,11 @@ postcss@^8.4.43, postcss@^8.4.48: picocolors "^1.1.1" source-map-js "^1.2.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + rollup@^4.20.0: version "4.34.7" resolved "https://registry.npmmirror.com/rollup/-/rollup-4.34.7.tgz#e00d8550688a616a3481c6446bb688d4c753ba8f" diff --git a/app.py b/app.py index e7949885..f2288840 100644 --- a/app.py +++ b/app.py @@ -6,8 +6,10 @@ from manager.http_server import WebUI from aiohttp import web from core.utils.util import get_local_ip +TAG = __name__ + async def main(): - setup_logging() # 最先初始化日志 + logger = setup_logging() config = load_config() # 启动 WebSocket 服务器 @@ -28,9 +30,9 @@ async def main(): await site.start() webui_runner = runner local_ip = get_local_ip() - print(f"WebUI server is running at http://{local_ip}:{port}") + logger.bind(tag=TAG).info(f"WebUI server is running at http://{local_ip}:{port}") except Exception as e: - print(f"Failed to start WebUI server: {e}") + logger.bind(tag=TAG).error(f"Failed to start WebUI server: {e}") try: # 等待 WebSocket 服务器运行 diff --git a/config/logger.py b/config/logger.py index c3b82992..d4e87bc6 100644 --- a/config/logger.py +++ b/config/logger.py @@ -1,20 +1,27 @@ -import logging -import sys import os - +import sys +from loguru import logger def setup_logging(log_dir='tmp', data_dir='data'): - """配置全局日志""" + """配置全局彩色日志(不同区块不同标签)""" os.makedirs(log_dir, exist_ok=True) os.makedirs(data_dir, exist_ok=True) - logging.basicConfig( - level=logging.INFO, - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", - handlers=[ - logging.StreamHandler(sys.stdout), - logging.FileHandler(os.path.join(log_dir, "server.log"), encoding='utf-8') - ], - force=True + # 设置日志格式,时间、日志级别、标签、消息 + log_format = ( + "{time:YYYY-MM-DD HH:mm:ss}" + "[{extra[tag]}]" + " - {level} - " + "{message}" ) - return logging.getLogger(__name__) + + # 配置日志输出 + logger.remove() + + # 输出到控制台 + logger.add(sys.stdout, format=log_format, level="INFO") + + # 输出到文件 + logger.add(os.path.join(log_dir, "server.log"), format="{time:YYYY-MM-DD HH:mm:ss} - {name} - {level} - {extra[tag]} - {message}", level="INFO") + + return logger diff --git a/config/private_config.py b/config/private_config.py index e5839b46..9b925e1e 100644 --- a/config/private_config.py +++ b/config/private_config.py @@ -1,7 +1,7 @@ import os import time import yaml -import logging +from config.logger import setup_logging from typing import Dict, Any, Optional from copy import deepcopy from core.utils.util import get_project_dir @@ -9,12 +9,14 @@ from core.utils import asr, vad, llm, tts from manager.api.user_manager import UserManager from core.utils.lock_manager import FileLockManager +TAG = __name__ + class PrivateConfig: def __init__(self, device_id: str, default_config: Dict[str, Any], auth_code_gen=None): self.device_id = device_id self.default_config = default_config self.config_path = get_project_dir() + 'data/.private_config.yaml' - self.logger = logging.getLogger(__name__) + self.logger = setup_logging() self.private_config = {} self.auth_code_gen = auth_code_gen self.user_manager = UserManager() @@ -74,7 +76,7 @@ class PrivateConfig: self.lock_manager.release_lock(self.config_path) except Exception as e: - self.logger.error(f"Error handling private config: {e}") + self.logger.bind(tag=TAG).error(f"Error handling private config: {e}") self.private_config = {} async def update_config(self, selected_modules: Dict[str, str], prompt: str, nickname: str) -> bool: @@ -129,7 +131,7 @@ class PrivateConfig: self.lock_manager.release_lock(self.config_path) except Exception as e: - self.logger.error(f"Error updating config: {e}") + self.logger.bind(tag=TAG).error(f"Error updating config: {e}") return False async def delete_config(self) -> bool: @@ -163,13 +165,13 @@ class PrivateConfig: self.lock_manager.release_lock(self.config_path) except Exception as e: - self.logger.error(f"Error deleting config: {e}") + self.logger.bind(tag=TAG).error(f"Error deleting config: {e}") return False def create_private_instances(self): # 判断存在私有配置,并且self.device_id在私有配置中 if not self.private_config: - logging.error("Private config not found for device_id: %s", self.device_id) + self.logger.bind(tag=TAG).error("Private config not found for device_id: {}", self.device_id) return None, None, None, None """创建私有处理模块实例""" @@ -208,7 +210,7 @@ class PrivateConfig: timestamp: 指定的时间戳,不传则使用当前时间 """ if not self.private_config: - self.logger.error("Private config not found") + self.logger.bind(tag=TAG).error("Private config not found") return False try: @@ -235,7 +237,7 @@ class PrivateConfig: self.lock_manager.release_lock(self.config_path) except Exception as e: - self.logger.error(f"Error updating last chat time: {e}") + self.logger.bind(tag=TAG).error(f"Error updating last chat time: {e}") return False def get_auth_code(self) -> str: @@ -252,7 +254,7 @@ class PrivateConfig: try: # 检查用户是否存在 if not self.user_manager.get_user(username): - self.logger.error(f"User {username} not found") + self.logger.bind(tag=TAG).error(f"User {username} not found") return False # 读取所有配置 @@ -260,12 +262,12 @@ class PrivateConfig: all_configs = yaml.safe_load(f) or {} if self.device_id not in all_configs: - self.logger.error(f"Device {self.device_id} not found") + self.logger.bind(tag=TAG).error(f"Device {self.device_id} not found") return False # 删除认证码 auth_code = all_configs[self.device_id].get('auth_code') - self.logger.info(f"Binding user {username} to device {self.device_id}") + self.logger.bind(tag=TAG).info(f"Binding user {username} to device {self.device_id}") if auth_code: del all_configs[self.device_id]['auth_code'] @@ -293,7 +295,7 @@ class PrivateConfig: self.lock_manager.release_lock(self.config_path) except Exception as e: - self.logger.error(f"Error binding user: {e}") + self.logger.bind(tag=TAG).error(f"Error binding user: {e}") return False async def unbind_user(self) -> bool: @@ -330,7 +332,7 @@ class PrivateConfig: self.lock_manager.release_lock(self.config_path) except Exception as e: - self.logger.error(f"Error unbinding user: {e}") + self.logger.bind(tag=TAG).error(f"Error unbinding user: {e}") return False def get_owner(self) -> Optional[str]: diff --git a/core/auth.py b/core/auth.py index dcf994eb..8aaa4f00 100755 --- a/core/auth.py +++ b/core/auth.py @@ -1,6 +1,6 @@ -import logging +from config.logger import setup_logging -logger = logging.getLogger(__name__) +TAG = __name__ class AuthenticationError(Exception): @@ -37,15 +37,15 @@ class AuthMiddleware: # 验证Authorization header auth_header = headers.get("authorization", "") if not auth_header.startswith("Bearer "): - logger.error("Missing or invalid Authorization header") + logger.bind(tag=TAG).error("Missing or invalid Authorization header") raise AuthenticationError("Missing or invalid Authorization header") token = auth_header.split(" ")[1] if token not in self.tokens: - logger.error(f"Invalid token: {token}") + logger.bind(tag=TAG).error(f"Invalid token: {token}") raise AuthenticationError("Invalid token") - logger.info(f"Authentication successful - Device: {device_id}, Token: {self.tokens[token]}") + logger.bind(tag=TAG).info(f"Authentication successful - Device: {device_id}, Token: {self.tokens[token]}") return True def get_token_name(self, token): diff --git a/core/connection.py b/core/connection.py index e8b42340..ab5d3957 100644 --- a/core/connection.py +++ b/core/connection.py @@ -4,7 +4,7 @@ import uuid import time import queue import asyncio -import logging +from config.logger import setup_logging import threading import websockets from typing import Dict, Any @@ -19,10 +19,12 @@ from config.private_config import PrivateConfig from core.auth import AuthMiddleware, AuthenticationError from core.utils.auth_code_gen import AuthCodeGenerator # 添加导入 +TAG = __name__ + class ConnectionHandler: def __init__(self, config: Dict[str, Any], _vad, _asr, _llm, _tts): self.config = config - self.logger = logging.getLogger(__name__) + self.logger = setup_logging() self.auth = AuthMiddleware(config) self.websocket = None @@ -85,7 +87,7 @@ class ConnectionHandler: try: # 获取并验证headers self.headers = dict(ws.request.headers) - self.logger.info(f"New connection request - Headers: {self.headers}") + self.logger.bind(tag=TAG).info(f"New connection request - Headers: {self.headers}") # 进行认证 await self.auth.authenticate(self.headers) @@ -94,7 +96,7 @@ class ConnectionHandler: # Load private configuration if device_id is provided bUsePrivateConfig = self.config.get("use_private_config", False) - logging.info(f"bUsePrivateConfig: {bUsePrivateConfig}, device_id: {device_id}") + self.info(f"bUsePrivateConfig: {bUsePrivateConfig}, device_id: {device_id}") if bUsePrivateConfig and device_id: try: self.private_config = PrivateConfig(device_id, self.config, self.auth_code_gen) @@ -112,12 +114,12 @@ class ConnectionHandler: self.asr = asr self.llm = llm self.tts = tts - self.logger.info(f"Loaded private config and instances for device {device_id}") + self.logger.bind(tag=TAG).info(f"Loaded private config and instances for device {device_id}") else: - self.logger.error(f"Failed to create instances for device {device_id}") + self.logger.bind(tag=TAG).error(f"Failed to create instances for device {device_id}") self.private_config = None except Exception as e: - self.logger.error(f"Error initializing private config: {e}") + self.logger.bind(tag=TAG).error(f"Error initializing private config: {e}") self.private_config = None raise @@ -138,15 +140,15 @@ class ConnectionHandler: async for message in self.websocket: await self._route_message(message) except websockets.exceptions.ConnectionClosed: - self.logger.info("客户端断开连接") + self.logger.bind(tag=TAG).info("客户端断开连接") await self.close() except AuthenticationError as e: - self.logger.error(f"Authentication failed: {str(e)}") + self.logger.bind(tag=TAG).error(f"Authentication failed: {str(e)}") await ws.close() return except Exception as e: - self.logger.error(f"Connection error: {str(e)}") + self.logger.bind(tag=TAG).error(f"Connection error: {str(e)}") await ws.close() return @@ -208,7 +210,7 @@ class ConnectionHandler: start_time = time.time() # 记录开始时间 llm_responses = self.llm.response(self.session_id, self.dialogue.get_llm_dialogue()) except Exception as e: - self.logger.error(f"LLM 处理出错 {query}: {e}") + self.logger.bind(tag=TAG).error(f"LLM 处理出错 {query}: {e}") return None # 提交 TTS 任务到线程池 self.llm_finish_task = False @@ -220,7 +222,7 @@ class ConnectionHandler: break end_time = time.time() # 记录结束时间 - self.logger.debug(f"大模型返回时间时间: {end_time - start_time} 秒, 生成token={content}") + self.logger.bind(tag=TAG).debug(f"大模型返回时间时间: {end_time - start_time} 秒, 生成token={content}") if is_segment(response_message): segment_text = "".join(response_message[start:]) segment_text = get_string_no_punctuation_or_emoji(segment_text) @@ -241,7 +243,7 @@ class ConnectionHandler: self.llm_finish_task = True # 更新对话 self.dialogue.put(Message(role="assistant", content="".join(response_message))) - self.logger.debug(json.dumps(self.dialogue.get_llm_dialogue(), indent=4, ensure_ascii=False)) + self.logger.bind(tag=TAG).debug(json.dumps(self.dialogue.get_llm_dialogue(), indent=4, ensure_ascii=False)) return True def _priority_thread(self): @@ -253,25 +255,25 @@ class ConnectionHandler: continue text = None try: - self.logger.debug("正在处理TTS任务...") + self.logger.bind(tag=TAG).debug("正在处理TTS任务...") tts_file, text = future.result(timeout=10) if text is None or len(text) <= 0: continue if tts_file is None: - self.logger.error(f"TTS文件生成失败: {text}") + self.logger.bind(tag=TAG).error(f"TTS文件生成失败: {text}") continue - self.logger.debug(f"TTS文件生成完毕,文件路径: {tts_file}") + self.logger.bind(tag=TAG).debug(f"TTS文件生成完毕,文件路径: {tts_file}") if os.path.exists(tts_file): opus_datas, duration = self.tts.wav_to_opus_data(tts_file) else: - self.logger.error(f"TTS文件不存在: {tts_file}") + self.logger.bind(tag=TAG).error(f"TTS文件不存在: {tts_file}") opus_datas = [] duration = 0 except TimeoutError: - self.logger.error("TTS 任务超时") + self.logger.bind(tag=TAG).error("TTS 任务超时") continue except Exception as e: - self.logger.error(f"TTS 任务出错: {e}") + self.logger.bind(tag=TAG).error(f"TTS 任务出错: {e}") continue if not self.client_abort: # 如果没有中途打断就发送语音 @@ -281,27 +283,27 @@ class ConnectionHandler: if self.tts.delete_audio_file and os.path.exists(tts_file): os.remove(tts_file) except Exception as e: - self.logger.error(f"TTS任务处理错误: {e}") + self.logger.bind(tag=TAG).error(f"TTS任务处理错误: {e}") self.clearSpeakStatus() asyncio.run_coroutine_threadsafe( self.websocket.send(json.dumps({"type": "tts", "state": "stop", "session_id": self.session_id})), self.loop ) - self.logger.error(f"tts_priority priority_thread: {text}{e}") + self.logger.bind(tag=TAG).error(f"tts_priority priority_thread: {text}{e}") def speak_and_play(self, text): if text is None or len(text) <= 0: - self.logger.info(f"无需tts转换,query为空,{text}") + self.logger.bind(tag=TAG).info(f"无需tts转换,query为空,{text}") return None, text tts_file = self.tts.to_tts(text) if tts_file is None: - self.logger.error(f"tts转换失败,{text}") + self.logger.bind(tag=TAG).error(f"tts转换失败,{text}") return None, text - self.logger.debug(f"TTS 文件生成完毕: {tts_file}") + self.logger.bind(tag=TAG).debug(f"TTS 文件生成完毕: {tts_file}") return tts_file, text def clearSpeakStatus(self): - self.logger.debug(f"清除服务端讲话状态") + self.logger.bind(tag=TAG).debug(f"清除服务端讲话状态") self.asr_server_receive = True self.tts_last_text = None self.tts_first_text = None @@ -310,7 +312,7 @@ class ConnectionHandler: def recode_first_last_text(self, text): if not self.tts_first_text: - self.logger.info(f"大模型说出第一句话: {text}") + self.logger.bind(tag=TAG).info(f"大模型说出第一句话: {text}") self.tts_first_text = text self.tts_last_text = text @@ -320,14 +322,14 @@ class ConnectionHandler: self.executor.shutdown(wait=False) if self.websocket: await self.websocket.close() - self.logger.info("连接资源已释放") + self.logger.bind(tag=TAG).info("连接资源已释放") def reset_vad_states(self): self.client_audio_buffer = bytes() self.client_have_voice = False self.client_have_voice_last_time = 0 self.client_voice_stop = False - self.logger.debug("VAD states reset.") + self.logger.bind(tag=TAG).debug("VAD states reset.") def stop_all_tasks(self): while self.scheduled_tasks: diff --git a/core/handle/abortHandle.py b/core/handle/abortHandle.py index 78549797..12d10ce4 100644 --- a/core/handle/abortHandle.py +++ b/core/handle/abortHandle.py @@ -1,11 +1,12 @@ import json -import logging +from config.logger import setup_logging -logger = logging.getLogger(__name__) +TAG = __name__ +logger = setup_logging() async def handleAbortMessage(conn): - logger.info("Abort message received") + logger.bind(tag=TAG).info("Abort message received") # 设置成打断状态,会自动打断llm、tts任务 conn.client_abort = True # 打断屏显任务 @@ -13,4 +14,4 @@ async def handleAbortMessage(conn): # 打断客户端说话状态 await conn.websocket.send(json.dumps({"type": "tts", "state": "stop", "session_id": conn.session_id})) conn.clearSpeakStatus() - logger.info("Abort message received-end") + logger.bind(tag=TAG).info("Abort message received-end") diff --git a/core/handle/audioHandle.py b/core/handle/audioHandle.py index c301e698..8d8d37ad 100644 --- a/core/handle/audioHandle.py +++ b/core/handle/audioHandle.py @@ -1,15 +1,17 @@ -import logging +from config.logger import setup_logging import json import asyncio import time from core.utils.util import remove_punctuation_and_length, get_string_no_punctuation_or_emoji -logger = logging.getLogger(__name__) +TAG = __name__ +logger = setup_logging() async def handleAudioMessage(conn, audio): if not conn.asr_server_receive: - logger.debug(f"前期数据处理中,暂停接收") + logger = setup_logging() + logger.bind(tag=TAG).debug(f"前期数据处理中,暂停接收") return if conn.client_listen_mode == "auto": have_voice = conn.vad.is_vad(conn, audio) @@ -28,7 +30,7 @@ async def handleAudioMessage(conn, audio): conn.client_abort = False conn.asr_server_receive = False text, file_path = conn.asr.speech_to_text(conn.asr_audio, conn.session_id) - logger.info(f"识别文本: {text}") + logger.bind(tag=TAG).info(f"识别文本: {text}") text_len, text_without_punctuation = remove_punctuation_and_length(text) if text_len <= conn.max_cmd_length and await handleCMDMessage(conn, text_without_punctuation): return @@ -44,7 +46,7 @@ async def handleCMDMessage(conn, text): cmd_exit = conn.cmd_exit for cmd in cmd_exit: if text == cmd: - logger.info("识别到明确的退出命令".format(text)) + logger.bind(tag=TAG).info("识别到明确的退出命令".format(text)) await finishToChat(conn) return True return False @@ -80,7 +82,7 @@ async def sendAudioMessage(conn, audios, duration, text): # 发送 tts.start if text == conn.tts_first_text: - logger.info(f"发送第一段语音: {text}") + logger.bind(tag=TAG).info(f"发送第一段语音: {text}") conn.tts_start_speak_time = time.time() # 发送 sentence_start(每个音频文件之前发送一次) diff --git a/core/handle/helloHandle.py b/core/handle/helloHandle.py index 1cd46987..bafcf09d 100644 --- a/core/handle/helloHandle.py +++ b/core/handle/helloHandle.py @@ -1,7 +1,7 @@ import json -import logging +from config.logger import setup_logging -logger = logging.getLogger(__name__) +logger = setup_logging() async def handleHelloMessage(conn): diff --git a/core/handle/textHandle.py b/core/handle/textHandle.py index 23da0fbd..9a6faaf0 100644 --- a/core/handle/textHandle.py +++ b/core/handle/textHandle.py @@ -1,15 +1,16 @@ -import logging +from config.logger import setup_logging import json from core.handle.abortHandle import handleAbortMessage from core.handle.helloHandle import handleHelloMessage from core.handle.audioHandle import startToChat -logger = logging.getLogger(__name__) +TAG = __name__ +logger = setup_logging() async def handleTextMessage(conn, message): """处理文本消息""" - logger.info(f"收到文本消息:{message}") + logger.bind(tag=TAG).info(f"收到文本消息:{message}") try: msg_json = json.loads(message) if isinstance(msg_json, int): @@ -22,7 +23,7 @@ async def handleTextMessage(conn, message): elif msg_json["type"] == "listen": if "mode" in msg_json: conn.client_listen_mode = msg_json["mode"] - logger.debug(f"客户端拾音模式:{conn.client_listen_mode}") + logger.bind(tag=TAG).debug(f"客户端拾音模式:{conn.client_listen_mode}") if msg_json["state"] == "start": conn.client_have_voice = True conn.client_voice_stop = False diff --git a/core/providers/llm/coze/coze.py b/core/providers/llm/coze/coze.py index 4b236572..8abdfbc5 100644 --- a/core/providers/llm/coze/coze.py +++ b/core/providers/llm/coze/coze.py @@ -1,10 +1,11 @@ -import logging +from config.logger import setup_logging import requests import json import re from core.providers.llm.base import LLMProviderBase -logger = logging.getLogger(__name__) +TAG = __name__ +logger = setup_logging() # 定义用于匹配中文标点符号的正则表达式(包括句号、感叹号、问号、分号) punctuation_pattern = re.compile(r'([。!?;])') @@ -27,7 +28,7 @@ class LLMProvider(LLMProviderBase): "query": last_msg["content"], "stream": True } - logger.info(f"发送到 Coze API 的请求数据: {json.dumps(data, ensure_ascii=False)}") + logger.bind(tag=TAG).info(f"发送到 Coze API 的请求数据: {json.dumps(data, ensure_ascii=False)}") headers = { 'Authorization': f'Bearer {self.personal_access_token}', @@ -43,7 +44,7 @@ class LLMProvider(LLMProviderBase): json=data, stream=True ) - logger.info(f"请求状态: {response.status_code}") + logger.bind(tag=TAG).info(f"请求状态: {response.status_code}") if response.status_code == 200: # 对每一行流数据进行处理,不做跨块累积 @@ -54,7 +55,7 @@ class LLMProvider(LLMProviderBase): # 使用 utf-8 解码,错误部分用替换符 line = line_bytes.decode('utf-8', errors='replace') except Exception as e: - logger.error(f"解码失败: {e}") + logger.bind(tag=TAG).error(f"解码失败: {e}") continue if line.startswith("data:"): data_str = line[len("data:"):].strip() @@ -63,7 +64,7 @@ class LLMProvider(LLMProviderBase): try: data_chunk = json.loads(data_str) except json.JSONDecodeError as e: - logger.error(f"JSON解析失败: {e} 数据: {line}") + logger.bind(tag=TAG).error(f"JSON解析失败: {e} 数据: {line}") continue msg = data_chunk.get("message", {}) if msg.get("role") == "assistant" and msg.get("type") == "answer": @@ -88,8 +89,8 @@ class LLMProvider(LLMProviderBase): if content.strip(): yield content.strip() else: - logger.error(f"请求失败,状态码: {response.status_code}") + logger.bind(tag=TAG).error(f"请求失败,状态码: {response.status_code}") yield f"【Coze服务响应异常:请求失败,状态码 {response.status_code}】" except Exception as e: - logger.error(f"Error in Coze response generation: {e}") + logger.bind(tag=TAG).error(f"Error in Coze response generation: {e}") yield "【Coze服务响应异常】" diff --git a/core/providers/llm/dify/dify.py b/core/providers/llm/dify/dify.py index 48f069ee..0e9c821a 100644 --- a/core/providers/llm/dify/dify.py +++ b/core/providers/llm/dify/dify.py @@ -1,10 +1,10 @@ import json -import logging +from config.logger import setup_logging import requests from core.providers.llm.base import LLMProviderBase -logger = logging.getLogger(__name__) - +TAG = __name__ +logger = setup_logging() class LLMProvider(LLMProviderBase): def __init__(self, config): @@ -35,5 +35,5 @@ class LLMProvider(LLMProviderBase): yield event['answer'] except Exception as e: - logger.error(f"Error in response generation: {e}") + logger.bind(tag=TAG).error(f"Error in response generation: {e}") yield "【服务响应异常】" diff --git a/core/providers/llm/gemini/gemini.py b/core/providers/llm/gemini/gemini.py index ef1351cd..7ddf84fa 100644 --- a/core/providers/llm/gemini/gemini.py +++ b/core/providers/llm/gemini/gemini.py @@ -1,8 +1,9 @@ -import logging +from config.logger import setup_logging import google.generativeai as genai from core.providers.llm.base import LLMProviderBase -logger = logging.getLogger(__name__) +TAG = __name__ +logger = setup_logging() class LLMProvider(LLMProviderBase): def __init__(self, config): @@ -11,7 +12,7 @@ class LLMProvider(LLMProviderBase): self.api_key = config.get("api_key") if not self.api_key or "你" in self.api_key: - logger.error("你还没配置Gemini LLM的密钥,请在配置文件中配置密钥,否则无法正常工作") + logger.bind(tag=TAG).error("你还没配置Gemini LLM的密钥,请在配置文件中配置密钥,否则无法正常工作") return try: @@ -28,7 +29,7 @@ class LLMProvider(LLMProviderBase): } self.chat = None except Exception as e: - logger.error(f"Gemini初始化失败: {e}") + logger.bind(tag=TAG).error(f"Gemini初始化失败: {e}") self.model = None def response(self, session_id, dialogue): @@ -69,7 +70,7 @@ class LLMProvider(LLMProviderBase): except Exception as e: error_msg = str(e) - logger.error(f"Gemini响应生成错误: {error_msg}") + logger.bind(tag=TAG).error(f"Gemini响应生成错误: {error_msg}") # 针对不同错误返回友好提示 if "Rate limit" in error_msg: diff --git a/core/providers/llm/ollama/ollama.py b/core/providers/llm/ollama/ollama.py index 28c377d3..d0250255 100644 --- a/core/providers/llm/ollama/ollama.py +++ b/core/providers/llm/ollama/ollama.py @@ -1,8 +1,9 @@ -import logging +from config.logger import setup_logging import requests, json from core.providers.llm.base import LLMProviderBase -logger = logging.getLogger(__name__) +TAG = __name__ +logger = setup_logging() class LLMProvider(LLMProviderBase): @@ -41,5 +42,5 @@ class LLMProvider(LLMProviderBase): yield json_response["response"] except Exception as e: - logger.error(f"Error in Ollama response generation: {e}") + logger.bind(tag=TAG).error(f"Error in Ollama response generation: {e}") yield "【Ollama服务响应异常】" diff --git a/core/providers/llm/openai/openai.py b/core/providers/llm/openai/openai.py index a0e0f33f..e574d50d 100644 --- a/core/providers/llm/openai/openai.py +++ b/core/providers/llm/openai/openai.py @@ -1,8 +1,9 @@ -import logging +from config.logger import setup_logging import openai from core.providers.llm.base import LLMProviderBase -logger = logging.getLogger(__name__) +TAG = __name__ +logger = setup_logging() class LLMProvider(LLMProviderBase): @@ -14,7 +15,7 @@ class LLMProvider(LLMProviderBase): else: self.base_url = config.get("url") if "你" in self.api_key: - logger.error("你还没配置LLM的密钥,请在配置文件中配置密钥,否则无法正常工作") + logger.bind(tag=TAG).error("你还没配置LLM的密钥,请在配置文件中配置密钥,否则无法正常工作") self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url) def response(self, session_id, dialogue): @@ -32,4 +33,4 @@ class LLMProvider(LLMProviderBase): if content: # 仅在content非空时生成 yield content except Exception as e: - logger.error(f"Error in response generation: {e}") + logger.bind(tag=TAG).error(f"Error in response generation: {e}") diff --git a/core/providers/tts/base.py b/core/providers/tts/base.py index ba5cdbdb..54438f31 100644 --- a/core/providers/tts/base.py +++ b/core/providers/tts/base.py @@ -1,12 +1,13 @@ import asyncio -import logging +from config.logger import setup_logging import os import numpy as np import opuslib from pydub import AudioSegment from abc import ABC, abstractmethod -logger = logging.getLogger(__name__) +TAG = __name__ +logger = setup_logging() class TTSProviderBase(ABC): @@ -26,14 +27,14 @@ class TTSProviderBase(ABC): asyncio.run(self.text_to_speak(text, tmp_file)) if not os.path.exists(tmp_file): max_repeat_time = max_repeat_time - 1 - logger.error(f"语音生成失败: {text}:{tmp_file},再试{max_repeat_time}次") + logger.bind(tag=TAG).error(f"语音生成失败: {text}:{tmp_file},再试{max_repeat_time}次") if max_repeat_time > 0: - logger.info(f"语音生成成功: {text}:{tmp_file},重试{5 - max_repeat_time}次") + logger.bind(tag=TAG).info(f"语音生成成功: {text}:{tmp_file},重试{5 - max_repeat_time}次") return tmp_file except Exception as e: - logger.info(f"Failed to generate TTS file: {e}") + logger.bind(tag=TAG).info(f"Failed to generate TTS file: {e}") return None @abstractmethod diff --git a/core/providers/tts/gpt_sovits_v2.py b/core/providers/tts/gpt_sovits_v2.py index 709e5338..ae20beb6 100644 --- a/core/providers/tts/gpt_sovits_v2.py +++ b/core/providers/tts/gpt_sovits_v2.py @@ -3,10 +3,12 @@ import uuid import json import base64 import requests -import logging +from config.logger import setup_logging from datetime import datetime from core.providers.tts.base import TTSProviderBase -logger = logging.getLogger(__name__) + +TAG = __name__ +logger = setup_logging() class TTSProvider(TTSProviderBase): def __init__(self, config, delete_audio_file): @@ -62,4 +64,4 @@ class TTSProvider(TTSProviderBase): with open(output_file, "wb") as file: file.write(resp.content) else: - logger.error(f"GPT_SoVITS_V2 TTS请求失败: {resp.status_code} - {resp.text}") + logger.bind(tag=TAG).error(f"GPT_SoVITS_V2 TTS请求失败: {resp.status_code} - {resp.text}") diff --git a/core/utils/asr.py b/core/utils/asr.py index 091df283..1c23fe55 100644 --- a/core/utils/asr.py +++ b/core/utils/asr.py @@ -2,7 +2,7 @@ import time import wave import os from abc import ABC, abstractmethod -import logging +from config.logger import setup_logging from typing import Optional, Tuple, List import uuid @@ -10,8 +10,8 @@ import opuslib from funasr import AutoModel from funasr.utils.postprocess_utils import rich_transcription_postprocess -logger = logging.getLogger(__name__) - +TAG = __name__ +logger = setup_logging() class ASR(ABC): @abstractmethod @@ -55,7 +55,7 @@ class FunASR(ASR): pcm_frame = decoder.decode(opus_packet, 960) # 960 samples = 60ms pcm_data.append(pcm_frame) except opuslib.OpusError as e: - logger.error(f"Opus解码错误: {e}", exc_info=True) + logger.bind(tag=TAG).error(f"Opus解码错误: {e}", exc_info=True) with wave.open(file_path, "wb") as wf: wf.setnchannels(1) @@ -72,7 +72,7 @@ class FunASR(ASR): # 保存音频文件 start_time = time.time() file_path = self.save_audio_to_file(opus_data, session_id) - logger.debug(f"音频文件保存耗时: {time.time() - start_time:.3f}s | 路径: {file_path}") + logger.bind(tag=TAG).debug(f"音频文件保存耗时: {time.time() - start_time:.3f}s | 路径: {file_path}") # 语音识别 start_time = time.time() @@ -84,12 +84,12 @@ class FunASR(ASR): batch_size_s=60, ) text = rich_transcription_postprocess(result[0]["text"]) - logger.debug(f"语音识别耗时: {time.time() - start_time:.3f}s | 结果: {text}") + logger.bind(tag=TAG).debug(f"语音识别耗时: {time.time() - start_time:.3f}s | 结果: {text}") return text, file_path except Exception as e: - logger.error(f"语音识别失败: {e}", exc_info=True) + logger.bind(tag=TAG).error(f"语音识别失败: {e}", exc_info=True) return None, None finally: @@ -97,9 +97,9 @@ class FunASR(ASR): if self.delete_audio_file and file_path and os.path.exists(file_path): try: os.remove(file_path) - logger.debug(f"已删除临时音频文件: {file_path}") + logger.bind(tag=TAG).debug(f"已删除临时音频文件: {file_path}") except Exception as e: - logger.error(f"文件删除失败: {file_path} | 错误: {e}") + logger.bind(tag=TAG).error(f"文件删除失败: {file_path} | 错误: {e}") def create_instance(class_name: str, *args, **kwargs) -> ASR: diff --git a/core/utils/llm.py b/core/utils/llm.py index 23c7ebae..f48c103d 100644 --- a/core/utils/llm.py +++ b/core/utils/llm.py @@ -1,13 +1,13 @@ import os import sys -import logging +from config.logger import setup_logging import importlib from datetime import datetime from core.utils.util import is_segment from core.utils.util import get_string_no_punctuation_or_emoji from core.utils.util import read_config, get_project_dir -logger = logging.getLogger(__name__) +logger = setup_logging() def create_instance(class_name, *args, **kwargs): diff --git a/core/utils/lock_manager.py b/core/utils/lock_manager.py index 049a33f5..f1db6fc7 100644 --- a/core/utils/lock_manager.py +++ b/core/utils/lock_manager.py @@ -1,8 +1,9 @@ import asyncio from typing import Dict -import logging +from config.logger import setup_logging -logger = logging.getLogger(__name__) +TAG = __name__ +logger = setup_logging() class FileLockManager: _instance = None @@ -25,7 +26,7 @@ class FileLockManager: """获取锁""" lock = cls.get_lock(file_path) await lock.acquire() - logger.debug(f"Acquired lock for {file_path}") + logger.bind(tag=TAG).debug(f"Acquired lock for {file_path}") @classmethod def release_lock(cls, file_path: str): @@ -33,6 +34,6 @@ class FileLockManager: if file_path in cls._locks: try: cls._locks[file_path].release() - logger.debug(f"Released lock for {file_path}") + logger.bind(tag=TAG).debug(f"Released lock for {file_path}") except RuntimeError as e: - logger.warning(f"Failed to release lock for {file_path}: {e}") + logger.bind(tag=TAG).warning(f"Failed to release lock for {file_path}: {e}") diff --git a/core/utils/tts.py b/core/utils/tts.py index 1ffc7e23..2fc733f7 100644 --- a/core/utils/tts.py +++ b/core/utils/tts.py @@ -1,11 +1,11 @@ import os import sys -import logging +from config.logger import setup_logging import importlib from datetime import datetime from core.utils.util import read_config, get_project_dir -logger = logging.getLogger(__name__) +logger = setup_logging() def create_instance(class_name, *args, **kwargs): diff --git a/core/utils/vad.py b/core/utils/vad.py index 8a05dc6a..05e11eb5 100644 --- a/core/utils/vad.py +++ b/core/utils/vad.py @@ -1,12 +1,12 @@ from abc import ABC, abstractmethod -import logging +from config.logger import setup_logging import opuslib import time import numpy as np import torch -logger = logging.getLogger(__name__) - +TAG = __name__ +logger = setup_logging() class VAD(ABC): @abstractmethod @@ -17,7 +17,7 @@ class VAD(ABC): class SileroVAD(VAD): def __init__(self, config): - logger.info("SileroVAD", config) + logger.bind(tag=TAG).info("SileroVAD", config) self.model, self.utils = torch.hub.load(repo_or_dir=config["model_dir"], source='local', model='silero_vad', @@ -60,9 +60,9 @@ class SileroVAD(VAD): return client_have_voice except opuslib.OpusError as e: - logger.info(f"解码错误: {e}") + logger.bind(tag=TAG).info(f"解码错误: {e}") except Exception as e: - logger.error(f"Error processing audio packet: {e}") + logger.bind(tag=TAG).error(f"Error processing audio packet: {e}") def create_instance(class_name, *args, **kwargs) -> VAD: diff --git a/core/websocket_server.py b/core/websocket_server.py index 0eb76e83..d8dce04d 100644 --- a/core/websocket_server.py +++ b/core/websocket_server.py @@ -1,15 +1,16 @@ import asyncio import websockets -import logging +from config.logger import setup_logging from core.connection import ConnectionHandler from core.utils.util import get_local_ip from core.utils import asr, vad, llm, tts +TAG = __name__ class WebSocketServer: def __init__(self, config: dict): self.config = config - self.logger = logging.getLogger(__name__) + self.logger = setup_logging() self._vad, self._asr, self._llm, self._tts = self._create_processing_instances() def _create_processing_instances(self): @@ -46,9 +47,8 @@ class WebSocketServer: host = server_config["ip"] port = server_config["port"] - self.logger.info("=======下面的地址是websocket协议地址,请勿用浏览器访问=======") - self.logger.info("Server is running at ws://%s:%s", get_local_ip(), port) - self.logger.info("=======上面的地址是websocket协议地址,请勿用浏览器访问=======") + self.logger.bind(tag=TAG).info("Server is running at ws://{}:{}", get_local_ip(), port) + self.logger.bind(tag=TAG).info("=======上面的地址是websocket协议地址,请勿用浏览器访问=======") async with websockets.serve( self._handle_connection, host, diff --git a/manager/api/config.py b/manager/api/config.py index cd1bc577..13588c9b 100644 --- a/manager/api/config.py +++ b/manager/api/config.py @@ -1,13 +1,14 @@ import os import yaml -import logging +from config.logger import setup_logging from aiohttp import web from core.utils.util import get_project_dir from config.private_config import PrivateConfig from manager.api.user_manager import UserManager # 添加导入 from core.utils.auth_code_gen import AuthCodeGenerator # 添加导入 -logger = logging.getLogger(__name__) +TAG = __name__ +logger = setup_logging() class ConfigHandler: def __init__(self, session_manager): @@ -42,7 +43,7 @@ class ConfigHandler: }) except Exception as e: - logger.error(f"Error getting module options: {str(e)}", exc_info=True) + logger.bind(tag=TAG).error(f"Error getting module options: {str(e)}", exc_info=True) return web.json_response({ 'success': False, 'message': '获取配置选项失败' @@ -52,11 +53,11 @@ class ConfigHandler: """只返回用户绑定的设备配置""" try: username = request['username'] - logger.info(f"Getting devices for user: {username}") + logger.bind(tag=TAG).info(f"Getting devices for user: {username}") # 从用户管理器获取用户的设备列表 user_devices = await self.user_manager.get_user_devices(username) - logger.info(f"User {username} has devices: {user_devices}") + logger.bind(tag=TAG).info(f"User {username} has devices: {user_devices}") # 读取所有配置 all_configs = {} @@ -71,7 +72,7 @@ class ConfigHandler: if device_id in user_devices } - logger.info(f"Returning {len(user_configs)} device configs for user {username}") + logger.bind(tag=TAG).info(f"Returning {len(user_configs)} device configs for user {username}") return web.json_response({ 'success': True, 'data': user_configs, @@ -79,7 +80,7 @@ class ConfigHandler: }) except Exception as e: - logger.error(f"Error getting devices for user {request.get('username')}: {str(e)}", exc_info=True) + logger.bind(tag=TAG).error(f"Error getting devices for user {request.get('username')}: {str(e)}", exc_info=True) return web.json_response({ 'success': False, 'message': f'获取设备列表失败: {str(e)}' @@ -101,7 +102,7 @@ class ConfigHandler: 'message': '无权操作此设备' }, status=403) - logger.info(f"Device config updated: {device_id} :\n{config}") + logger.bind(tag=TAG).info(f"Device config updated: {device_id} :\n{config}") if not device_id or not config: return web.json_response({ 'success': False, @@ -128,7 +129,7 @@ class ConfigHandler: }) except Exception as e: - logger.error(f"Error saving device config: {str(e)}", exc_info=True) + logger.bind(tag=TAG).error(f"Error saving device config: {str(e)}", exc_info=True) return web.json_response({ 'success': False, 'message': f'保存配置失败: {str(e)}' @@ -163,7 +164,7 @@ class ConfigHandler: }) except Exception as e: - logger.error(f"Error deleting device config: {str(e)}", exc_info=True) + logger.bind(tag=TAG).error(f"Error deleting device config: {str(e)}", exc_info=True) return web.json_response({ 'success': False, 'message': f'删除配置失败: {str(e)}' @@ -219,7 +220,7 @@ class ConfigHandler: }, status=500) except Exception as e: - logger.error(f"Error binding device: {str(e)}", exc_info=True) + logger.bind(tag=TAG).error(f"Error binding device: {str(e)}", exc_info=True) return web.json_response({ 'success': False, 'message': f'绑定设备失败: {str(e)}' diff --git a/manager/api/login.py b/manager/api/login.py index bc0ddd7a..22070357 100644 --- a/manager/api/login.py +++ b/manager/api/login.py @@ -1,8 +1,9 @@ -import logging +from config.logger import setup_logging from aiohttp import web import datetime -logger = logging.getLogger(__name__) +TAG = __name__ +logger = setup_logging() class LoginHandler: def __init__(self, user_manager, session_manager): @@ -17,7 +18,7 @@ class LoginHandler: password = data.get('password') if not username or not password: - logger.warning(f"Login attempt with empty credentials from {request.remote}") + logger.bind(tag=TAG).warning(f"Login attempt with empty credentials from {request.remote}") return web.json_response({ 'success': False, 'message': '用户名和密码不能为空' @@ -25,7 +26,7 @@ class LoginHandler: stored_user = await self.user_manager.get_user(username) if not stored_user or stored_user['password'] != self.user_manager.hash_password(password): - logger.warning(f"Failed login attempt for user {username} from {request.remote}") + logger.bind(tag=TAG).warning(f"Failed login attempt for user {username} from {request.remote}") return web.json_response({ 'success': False, 'message': '用户名或密码错误' @@ -45,7 +46,7 @@ class LoginHandler: }) except Exception as e: - logger.error(f"Login error: {str(e)}", exc_info=True) + logger.bind(tag=TAG).error(f"Login error: {str(e)}", exc_info=True) return web.json_response({ 'success': False, 'message': '登录失败,请稍后重试' diff --git a/manager/api/prompt.py b/manager/api/prompt.py index 1fe5aa08..2a4232fa 100644 --- a/manager/api/prompt.py +++ b/manager/api/prompt.py @@ -1,12 +1,12 @@ -import logging +from config.logger import setup_logging from aiohttp import web from config.settings import update_config from ruamel.yaml.scalarstring import PreservedScalarString from manager.api.auth import verify_token from manager.api.response import response_unauthorized, response_success, response_error -logger = logging.getLogger(__name__) - +TAG = __name__ +logger = setup_logging() class PromptApi: def __init__(self, config): @@ -38,5 +38,5 @@ class PromptApi: return response_success() except Exception as e: - logger.error(f"Failed to update prompt: {e}") + logger.bind(tag=TAG).error(f"Failed to update prompt: {e}") return response_error(str(e)) diff --git a/manager/api/register.py b/manager/api/register.py index f963e5eb..27879afc 100644 --- a/manager/api/register.py +++ b/manager/api/register.py @@ -1,9 +1,9 @@ -import logging +from config.logger import setup_logging from aiohttp import web import datetime -logger = logging.getLogger(__name__) - +TAG = __name__ +logger = setup_logging() class RegisterHandler: def __init__(self, user_manager): @@ -17,7 +17,7 @@ class RegisterHandler: password = data.get('password') if not username or not password: - logger.warning(f"Registration attempt with empty credentials from {request.remote}") + logger.bind(tag=TAG).warning(f"Registration attempt with empty credentials from {request.remote}") return web.json_response({ 'success': False, 'message': '用户名和密码不能为空' @@ -25,7 +25,7 @@ class RegisterHandler: # 检查用户是否已存在 if await self.user_manager.get_user(username): - logger.warning(f"Registration attempt with existing username {username} from {request.remote}") + logger.bind(tag=TAG).warning(f"Registration attempt with existing username {username} from {request.remote}") return web.json_response({ 'success': False, 'message': '用户名已存在' @@ -41,14 +41,14 @@ class RegisterHandler: } await self.user_manager.add_user(username, user_data) - logger.info(f"Successfully registered new user {username} from {request.remote}") + logger.bind(tag=TAG).info(f"Successfully registered new user {username} from {request.remote}") return web.json_response({ 'success': True, 'message': '注册成功' }) except Exception as e: - logger.error(f"Register error: {str(e)}", exc_info=True) + logger.bind(tag=TAG).error(f"Register error: {str(e)}", exc_info=True) return web.json_response({ 'success': False, 'message': '注册失败,请稍后重试' diff --git a/manager/api/user_manager.py b/manager/api/user_manager.py index aa48ac25..abc7e3e5 100644 --- a/manager/api/user_manager.py +++ b/manager/api/user_manager.py @@ -1,11 +1,12 @@ import os import yaml import hashlib -import logging +from config.logger import setup_logging from core.utils.util import get_project_dir from core.utils.lock_manager import FileLockManager -logger = logging.getLogger(__name__) +TAG = __name__ +logger = setup_logging() class UserManager: def __init__(self): @@ -23,9 +24,9 @@ class UserManager: with open(self.secrets_path, 'w', encoding='utf-8') as f: yaml.dump(default_config, f) os.chmod(self.secrets_path, 0o600) - logger.info("Created new .secrets.yaml file") + logger.bind(tag=TAG).info("Created new .secrets.yaml file") except Exception as e: - logger.error(f"Failed to create .secrets.yaml: {e}") + logger.bind(tag=TAG).error(f"Failed to create .secrets.yaml: {e}") raise async def _load_user_data_internal(self): @@ -34,9 +35,9 @@ class UserManager: with open(self.secrets_path, 'r', encoding='utf-8') as f: data = yaml.safe_load(f) or {'users': {}} users = data['users'] - logger.debug("Successfully loaded user data") + logger.bind(tag=TAG).debug("Successfully loaded user data") except Exception as e: - logger.error(f"Failed to load user data: {e}") + logger.bind(tag=TAG).error(f"Failed to load user data: {e}") users = {} return users @@ -50,7 +51,7 @@ class UserManager: self.lock_manager.release_lock(self.secrets_path) except Exception as e: - logger.error(f"Failed to load user data: {e}") + logger.bind(tag=TAG).error(f"Failed to load user data: {e}") users = {} return users @@ -59,9 +60,9 @@ class UserManager: try: with open(self.secrets_path, 'w', encoding='utf-8') as f: yaml.dump({'users': users}, f) - logger.debug("Successfully saved user data") + logger.bind(tag=TAG).debug("Successfully saved user data") except Exception as e: - logger.error(f"Failed to save user data: {e}") + logger.bind(tag=TAG).error(f"Failed to save user data: {e}") raise async def save_user_data(self, users): @@ -73,7 +74,7 @@ class UserManager: finally: self.lock_manager.release_lock(self.secrets_path) except Exception as e: - logger.error(f"Failed to save user data: {e}") + logger.bind(tag=TAG).error(f"Failed to save user data: {e}") raise def hash_password(self, password): @@ -103,7 +104,7 @@ class UserManager: finally: self.lock_manager.release_lock(self.secrets_path) except Exception as e: - logger.error(f"Error adding user: {e}") + logger.bind(tag=TAG).error(f"Error adding user: {e}") raise async def update_user(self, username, data): @@ -120,7 +121,7 @@ class UserManager: finally: self.lock_manager.release_lock(self.secrets_path) except Exception as e: - logger.error(f"Error updating user: {e}") + logger.bind(tag=TAG).error(f"Error updating user: {e}") return False async def get_user_devices(self, username: str) -> list: @@ -151,7 +152,7 @@ class UserManager: finally: self.lock_manager.release_lock(self.secrets_path) except Exception as e: - logger.error(f"Error adding device: {e}") + logger.bind(tag=TAG).error(f"Error adding device: {e}") return False async def remove_device(self, username: str, device_id: str) -> bool: @@ -170,5 +171,5 @@ class UserManager: finally: self.lock_manager.release_lock(self.secrets_path) except Exception as e: - logger.error(f"Error removing device: {e}") + logger.bind(tag=TAG).error(f"Error removing device: {e}") return False diff --git a/manager/http_server.py b/manager/http_server.py index 202e4a2d..841ea876 100644 --- a/manager/http_server.py +++ b/manager/http_server.py @@ -5,10 +5,9 @@ current_dir = os.path.dirname(os.path.abspath(__file__)) root_dir = os.path.dirname(current_dir) sys.path.append(root_dir) -import logging +from config.logger import setup_logging from aiohttp import web from aiohttp_cors import setup as cors_setup, ResourceOptions -from core.utils.util import get_local_ip from manager.api.login import LoginHandler from manager.api.register import RegisterHandler from manager.api.user_manager import UserManager @@ -16,7 +15,8 @@ from manager.api.config import ConfigHandler from manager.session import SessionManager from functools import wraps -logger = logging.getLogger(__name__) +TAG = __name__ +logger = setup_logging() def auth_required(handler): """鉴权装饰器""" @@ -94,23 +94,21 @@ class WebUI: # 从请求头获取session_id session_id = request.headers.get('Authorization') if not session_id: - logger.warning("No session_id in Authorization header") + logger.bind(tag=TAG).warning("No session_id in Authorization header") return web.json_response({'error': 'Unauthorized'}, status=401) username = self.session_manager.validate_session(session_id) if not username: - logger.warning(f"Invalid session_id: {session_id}") + logger.bind(tag=TAG).warning(f"Invalid session_id: {session_id}") return web.json_response({'error': 'Unauthorized'}, status=401) request['username'] = username - logger.debug(f"Auth success for user: {username}") + logger.bind(tag=TAG).debug(f"Auth success for user: {username}") return await handler(request) return wrapper def run(self, host='0.0.0.0', port=8002): """运行服务器""" - local_ip = get_local_ip() - logger.info(f"WebUI server is running at http://{local_ip}:{port}") web.run_app(self.app, host=host, port=port) if __name__ == '__main__': diff --git a/poetry.lock b/poetry.lock index 7a2b337e..470620b6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -539,7 +539,7 @@ description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["main"] -markers = "platform_system == \"Windows\"" +markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -1813,6 +1813,30 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "mirrors" +[[package]] +name = "loguru" +version = "0.7.3" +description = "Python logging made (stupidly) simple" +optional = false +python-versions = ">=3.5,<4.0" +groups = ["main"] +files = [ + {file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"}, + {file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"}, +] + +[package.dependencies] +colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} +win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} + +[package.extras] +dev = ["Sphinx (==8.1.3) ; python_version >= \"3.11\"", "build (==1.2.2) ; python_version >= \"3.11\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.5.0) ; python_version >= \"3.8\"", "mypy (==v0.910) ; python_version < \"3.6\"", "mypy (==v0.971) ; python_version == \"3.6\"", "mypy (==v1.13.0) ; python_version >= \"3.8\"", "mypy (==v1.4.1) ; python_version == \"3.7\"", "myst-parser (==4.0.0) ; python_version >= \"3.11\"", "pre-commit (==4.0.1) ; python_version >= \"3.9\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==8.3.2) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==5.0.0) ; python_version == \"3.8\"", "pytest-cov (==6.0.0) ; python_version >= \"3.9\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.1.0) ; python_version >= \"3.8\"", "sphinx-rtd-theme (==3.0.2) ; python_version >= \"3.11\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.23.2) ; python_version >= \"3.8\"", "twine (==6.0.1) ; python_version >= \"3.11\""] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "mirrors" + [[package]] name = "markupsafe" version = "3.0.2" @@ -4241,6 +4265,27 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "mirrors" +[[package]] +name = "win32-setctime" +version = "1.2.0" +description = "A small Python utility to set file creation time on Windows" +optional = false +python-versions = ">=3.5" +groups = ["main"] +markers = "sys_platform == \"win32\"" +files = [ + {file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"}, + {file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"}, +] + +[package.extras] +dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "mirrors" + [[package]] name = "yarl" version = "1.18.3" @@ -4346,4 +4391,4 @@ reference = "mirrors" [metadata] lock-version = "2.1" python-versions = "^3.10.16" -content-hash = "9e62f0425b6f332e766fb61ceb2b0d8e49a9ba4dc439e181ba709fa8436ad482" +content-hash = "8645b79840ee30a5262cc87a54aff45a21756f37d6e15fec3215e342ebfe864d" diff --git a/pyproject.toml b/pyproject.toml index 59d89bd4..096e32cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ aiohttp-cors = "0.7.0" ormsgpack = "1.7.0" ruamel-yaml = "0.18.10" setuptools = "^75.8.0" +loguru = "^0.7.3" [[tool.poetry.source]]