🦄 refactor(log): colorful log

Merge pull request #49 from kalicyh/main
This commit is contained in:
kalicyh
2025-02-18 00:09:58 +08:00
committed by GitHub
32 changed files with 423 additions and 190 deletions
+1
View File
@@ -9,6 +9,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"axios": "^1.7.9",
"vue": "^3.5.10", "vue": "^3.5.10",
"vue-router": "^4.0.0" "vue-router": "^4.0.0"
}, },
+160
View File
@@ -337,16 +337,86 @@
resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.13.tgz#87b309a6379c22b926e696893237826f64339b6f" resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.13.tgz#87b309a6379c22b926e696893237826f64339b6f"
integrity sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ== 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: csstype@^3.1.3:
version "3.1.3" version "3.1.3"
resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== 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: entities@^4.5.0:
version "4.5.0" version "4.5.0"
resolved "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" resolved "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== 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: esbuild@^0.21.3:
version "0.21.5" version "0.21.5"
resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" 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" resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== 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: fsevents@~2.3.2, fsevents@~2.3.3:
version "2.3.3" version "2.3.3"
resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== 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: magic-string@^0.30.11:
version "0.30.17" version "0.30.17"
resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453"
@@ -393,6 +531,23 @@ magic-string@^0.30.11:
dependencies: dependencies:
"@jridgewell/sourcemap-codec" "^1.5.0" "@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: nanoid@^3.3.8:
version "3.3.8" version "3.3.8"
resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" 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" picocolors "^1.1.1"
source-map-js "^1.2.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: rollup@^4.20.0:
version "4.34.7" version "4.34.7"
resolved "https://registry.npmmirror.com/rollup/-/rollup-4.34.7.tgz#e00d8550688a616a3481c6446bb688d4c753ba8f" resolved "https://registry.npmmirror.com/rollup/-/rollup-4.34.7.tgz#e00d8550688a616a3481c6446bb688d4c753ba8f"
+5 -3
View File
@@ -6,8 +6,10 @@ from manager.http_server import WebUI
from aiohttp import web from aiohttp import web
from core.utils.util import get_local_ip from core.utils.util import get_local_ip
TAG = __name__
async def main(): async def main():
setup_logging() # 最先初始化日志 logger = setup_logging()
config = load_config() config = load_config()
# 启动 WebSocket 服务器 # 启动 WebSocket 服务器
@@ -28,9 +30,9 @@ async def main():
await site.start() await site.start()
webui_runner = runner webui_runner = runner
local_ip = get_local_ip() 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: 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: try:
# 等待 WebSocket 服务器运行 # 等待 WebSocket 服务器运行
+20 -13
View File
@@ -1,20 +1,27 @@
import logging
import sys
import os import os
import sys
from loguru import logger
def setup_logging(log_dir='tmp', data_dir='data'): def setup_logging(log_dir='tmp', data_dir='data'):
"""配置全局日志""" """配置全局彩色日志(不同区块不同标签)"""
os.makedirs(log_dir, exist_ok=True) os.makedirs(log_dir, exist_ok=True)
os.makedirs(data_dir, exist_ok=True) os.makedirs(data_dir, exist_ok=True)
logging.basicConfig( # 设置日志格式,时间、日志级别、标签、消息
level=logging.INFO, log_format = (
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", "<green>{time:YYYY-MM-DD HH:mm:ss}</green>"
handlers=[ "[<light-blue>{extra[tag]}</light-blue>]"
logging.StreamHandler(sys.stdout), " - <level>{level}</level> - "
logging.FileHandler(os.path.join(log_dir, "server.log"), encoding='utf-8') "<light-green>{message}</light-green>"
],
force=True
) )
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
+15 -13
View File
@@ -1,7 +1,7 @@
import os import os
import time import time
import yaml import yaml
import logging from config.logger import setup_logging
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
from copy import deepcopy from copy import deepcopy
from core.utils.util import get_project_dir 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 manager.api.user_manager import UserManager
from core.utils.lock_manager import FileLockManager from core.utils.lock_manager import FileLockManager
TAG = __name__
class PrivateConfig: class PrivateConfig:
def __init__(self, device_id: str, default_config: Dict[str, Any], auth_code_gen=None): def __init__(self, device_id: str, default_config: Dict[str, Any], auth_code_gen=None):
self.device_id = device_id self.device_id = device_id
self.default_config = default_config self.default_config = default_config
self.config_path = get_project_dir() + 'data/.private_config.yaml' self.config_path = get_project_dir() + 'data/.private_config.yaml'
self.logger = logging.getLogger(__name__) self.logger = setup_logging()
self.private_config = {} self.private_config = {}
self.auth_code_gen = auth_code_gen self.auth_code_gen = auth_code_gen
self.user_manager = UserManager() self.user_manager = UserManager()
@@ -74,7 +76,7 @@ class PrivateConfig:
self.lock_manager.release_lock(self.config_path) self.lock_manager.release_lock(self.config_path)
except Exception as e: 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 = {} self.private_config = {}
async def update_config(self, selected_modules: Dict[str, str], prompt: str, nickname: str) -> bool: 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) self.lock_manager.release_lock(self.config_path)
except Exception as e: 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 return False
async def delete_config(self) -> bool: async def delete_config(self) -> bool:
@@ -163,13 +165,13 @@ class PrivateConfig:
self.lock_manager.release_lock(self.config_path) self.lock_manager.release_lock(self.config_path)
except Exception as e: 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 return False
def create_private_instances(self): def create_private_instances(self):
# 判断存在私有配置,并且self.device_id在私有配置中 # 判断存在私有配置,并且self.device_id在私有配置中
if not self.private_config: 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 return None, None, None, None
"""创建私有处理模块实例""" """创建私有处理模块实例"""
@@ -208,7 +210,7 @@ class PrivateConfig:
timestamp: 指定的时间戳,不传则使用当前时间 timestamp: 指定的时间戳,不传则使用当前时间
""" """
if not self.private_config: if not self.private_config:
self.logger.error("Private config not found") self.logger.bind(tag=TAG).error("Private config not found")
return False return False
try: try:
@@ -235,7 +237,7 @@ class PrivateConfig:
self.lock_manager.release_lock(self.config_path) self.lock_manager.release_lock(self.config_path)
except Exception as e: 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 return False
def get_auth_code(self) -> str: def get_auth_code(self) -> str:
@@ -252,7 +254,7 @@ class PrivateConfig:
try: try:
# 检查用户是否存在 # 检查用户是否存在
if not self.user_manager.get_user(username): 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 return False
# 读取所有配置 # 读取所有配置
@@ -260,12 +262,12 @@ class PrivateConfig:
all_configs = yaml.safe_load(f) or {} all_configs = yaml.safe_load(f) or {}
if self.device_id not in all_configs: 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 return False
# 删除认证码 # 删除认证码
auth_code = all_configs[self.device_id].get('auth_code') 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: if auth_code:
del all_configs[self.device_id]['auth_code'] del all_configs[self.device_id]['auth_code']
@@ -293,7 +295,7 @@ class PrivateConfig:
self.lock_manager.release_lock(self.config_path) self.lock_manager.release_lock(self.config_path)
except Exception as e: 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 return False
async def unbind_user(self) -> bool: async def unbind_user(self) -> bool:
@@ -330,7 +332,7 @@ class PrivateConfig:
self.lock_manager.release_lock(self.config_path) self.lock_manager.release_lock(self.config_path)
except Exception as e: 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 return False
def get_owner(self) -> Optional[str]: def get_owner(self) -> Optional[str]:
+5 -5
View File
@@ -1,6 +1,6 @@
import logging from config.logger import setup_logging
logger = logging.getLogger(__name__) TAG = __name__
class AuthenticationError(Exception): class AuthenticationError(Exception):
@@ -37,15 +37,15 @@ class AuthMiddleware:
# 验证Authorization header # 验证Authorization header
auth_header = headers.get("authorization", "") auth_header = headers.get("authorization", "")
if not auth_header.startswith("Bearer "): 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") raise AuthenticationError("Missing or invalid Authorization header")
token = auth_header.split(" ")[1] token = auth_header.split(" ")[1]
if token not in self.tokens: 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") 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 return True
def get_token_name(self, token): def get_token_name(self, token):
+30 -28
View File
@@ -4,7 +4,7 @@ import uuid
import time import time
import queue import queue
import asyncio import asyncio
import logging from config.logger import setup_logging
import threading import threading
import websockets import websockets
from typing import Dict, Any from typing import Dict, Any
@@ -19,10 +19,12 @@ from config.private_config import PrivateConfig
from core.auth import AuthMiddleware, AuthenticationError from core.auth import AuthMiddleware, AuthenticationError
from core.utils.auth_code_gen import AuthCodeGenerator # 添加导入 from core.utils.auth_code_gen import AuthCodeGenerator # 添加导入
TAG = __name__
class ConnectionHandler: class ConnectionHandler:
def __init__(self, config: Dict[str, Any], _vad, _asr, _llm, _tts): def __init__(self, config: Dict[str, Any], _vad, _asr, _llm, _tts):
self.config = config self.config = config
self.logger = logging.getLogger(__name__) self.logger = setup_logging()
self.auth = AuthMiddleware(config) self.auth = AuthMiddleware(config)
self.websocket = None self.websocket = None
@@ -85,7 +87,7 @@ class ConnectionHandler:
try: try:
# 获取并验证headers # 获取并验证headers
self.headers = dict(ws.request.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) await self.auth.authenticate(self.headers)
@@ -94,7 +96,7 @@ class ConnectionHandler:
# Load private configuration if device_id is provided # Load private configuration if device_id is provided
bUsePrivateConfig = self.config.get("use_private_config", False) 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: if bUsePrivateConfig and device_id:
try: try:
self.private_config = PrivateConfig(device_id, self.config, self.auth_code_gen) self.private_config = PrivateConfig(device_id, self.config, self.auth_code_gen)
@@ -112,12 +114,12 @@ class ConnectionHandler:
self.asr = asr self.asr = asr
self.llm = llm self.llm = llm
self.tts = tts 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: 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 self.private_config = None
except Exception as e: 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 self.private_config = None
raise raise
@@ -138,15 +140,15 @@ class ConnectionHandler:
async for message in self.websocket: async for message in self.websocket:
await self._route_message(message) await self._route_message(message)
except websockets.exceptions.ConnectionClosed: except websockets.exceptions.ConnectionClosed:
self.logger.info("客户端断开连接") self.logger.bind(tag=TAG).info("客户端断开连接")
await self.close() await self.close()
except AuthenticationError as e: 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() await ws.close()
return return
except Exception as e: 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() await ws.close()
return return
@@ -208,7 +210,7 @@ class ConnectionHandler:
start_time = time.time() # 记录开始时间 start_time = time.time() # 记录开始时间
llm_responses = self.llm.response(self.session_id, self.dialogue.get_llm_dialogue()) llm_responses = self.llm.response(self.session_id, self.dialogue.get_llm_dialogue())
except Exception as e: except Exception as e:
self.logger.error(f"LLM 处理出错 {query}: {e}") self.logger.bind(tag=TAG).error(f"LLM 处理出错 {query}: {e}")
return None return None
# 提交 TTS 任务到线程池 # 提交 TTS 任务到线程池
self.llm_finish_task = False self.llm_finish_task = False
@@ -220,7 +222,7 @@ class ConnectionHandler:
break break
end_time = time.time() # 记录结束时间 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): if is_segment(response_message):
segment_text = "".join(response_message[start:]) segment_text = "".join(response_message[start:])
segment_text = get_string_no_punctuation_or_emoji(segment_text) segment_text = get_string_no_punctuation_or_emoji(segment_text)
@@ -241,7 +243,7 @@ class ConnectionHandler:
self.llm_finish_task = True self.llm_finish_task = True
# 更新对话 # 更新对话
self.dialogue.put(Message(role="assistant", content="".join(response_message))) 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 return True
def _priority_thread(self): def _priority_thread(self):
@@ -253,25 +255,25 @@ class ConnectionHandler:
continue continue
text = None text = None
try: try:
self.logger.debug("正在处理TTS任务...") self.logger.bind(tag=TAG).debug("正在处理TTS任务...")
tts_file, text = future.result(timeout=10) tts_file, text = future.result(timeout=10)
if text is None or len(text) <= 0: if text is None or len(text) <= 0:
continue continue
if tts_file is None: if tts_file is None:
self.logger.error(f"TTS文件生成失败: {text}") self.logger.bind(tag=TAG).error(f"TTS文件生成失败: {text}")
continue continue
self.logger.debug(f"TTS文件生成完毕,文件路径: {tts_file}") self.logger.bind(tag=TAG).debug(f"TTS文件生成完毕,文件路径: {tts_file}")
if os.path.exists(tts_file): if os.path.exists(tts_file):
opus_datas, duration = self.tts.wav_to_opus_data(tts_file) opus_datas, duration = self.tts.wav_to_opus_data(tts_file)
else: else:
self.logger.error(f"TTS文件不存在: {tts_file}") self.logger.bind(tag=TAG).error(f"TTS文件不存在: {tts_file}")
opus_datas = [] opus_datas = []
duration = 0 duration = 0
except TimeoutError: except TimeoutError:
self.logger.error("TTS 任务超时") self.logger.bind(tag=TAG).error("TTS 任务超时")
continue continue
except Exception as e: except Exception as e:
self.logger.error(f"TTS 任务出错: {e}") self.logger.bind(tag=TAG).error(f"TTS 任务出错: {e}")
continue continue
if not self.client_abort: if not self.client_abort:
# 如果没有中途打断就发送语音 # 如果没有中途打断就发送语音
@@ -281,27 +283,27 @@ class ConnectionHandler:
if self.tts.delete_audio_file and os.path.exists(tts_file): if self.tts.delete_audio_file and os.path.exists(tts_file):
os.remove(tts_file) os.remove(tts_file)
except Exception as e: except Exception as e:
self.logger.error(f"TTS任务处理错误: {e}") self.logger.bind(tag=TAG).error(f"TTS任务处理错误: {e}")
self.clearSpeakStatus() self.clearSpeakStatus()
asyncio.run_coroutine_threadsafe( asyncio.run_coroutine_threadsafe(
self.websocket.send(json.dumps({"type": "tts", "state": "stop", "session_id": self.session_id})), self.websocket.send(json.dumps({"type": "tts", "state": "stop", "session_id": self.session_id})),
self.loop 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): def speak_and_play(self, text):
if text is None or len(text) <= 0: 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 return None, text
tts_file = self.tts.to_tts(text) tts_file = self.tts.to_tts(text)
if tts_file is None: if tts_file is None:
self.logger.error(f"tts转换失败,{text}") self.logger.bind(tag=TAG).error(f"tts转换失败,{text}")
return None, 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 return tts_file, text
def clearSpeakStatus(self): def clearSpeakStatus(self):
self.logger.debug(f"清除服务端讲话状态") self.logger.bind(tag=TAG).debug(f"清除服务端讲话状态")
self.asr_server_receive = True self.asr_server_receive = True
self.tts_last_text = None self.tts_last_text = None
self.tts_first_text = None self.tts_first_text = None
@@ -310,7 +312,7 @@ class ConnectionHandler:
def recode_first_last_text(self, text): def recode_first_last_text(self, text):
if not self.tts_first_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_first_text = text
self.tts_last_text = text self.tts_last_text = text
@@ -320,14 +322,14 @@ class ConnectionHandler:
self.executor.shutdown(wait=False) self.executor.shutdown(wait=False)
if self.websocket: if self.websocket:
await self.websocket.close() await self.websocket.close()
self.logger.info("连接资源已释放") self.logger.bind(tag=TAG).info("连接资源已释放")
def reset_vad_states(self): def reset_vad_states(self):
self.client_audio_buffer = bytes() self.client_audio_buffer = bytes()
self.client_have_voice = False self.client_have_voice = False
self.client_have_voice_last_time = 0 self.client_have_voice_last_time = 0
self.client_voice_stop = False 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): def stop_all_tasks(self):
while self.scheduled_tasks: while self.scheduled_tasks:
+5 -4
View File
@@ -1,11 +1,12 @@
import json import json
import logging from config.logger import setup_logging
logger = logging.getLogger(__name__) TAG = __name__
logger = setup_logging()
async def handleAbortMessage(conn): async def handleAbortMessage(conn):
logger.info("Abort message received") logger.bind(tag=TAG).info("Abort message received")
# 设置成打断状态,会自动打断llm、tts任务 # 设置成打断状态,会自动打断llm、tts任务
conn.client_abort = True 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})) await conn.websocket.send(json.dumps({"type": "tts", "state": "stop", "session_id": conn.session_id}))
conn.clearSpeakStatus() conn.clearSpeakStatus()
logger.info("Abort message received-end") logger.bind(tag=TAG).info("Abort message received-end")
+8 -6
View File
@@ -1,15 +1,17 @@
import logging from config.logger import setup_logging
import json import json
import asyncio import asyncio
import time import time
from core.utils.util import remove_punctuation_and_length, get_string_no_punctuation_or_emoji 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): async def handleAudioMessage(conn, audio):
if not conn.asr_server_receive: if not conn.asr_server_receive:
logger.debug(f"前期数据处理中,暂停接收") logger = setup_logging()
logger.bind(tag=TAG).debug(f"前期数据处理中,暂停接收")
return return
if conn.client_listen_mode == "auto": if conn.client_listen_mode == "auto":
have_voice = conn.vad.is_vad(conn, audio) have_voice = conn.vad.is_vad(conn, audio)
@@ -28,7 +30,7 @@ async def handleAudioMessage(conn, audio):
conn.client_abort = False conn.client_abort = False
conn.asr_server_receive = False conn.asr_server_receive = False
text, file_path = conn.asr.speech_to_text(conn.asr_audio, conn.session_id) 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) text_len, text_without_punctuation = remove_punctuation_and_length(text)
if text_len <= conn.max_cmd_length and await handleCMDMessage(conn, text_without_punctuation): if text_len <= conn.max_cmd_length and await handleCMDMessage(conn, text_without_punctuation):
return return
@@ -44,7 +46,7 @@ async def handleCMDMessage(conn, text):
cmd_exit = conn.cmd_exit cmd_exit = conn.cmd_exit
for cmd in cmd_exit: for cmd in cmd_exit:
if text == cmd: if text == cmd:
logger.info("识别到明确的退出命令".format(text)) logger.bind(tag=TAG).info("识别到明确的退出命令".format(text))
await finishToChat(conn) await finishToChat(conn)
return True return True
return False return False
@@ -80,7 +82,7 @@ async def sendAudioMessage(conn, audios, duration, text):
# 发送 tts.start # 发送 tts.start
if text == conn.tts_first_text: if text == conn.tts_first_text:
logger.info(f"发送第一段语音: {text}") logger.bind(tag=TAG).info(f"发送第一段语音: {text}")
conn.tts_start_speak_time = time.time() conn.tts_start_speak_time = time.time()
# 发送 sentence_start(每个音频文件之前发送一次) # 发送 sentence_start(每个音频文件之前发送一次)
+2 -2
View File
@@ -1,7 +1,7 @@
import json import json
import logging from config.logger import setup_logging
logger = logging.getLogger(__name__) logger = setup_logging()
async def handleHelloMessage(conn): async def handleHelloMessage(conn):
+5 -4
View File
@@ -1,15 +1,16 @@
import logging from config.logger import setup_logging
import json import json
from core.handle.abortHandle import handleAbortMessage from core.handle.abortHandle import handleAbortMessage
from core.handle.helloHandle import handleHelloMessage from core.handle.helloHandle import handleHelloMessage
from core.handle.audioHandle import startToChat from core.handle.audioHandle import startToChat
logger = logging.getLogger(__name__) TAG = __name__
logger = setup_logging()
async def handleTextMessage(conn, message): async def handleTextMessage(conn, message):
"""处理文本消息""" """处理文本消息"""
logger.info(f"收到文本消息:{message}") logger.bind(tag=TAG).info(f"收到文本消息:{message}")
try: try:
msg_json = json.loads(message) msg_json = json.loads(message)
if isinstance(msg_json, int): if isinstance(msg_json, int):
@@ -22,7 +23,7 @@ async def handleTextMessage(conn, message):
elif msg_json["type"] == "listen": elif msg_json["type"] == "listen":
if "mode" in msg_json: if "mode" in msg_json:
conn.client_listen_mode = msg_json["mode"] 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": if msg_json["state"] == "start":
conn.client_have_voice = True conn.client_have_voice = True
conn.client_voice_stop = False conn.client_voice_stop = False
+9 -8
View File
@@ -1,10 +1,11 @@
import logging from config.logger import setup_logging
import requests import requests
import json import json
import re import re
from core.providers.llm.base import LLMProviderBase from core.providers.llm.base import LLMProviderBase
logger = logging.getLogger(__name__) TAG = __name__
logger = setup_logging()
# 定义用于匹配中文标点符号的正则表达式(包括句号、感叹号、问号、分号) # 定义用于匹配中文标点符号的正则表达式(包括句号、感叹号、问号、分号)
punctuation_pattern = re.compile(r'([。!?;])') punctuation_pattern = re.compile(r'([。!?;])')
@@ -27,7 +28,7 @@ class LLMProvider(LLMProviderBase):
"query": last_msg["content"], "query": last_msg["content"],
"stream": True "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 = { headers = {
'Authorization': f'Bearer {self.personal_access_token}', 'Authorization': f'Bearer {self.personal_access_token}',
@@ -43,7 +44,7 @@ class LLMProvider(LLMProviderBase):
json=data, json=data,
stream=True stream=True
) )
logger.info(f"请求状态: {response.status_code}") logger.bind(tag=TAG).info(f"请求状态: {response.status_code}")
if response.status_code == 200: if response.status_code == 200:
# 对每一行流数据进行处理,不做跨块累积 # 对每一行流数据进行处理,不做跨块累积
@@ -54,7 +55,7 @@ class LLMProvider(LLMProviderBase):
# 使用 utf-8 解码,错误部分用替换符 # 使用 utf-8 解码,错误部分用替换符
line = line_bytes.decode('utf-8', errors='replace') line = line_bytes.decode('utf-8', errors='replace')
except Exception as e: except Exception as e:
logger.error(f"解码失败: {e}") logger.bind(tag=TAG).error(f"解码失败: {e}")
continue continue
if line.startswith("data:"): if line.startswith("data:"):
data_str = line[len("data:"):].strip() data_str = line[len("data:"):].strip()
@@ -63,7 +64,7 @@ class LLMProvider(LLMProviderBase):
try: try:
data_chunk = json.loads(data_str) data_chunk = json.loads(data_str)
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
logger.error(f"JSON解析失败: {e} 数据: {line}") logger.bind(tag=TAG).error(f"JSON解析失败: {e} 数据: {line}")
continue continue
msg = data_chunk.get("message", {}) msg = data_chunk.get("message", {})
if msg.get("role") == "assistant" and msg.get("type") == "answer": if msg.get("role") == "assistant" and msg.get("type") == "answer":
@@ -88,8 +89,8 @@ class LLMProvider(LLMProviderBase):
if content.strip(): if content.strip():
yield content.strip() yield content.strip()
else: else:
logger.error(f"请求失败,状态码: {response.status_code}") logger.bind(tag=TAG).error(f"请求失败,状态码: {response.status_code}")
yield f"【Coze服务响应异常:请求失败,状态码 {response.status_code}" yield f"【Coze服务响应异常:请求失败,状态码 {response.status_code}"
except Exception as e: 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服务响应异常】" yield "【Coze服务响应异常】"
+4 -4
View File
@@ -1,10 +1,10 @@
import json import json
import logging from config.logger import setup_logging
import requests import requests
from core.providers.llm.base import LLMProviderBase from core.providers.llm.base import LLMProviderBase
logger = logging.getLogger(__name__) TAG = __name__
logger = setup_logging()
class LLMProvider(LLMProviderBase): class LLMProvider(LLMProviderBase):
def __init__(self, config): def __init__(self, config):
@@ -35,5 +35,5 @@ class LLMProvider(LLMProviderBase):
yield event['answer'] yield event['answer']
except Exception as e: except Exception as e:
logger.error(f"Error in response generation: {e}") logger.bind(tag=TAG).error(f"Error in response generation: {e}")
yield "【服务响应异常】" yield "【服务响应异常】"
+6 -5
View File
@@ -1,8 +1,9 @@
import logging from config.logger import setup_logging
import google.generativeai as genai import google.generativeai as genai
from core.providers.llm.base import LLMProviderBase from core.providers.llm.base import LLMProviderBase
logger = logging.getLogger(__name__) TAG = __name__
logger = setup_logging()
class LLMProvider(LLMProviderBase): class LLMProvider(LLMProviderBase):
def __init__(self, config): def __init__(self, config):
@@ -11,7 +12,7 @@ class LLMProvider(LLMProviderBase):
self.api_key = config.get("api_key") self.api_key = config.get("api_key")
if not self.api_key or "" in self.api_key: if not self.api_key or "" in self.api_key:
logger.error("你还没配置Gemini LLM的密钥,请在配置文件中配置密钥,否则无法正常工作") logger.bind(tag=TAG).error("你还没配置Gemini LLM的密钥,请在配置文件中配置密钥,否则无法正常工作")
return return
try: try:
@@ -28,7 +29,7 @@ class LLMProvider(LLMProviderBase):
} }
self.chat = None self.chat = None
except Exception as e: except Exception as e:
logger.error(f"Gemini初始化失败: {e}") logger.bind(tag=TAG).error(f"Gemini初始化失败: {e}")
self.model = None self.model = None
def response(self, session_id, dialogue): def response(self, session_id, dialogue):
@@ -69,7 +70,7 @@ class LLMProvider(LLMProviderBase):
except Exception as e: except Exception as e:
error_msg = str(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: if "Rate limit" in error_msg:
+4 -3
View File
@@ -1,8 +1,9 @@
import logging from config.logger import setup_logging
import requests, json import requests, json
from core.providers.llm.base import LLMProviderBase from core.providers.llm.base import LLMProviderBase
logger = logging.getLogger(__name__) TAG = __name__
logger = setup_logging()
class LLMProvider(LLMProviderBase): class LLMProvider(LLMProviderBase):
@@ -41,5 +42,5 @@ class LLMProvider(LLMProviderBase):
yield json_response["response"] yield json_response["response"]
except Exception as e: 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服务响应异常】" yield "【Ollama服务响应异常】"
+5 -4
View File
@@ -1,8 +1,9 @@
import logging from config.logger import setup_logging
import openai import openai
from core.providers.llm.base import LLMProviderBase from core.providers.llm.base import LLMProviderBase
logger = logging.getLogger(__name__) TAG = __name__
logger = setup_logging()
class LLMProvider(LLMProviderBase): class LLMProvider(LLMProviderBase):
@@ -14,7 +15,7 @@ class LLMProvider(LLMProviderBase):
else: else:
self.base_url = config.get("url") self.base_url = config.get("url")
if "" in self.api_key: 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) self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url)
def response(self, session_id, dialogue): def response(self, session_id, dialogue):
@@ -32,4 +33,4 @@ class LLMProvider(LLMProviderBase):
if content: # 仅在content非空时生成 if content: # 仅在content非空时生成
yield content yield content
except Exception as e: except Exception as e:
logger.error(f"Error in response generation: {e}") logger.bind(tag=TAG).error(f"Error in response generation: {e}")
+6 -5
View File
@@ -1,12 +1,13 @@
import asyncio import asyncio
import logging from config.logger import setup_logging
import os import os
import numpy as np import numpy as np
import opuslib import opuslib
from pydub import AudioSegment from pydub import AudioSegment
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
logger = logging.getLogger(__name__) TAG = __name__
logger = setup_logging()
class TTSProviderBase(ABC): class TTSProviderBase(ABC):
@@ -26,14 +27,14 @@ class TTSProviderBase(ABC):
asyncio.run(self.text_to_speak(text, tmp_file)) asyncio.run(self.text_to_speak(text, tmp_file))
if not os.path.exists(tmp_file): if not os.path.exists(tmp_file):
max_repeat_time = max_repeat_time - 1 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: 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 return tmp_file
except Exception as e: 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 return None
@abstractmethod @abstractmethod
+5 -3
View File
@@ -3,10 +3,12 @@ import uuid
import json import json
import base64 import base64
import requests import requests
import logging from config.logger import setup_logging
from datetime import datetime from datetime import datetime
from core.providers.tts.base import TTSProviderBase from core.providers.tts.base import TTSProviderBase
logger = logging.getLogger(__name__)
TAG = __name__
logger = setup_logging()
class TTSProvider(TTSProviderBase): class TTSProvider(TTSProviderBase):
def __init__(self, config, delete_audio_file): def __init__(self, config, delete_audio_file):
@@ -62,4 +64,4 @@ class TTSProvider(TTSProviderBase):
with open(output_file, "wb") as file: with open(output_file, "wb") as file:
file.write(resp.content) file.write(resp.content)
else: 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}")
+9 -9
View File
@@ -2,7 +2,7 @@ import time
import wave import wave
import os import os
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
import logging from config.logger import setup_logging
from typing import Optional, Tuple, List from typing import Optional, Tuple, List
import uuid import uuid
@@ -10,8 +10,8 @@ import opuslib
from funasr import AutoModel from funasr import AutoModel
from funasr.utils.postprocess_utils import rich_transcription_postprocess from funasr.utils.postprocess_utils import rich_transcription_postprocess
logger = logging.getLogger(__name__) TAG = __name__
logger = setup_logging()
class ASR(ABC): class ASR(ABC):
@abstractmethod @abstractmethod
@@ -55,7 +55,7 @@ class FunASR(ASR):
pcm_frame = decoder.decode(opus_packet, 960) # 960 samples = 60ms pcm_frame = decoder.decode(opus_packet, 960) # 960 samples = 60ms
pcm_data.append(pcm_frame) pcm_data.append(pcm_frame)
except opuslib.OpusError as e: 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: with wave.open(file_path, "wb") as wf:
wf.setnchannels(1) wf.setnchannels(1)
@@ -72,7 +72,7 @@ class FunASR(ASR):
# 保存音频文件 # 保存音频文件
start_time = time.time() start_time = time.time()
file_path = self.save_audio_to_file(opus_data, session_id) 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() start_time = time.time()
@@ -84,12 +84,12 @@ class FunASR(ASR):
batch_size_s=60, batch_size_s=60,
) )
text = rich_transcription_postprocess(result[0]["text"]) 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 return text, file_path
except Exception as e: except Exception as e:
logger.error(f"语音识别失败: {e}", exc_info=True) logger.bind(tag=TAG).error(f"语音识别失败: {e}", exc_info=True)
return None, None return None, None
finally: finally:
@@ -97,9 +97,9 @@ class FunASR(ASR):
if self.delete_audio_file and file_path and os.path.exists(file_path): if self.delete_audio_file and file_path and os.path.exists(file_path):
try: try:
os.remove(file_path) os.remove(file_path)
logger.debug(f"已删除临时音频文件: {file_path}") logger.bind(tag=TAG).debug(f"已删除临时音频文件: {file_path}")
except Exception as e: 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: def create_instance(class_name: str, *args, **kwargs) -> ASR:
+2 -2
View File
@@ -1,13 +1,13 @@
import os import os
import sys import sys
import logging from config.logger import setup_logging
import importlib import importlib
from datetime import datetime from datetime import datetime
from core.utils.util import is_segment from core.utils.util import is_segment
from core.utils.util import get_string_no_punctuation_or_emoji from core.utils.util import get_string_no_punctuation_or_emoji
from core.utils.util import read_config, get_project_dir from core.utils.util import read_config, get_project_dir
logger = logging.getLogger(__name__) logger = setup_logging()
def create_instance(class_name, *args, **kwargs): def create_instance(class_name, *args, **kwargs):
+6 -5
View File
@@ -1,8 +1,9 @@
import asyncio import asyncio
from typing import Dict from typing import Dict
import logging from config.logger import setup_logging
logger = logging.getLogger(__name__) TAG = __name__
logger = setup_logging()
class FileLockManager: class FileLockManager:
_instance = None _instance = None
@@ -25,7 +26,7 @@ class FileLockManager:
"""获取锁""" """获取锁"""
lock = cls.get_lock(file_path) lock = cls.get_lock(file_path)
await lock.acquire() await lock.acquire()
logger.debug(f"Acquired lock for {file_path}") logger.bind(tag=TAG).debug(f"Acquired lock for {file_path}")
@classmethod @classmethod
def release_lock(cls, file_path: str): def release_lock(cls, file_path: str):
@@ -33,6 +34,6 @@ class FileLockManager:
if file_path in cls._locks: if file_path in cls._locks:
try: try:
cls._locks[file_path].release() 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: 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}")
+2 -2
View File
@@ -1,11 +1,11 @@
import os import os
import sys import sys
import logging from config.logger import setup_logging
import importlib import importlib
from datetime import datetime from datetime import datetime
from core.utils.util import read_config, get_project_dir from core.utils.util import read_config, get_project_dir
logger = logging.getLogger(__name__) logger = setup_logging()
def create_instance(class_name, *args, **kwargs): def create_instance(class_name, *args, **kwargs):
+6 -6
View File
@@ -1,12 +1,12 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
import logging from config.logger import setup_logging
import opuslib import opuslib
import time import time
import numpy as np import numpy as np
import torch import torch
logger = logging.getLogger(__name__) TAG = __name__
logger = setup_logging()
class VAD(ABC): class VAD(ABC):
@abstractmethod @abstractmethod
@@ -17,7 +17,7 @@ class VAD(ABC):
class SileroVAD(VAD): class SileroVAD(VAD):
def __init__(self, config): 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"], self.model, self.utils = torch.hub.load(repo_or_dir=config["model_dir"],
source='local', source='local',
model='silero_vad', model='silero_vad',
@@ -60,9 +60,9 @@ class SileroVAD(VAD):
return client_have_voice return client_have_voice
except opuslib.OpusError as e: except opuslib.OpusError as e:
logger.info(f"解码错误: {e}") logger.bind(tag=TAG).info(f"解码错误: {e}")
except Exception as 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: def create_instance(class_name, *args, **kwargs) -> VAD:
+5 -5
View File
@@ -1,15 +1,16 @@
import asyncio import asyncio
import websockets import websockets
import logging from config.logger import setup_logging
from core.connection import ConnectionHandler from core.connection import ConnectionHandler
from core.utils.util import get_local_ip from core.utils.util import get_local_ip
from core.utils import asr, vad, llm, tts from core.utils import asr, vad, llm, tts
TAG = __name__
class WebSocketServer: class WebSocketServer:
def __init__(self, config: dict): def __init__(self, config: dict):
self.config = config self.config = config
self.logger = logging.getLogger(__name__) self.logger = setup_logging()
self._vad, self._asr, self._llm, self._tts = self._create_processing_instances() self._vad, self._asr, self._llm, self._tts = self._create_processing_instances()
def _create_processing_instances(self): def _create_processing_instances(self):
@@ -46,9 +47,8 @@ class WebSocketServer:
host = server_config["ip"] host = server_config["ip"]
port = server_config["port"] port = server_config["port"]
self.logger.info("=======下面的地址是websocket协议地址,请勿用浏览器访问=======") self.logger.bind(tag=TAG).info("Server is running at ws://{}:{}", get_local_ip(), port)
self.logger.info("Server is running at ws://%s:%s", get_local_ip(), port) self.logger.bind(tag=TAG).info("=======上面的地址是websocket协议地址,请勿用浏览器访问=======")
self.logger.info("=======上面的地址是websocket协议地址,请勿用浏览器访问=======")
async with websockets.serve( async with websockets.serve(
self._handle_connection, self._handle_connection,
host, host,
+12 -11
View File
@@ -1,13 +1,14 @@
import os import os
import yaml import yaml
import logging from config.logger import setup_logging
from aiohttp import web from aiohttp import web
from core.utils.util import get_project_dir from core.utils.util import get_project_dir
from config.private_config import PrivateConfig from config.private_config import PrivateConfig
from manager.api.user_manager import UserManager # 添加导入 from manager.api.user_manager import UserManager # 添加导入
from core.utils.auth_code_gen import AuthCodeGenerator # 添加导入 from core.utils.auth_code_gen import AuthCodeGenerator # 添加导入
logger = logging.getLogger(__name__) TAG = __name__
logger = setup_logging()
class ConfigHandler: class ConfigHandler:
def __init__(self, session_manager): def __init__(self, session_manager):
@@ -42,7 +43,7 @@ class ConfigHandler:
}) })
except Exception as e: 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({ return web.json_response({
'success': False, 'success': False,
'message': '获取配置选项失败' 'message': '获取配置选项失败'
@@ -52,11 +53,11 @@ class ConfigHandler:
"""只返回用户绑定的设备配置""" """只返回用户绑定的设备配置"""
try: try:
username = request['username'] 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) 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 = {} all_configs = {}
@@ -71,7 +72,7 @@ class ConfigHandler:
if device_id in user_devices 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({ return web.json_response({
'success': True, 'success': True,
'data': user_configs, 'data': user_configs,
@@ -79,7 +80,7 @@ class ConfigHandler:
}) })
except Exception as e: 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({ return web.json_response({
'success': False, 'success': False,
'message': f'获取设备列表失败: {str(e)}' 'message': f'获取设备列表失败: {str(e)}'
@@ -101,7 +102,7 @@ class ConfigHandler:
'message': '无权操作此设备' 'message': '无权操作此设备'
}, status=403) }, 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: if not device_id or not config:
return web.json_response({ return web.json_response({
'success': False, 'success': False,
@@ -128,7 +129,7 @@ class ConfigHandler:
}) })
except Exception as e: 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({ return web.json_response({
'success': False, 'success': False,
'message': f'保存配置失败: {str(e)}' 'message': f'保存配置失败: {str(e)}'
@@ -163,7 +164,7 @@ class ConfigHandler:
}) })
except Exception as e: 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({ return web.json_response({
'success': False, 'success': False,
'message': f'删除配置失败: {str(e)}' 'message': f'删除配置失败: {str(e)}'
@@ -219,7 +220,7 @@ class ConfigHandler:
}, status=500) }, status=500)
except Exception as e: 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({ return web.json_response({
'success': False, 'success': False,
'message': f'绑定设备失败: {str(e)}' 'message': f'绑定设备失败: {str(e)}'
+6 -5
View File
@@ -1,8 +1,9 @@
import logging from config.logger import setup_logging
from aiohttp import web from aiohttp import web
import datetime import datetime
logger = logging.getLogger(__name__) TAG = __name__
logger = setup_logging()
class LoginHandler: class LoginHandler:
def __init__(self, user_manager, session_manager): def __init__(self, user_manager, session_manager):
@@ -17,7 +18,7 @@ class LoginHandler:
password = data.get('password') password = data.get('password')
if not username or not 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({ return web.json_response({
'success': False, 'success': False,
'message': '用户名和密码不能为空' 'message': '用户名和密码不能为空'
@@ -25,7 +26,7 @@ class LoginHandler:
stored_user = await self.user_manager.get_user(username) stored_user = await self.user_manager.get_user(username)
if not stored_user or stored_user['password'] != self.user_manager.hash_password(password): 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({ return web.json_response({
'success': False, 'success': False,
'message': '用户名或密码错误' 'message': '用户名或密码错误'
@@ -45,7 +46,7 @@ class LoginHandler:
}) })
except Exception as e: 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({ return web.json_response({
'success': False, 'success': False,
'message': '登录失败,请稍后重试' 'message': '登录失败,请稍后重试'
+4 -4
View File
@@ -1,12 +1,12 @@
import logging from config.logger import setup_logging
from aiohttp import web from aiohttp import web
from config.settings import update_config from config.settings import update_config
from ruamel.yaml.scalarstring import PreservedScalarString from ruamel.yaml.scalarstring import PreservedScalarString
from manager.api.auth import verify_token from manager.api.auth import verify_token
from manager.api.response import response_unauthorized, response_success, response_error from manager.api.response import response_unauthorized, response_success, response_error
logger = logging.getLogger(__name__) TAG = __name__
logger = setup_logging()
class PromptApi: class PromptApi:
def __init__(self, config): def __init__(self, config):
@@ -38,5 +38,5 @@ class PromptApi:
return response_success() return response_success()
except Exception as e: 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)) return response_error(str(e))
+7 -7
View File
@@ -1,9 +1,9 @@
import logging from config.logger import setup_logging
from aiohttp import web from aiohttp import web
import datetime import datetime
logger = logging.getLogger(__name__) TAG = __name__
logger = setup_logging()
class RegisterHandler: class RegisterHandler:
def __init__(self, user_manager): def __init__(self, user_manager):
@@ -17,7 +17,7 @@ class RegisterHandler:
password = data.get('password') password = data.get('password')
if not username or not 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({ return web.json_response({
'success': False, 'success': False,
'message': '用户名和密码不能为空' 'message': '用户名和密码不能为空'
@@ -25,7 +25,7 @@ class RegisterHandler:
# 检查用户是否已存在 # 检查用户是否已存在
if await self.user_manager.get_user(username): 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({ return web.json_response({
'success': False, 'success': False,
'message': '用户名已存在' 'message': '用户名已存在'
@@ -41,14 +41,14 @@ class RegisterHandler:
} }
await self.user_manager.add_user(username, user_data) 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({ return web.json_response({
'success': True, 'success': True,
'message': '注册成功' 'message': '注册成功'
}) })
except Exception as e: 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({ return web.json_response({
'success': False, 'success': False,
'message': '注册失败,请稍后重试' 'message': '注册失败,请稍后重试'
+15 -14
View File
@@ -1,11 +1,12 @@
import os import os
import yaml import yaml
import hashlib import hashlib
import logging from config.logger import setup_logging
from core.utils.util import get_project_dir from core.utils.util import get_project_dir
from core.utils.lock_manager import FileLockManager from core.utils.lock_manager import FileLockManager
logger = logging.getLogger(__name__) TAG = __name__
logger = setup_logging()
class UserManager: class UserManager:
def __init__(self): def __init__(self):
@@ -23,9 +24,9 @@ class UserManager:
with open(self.secrets_path, 'w', encoding='utf-8') as f: with open(self.secrets_path, 'w', encoding='utf-8') as f:
yaml.dump(default_config, f) yaml.dump(default_config, f)
os.chmod(self.secrets_path, 0o600) 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: 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 raise
async def _load_user_data_internal(self): async def _load_user_data_internal(self):
@@ -34,9 +35,9 @@ class UserManager:
with open(self.secrets_path, 'r', encoding='utf-8') as f: with open(self.secrets_path, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f) or {'users': {}} data = yaml.safe_load(f) or {'users': {}}
users = data['users'] users = data['users']
logger.debug("Successfully loaded user data") logger.bind(tag=TAG).debug("Successfully loaded user data")
except Exception as e: 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 = {} users = {}
return users return users
@@ -50,7 +51,7 @@ class UserManager:
self.lock_manager.release_lock(self.secrets_path) self.lock_manager.release_lock(self.secrets_path)
except Exception as e: 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 = {} users = {}
return users return users
@@ -59,9 +60,9 @@ class UserManager:
try: try:
with open(self.secrets_path, 'w', encoding='utf-8') as f: with open(self.secrets_path, 'w', encoding='utf-8') as f:
yaml.dump({'users': users}, 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: 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 raise
async def save_user_data(self, users): async def save_user_data(self, users):
@@ -73,7 +74,7 @@ class UserManager:
finally: finally:
self.lock_manager.release_lock(self.secrets_path) self.lock_manager.release_lock(self.secrets_path)
except Exception as e: 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 raise
def hash_password(self, password): def hash_password(self, password):
@@ -103,7 +104,7 @@ class UserManager:
finally: finally:
self.lock_manager.release_lock(self.secrets_path) self.lock_manager.release_lock(self.secrets_path)
except Exception as e: except Exception as e:
logger.error(f"Error adding user: {e}") logger.bind(tag=TAG).error(f"Error adding user: {e}")
raise raise
async def update_user(self, username, data): async def update_user(self, username, data):
@@ -120,7 +121,7 @@ class UserManager:
finally: finally:
self.lock_manager.release_lock(self.secrets_path) self.lock_manager.release_lock(self.secrets_path)
except Exception as e: except Exception as e:
logger.error(f"Error updating user: {e}") logger.bind(tag=TAG).error(f"Error updating user: {e}")
return False return False
async def get_user_devices(self, username: str) -> list: async def get_user_devices(self, username: str) -> list:
@@ -151,7 +152,7 @@ class UserManager:
finally: finally:
self.lock_manager.release_lock(self.secrets_path) self.lock_manager.release_lock(self.secrets_path)
except Exception as e: except Exception as e:
logger.error(f"Error adding device: {e}") logger.bind(tag=TAG).error(f"Error adding device: {e}")
return False return False
async def remove_device(self, username: str, device_id: str) -> bool: async def remove_device(self, username: str, device_id: str) -> bool:
@@ -170,5 +171,5 @@ class UserManager:
finally: finally:
self.lock_manager.release_lock(self.secrets_path) self.lock_manager.release_lock(self.secrets_path)
except Exception as e: except Exception as e:
logger.error(f"Error removing device: {e}") logger.bind(tag=TAG).error(f"Error removing device: {e}")
return False return False
+6 -8
View File
@@ -5,10 +5,9 @@ current_dir = os.path.dirname(os.path.abspath(__file__))
root_dir = os.path.dirname(current_dir) root_dir = os.path.dirname(current_dir)
sys.path.append(root_dir) sys.path.append(root_dir)
import logging from config.logger import setup_logging
from aiohttp import web from aiohttp import web
from aiohttp_cors import setup as cors_setup, ResourceOptions 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.login import LoginHandler
from manager.api.register import RegisterHandler from manager.api.register import RegisterHandler
from manager.api.user_manager import UserManager from manager.api.user_manager import UserManager
@@ -16,7 +15,8 @@ from manager.api.config import ConfigHandler
from manager.session import SessionManager from manager.session import SessionManager
from functools import wraps from functools import wraps
logger = logging.getLogger(__name__) TAG = __name__
logger = setup_logging()
def auth_required(handler): def auth_required(handler):
"""鉴权装饰器""" """鉴权装饰器"""
@@ -94,23 +94,21 @@ class WebUI:
# 从请求头获取session_id # 从请求头获取session_id
session_id = request.headers.get('Authorization') session_id = request.headers.get('Authorization')
if not session_id: 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) return web.json_response({'error': 'Unauthorized'}, status=401)
username = self.session_manager.validate_session(session_id) username = self.session_manager.validate_session(session_id)
if not username: 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) return web.json_response({'error': 'Unauthorized'}, status=401)
request['username'] = username 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 await handler(request)
return wrapper return wrapper
def run(self, host='0.0.0.0', port=8002): 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) web.run_app(self.app, host=host, port=port)
if __name__ == '__main__': if __name__ == '__main__':
Generated
+47 -2
View File
@@ -539,7 +539,7 @@ description = "Cross-platform colored terminal text."
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["main"] groups = ["main"]
markers = "platform_system == \"Windows\"" markers = "platform_system == \"Windows\" or sys_platform == \"win32\""
files = [ files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
@@ -1813,6 +1813,30 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple" url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors" 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]] [[package]]
name = "markupsafe" name = "markupsafe"
version = "3.0.2" version = "3.0.2"
@@ -4241,6 +4265,27 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple" url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "mirrors" 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]] [[package]]
name = "yarl" name = "yarl"
version = "1.18.3" version = "1.18.3"
@@ -4346,4 +4391,4 @@ reference = "mirrors"
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = "^3.10.16" python-versions = "^3.10.16"
content-hash = "9e62f0425b6f332e766fb61ceb2b0d8e49a9ba4dc439e181ba709fa8436ad482" content-hash = "8645b79840ee30a5262cc87a54aff45a21756f37d6e15fec3215e342ebfe864d"
+1
View File
@@ -25,6 +25,7 @@ aiohttp-cors = "0.7.0"
ormsgpack = "1.7.0" ormsgpack = "1.7.0"
ruamel-yaml = "0.18.10" ruamel-yaml = "0.18.10"
setuptools = "^75.8.0" setuptools = "^75.8.0"
loguru = "^0.7.3"
[[tool.poetry.source]] [[tool.poetry.source]]