liumaolin
commited on
Commit
·
851495c
1
Parent(s):
013a9e4
Replace `logging` with centralized `loguru`-based logger across all modules.
Browse files- src/voice_dialogue/api/app.py +1 -8
- src/voice_dialogue/api/core/config.py +1 -3
- src/voice_dialogue/api/core/lifespan.py +3 -7
- src/voice_dialogue/api/core/service_manager.py +1 -2
- src/voice_dialogue/api/middleware/logging.py +2 -3
- src/voice_dialogue/api/routes/asr_routes.py +1 -3
- src/voice_dialogue/api/routes/system_routes.py +1 -2
- src/voice_dialogue/api/routes/tts_routes.py +1 -2
- src/voice_dialogue/api/routes/websocket_routes.py +1 -3
- src/voice_dialogue/api/server.py +15 -9
- src/voice_dialogue/core/launcher.py +5 -4
- src/voice_dialogue/services/audio/capture.py +2 -1
- src/voice_dialogue/services/audio/generators/configs/__init__.py +6 -6
- src/voice_dialogue/services/audio/generators/manager.py +11 -11
- src/voice_dialogue/services/audio/generators/models/__init__.py +8 -8
- src/voice_dialogue/services/audio/generators/models/base.py +1 -2
- src/voice_dialogue/services/audio/generators/runtime/__init__.py +4 -4
- src/voice_dialogue/services/audio/generators/runtime/kokoro.py +3 -2
- src/voice_dialogue/services/audio/generators/runtime/moyoyo.py +3 -2
- src/voice_dialogue/services/audio/player.py +3 -2
- src/voice_dialogue/services/speech/monitor.py +2 -1
- src/voice_dialogue/services/speech/recognizers/manager.py +17 -17
- src/voice_dialogue/services/speech/recognizers/models/__init__.py +4 -4
- src/voice_dialogue/services/speech/recognizers/models/funasr.py +5 -4
- src/voice_dialogue/services/speech/recognizers/models/whisper.py +4 -3
- src/voice_dialogue/services/text/generator.py +9 -8
- src/voice_dialogue/services/text/processor.py +3 -2
- src/voice_dialogue/utils/__init__.py +2 -0
- src/voice_dialogue/utils/logger.py +23 -67
src/voice_dialogue/api/app.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
import logging
|
| 2 |
from typing import Dict, Any
|
| 3 |
|
| 4 |
from fastapi import FastAPI, HTTPException, APIRouter
|
|
@@ -7,19 +6,13 @@ from fastapi.responses import RedirectResponse
|
|
| 7 |
from fastapi.staticfiles import StaticFiles
|
| 8 |
|
| 9 |
from voice_dialogue.config.paths import FRONTEND_ASSETS_PATH
|
|
|
|
| 10 |
from .core.config import AppConfig
|
| 11 |
from .core.lifespan import lifespan
|
| 12 |
from .middleware.logging import LoggingMiddleware
|
| 13 |
from .middleware.rate_limit import RateLimitMiddleware
|
| 14 |
from .routes import tts_routes, asr_routes, system_routes, websocket_routes
|
| 15 |
|
| 16 |
-
# 配置日志
|
| 17 |
-
logging.basicConfig(
|
| 18 |
-
level=logging.INFO,
|
| 19 |
-
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
| 20 |
-
)
|
| 21 |
-
logger = logging.getLogger(__name__)
|
| 22 |
-
|
| 23 |
|
| 24 |
def create_app() -> FastAPI:
|
| 25 |
"""创建并配置FastAPI应用"""
|
|
|
|
|
|
|
| 1 |
from typing import Dict, Any
|
| 2 |
|
| 3 |
from fastapi import FastAPI, HTTPException, APIRouter
|
|
|
|
| 6 |
from fastapi.staticfiles import StaticFiles
|
| 7 |
|
| 8 |
from voice_dialogue.config.paths import FRONTEND_ASSETS_PATH
|
| 9 |
+
from voice_dialogue.utils.logger import logger
|
| 10 |
from .core.config import AppConfig
|
| 11 |
from .core.lifespan import lifespan
|
| 12 |
from .middleware.logging import LoggingMiddleware
|
| 13 |
from .middleware.rate_limit import RateLimitMiddleware
|
| 14 |
from .routes import tts_routes, asr_routes, system_routes, websocket_routes
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
def create_app() -> FastAPI:
|
| 18 |
"""创建并配置FastAPI应用"""
|
src/voice_dialogue/api/core/config.py
CHANGED
|
@@ -1,9 +1,7 @@
|
|
| 1 |
-
import logging
|
| 2 |
from typing import Dict, Any
|
| 3 |
|
| 4 |
from voice_dialogue.services.audio.generators import tts_config_registry
|
| 5 |
-
|
| 6 |
-
logger = logging.getLogger(__name__)
|
| 7 |
|
| 8 |
|
| 9 |
class TTSConfigInitializer:
|
|
|
|
|
|
|
| 1 |
from typing import Dict, Any
|
| 2 |
|
| 3 |
from voice_dialogue.services.audio.generators import tts_config_registry
|
| 4 |
+
from voice_dialogue.utils.logger import logger
|
|
|
|
| 5 |
|
| 6 |
|
| 7 |
class TTSConfigInitializer:
|
src/voice_dialogue/api/core/lifespan.py
CHANGED
|
@@ -1,18 +1,15 @@
|
|
| 1 |
-
import logging
|
| 2 |
import time
|
| 3 |
from contextlib import asynccontextmanager
|
| 4 |
|
| 5 |
from fastapi import FastAPI
|
| 6 |
|
| 7 |
from voice_dialogue.services.audio.generators import tts_config_registry
|
| 8 |
-
from voice_dialogue.utils import get_system_language
|
| 9 |
from .config import TTSConfigInitializer
|
| 10 |
from .service_factories import get_core_voice_service_definitions
|
| 11 |
from .service_manager import ServiceManager
|
| 12 |
from ..schemas.tts_schemas import generate_model_id
|
| 13 |
|
| 14 |
-
logger = logging.getLogger(__name__)
|
| 15 |
-
|
| 16 |
|
| 17 |
class LifespanManager:
|
| 18 |
"""应用生命周期管理器"""
|
|
@@ -38,16 +35,15 @@ class LifespanManager:
|
|
| 38 |
default_tts_config = tts_config_registry.get_default_config_for_system()
|
| 39 |
current_tts_model_id = None
|
| 40 |
current_tts_character_name = None
|
| 41 |
-
|
| 42 |
if default_tts_config:
|
| 43 |
current_tts_model_id = generate_model_id(
|
| 44 |
-
default_tts_config.tts_type.value,
|
| 45 |
default_tts_config.character_name
|
| 46 |
)
|
| 47 |
current_tts_character_name = default_tts_config.character_name
|
| 48 |
logger.info(f"系统默认TTS模型: {current_tts_character_name} (ID: {current_tts_model_id})")
|
| 49 |
|
| 50 |
-
|
| 51 |
# 获取服务定义
|
| 52 |
service_definitions = get_core_voice_service_definitions(system_language)
|
| 53 |
|
|
|
|
|
|
|
| 1 |
import time
|
| 2 |
from contextlib import asynccontextmanager
|
| 3 |
|
| 4 |
from fastapi import FastAPI
|
| 5 |
|
| 6 |
from voice_dialogue.services.audio.generators import tts_config_registry
|
| 7 |
+
from voice_dialogue.utils import get_system_language, logger
|
| 8 |
from .config import TTSConfigInitializer
|
| 9 |
from .service_factories import get_core_voice_service_definitions
|
| 10 |
from .service_manager import ServiceManager
|
| 11 |
from ..schemas.tts_schemas import generate_model_id
|
| 12 |
|
|
|
|
|
|
|
| 13 |
|
| 14 |
class LifespanManager:
|
| 15 |
"""应用生命周期管理器"""
|
|
|
|
| 35 |
default_tts_config = tts_config_registry.get_default_config_for_system()
|
| 36 |
current_tts_model_id = None
|
| 37 |
current_tts_character_name = None
|
| 38 |
+
|
| 39 |
if default_tts_config:
|
| 40 |
current_tts_model_id = generate_model_id(
|
| 41 |
+
default_tts_config.tts_type.value,
|
| 42 |
default_tts_config.character_name
|
| 43 |
)
|
| 44 |
current_tts_character_name = default_tts_config.character_name
|
| 45 |
logger.info(f"系统默认TTS模型: {current_tts_character_name} (ID: {current_tts_model_id})")
|
| 46 |
|
|
|
|
| 47 |
# 获取服务定义
|
| 48 |
service_definitions = get_core_voice_service_definitions(system_language)
|
| 49 |
|
src/voice_dialogue/api/core/service_manager.py
CHANGED
|
@@ -1,9 +1,8 @@
|
|
| 1 |
-
import logging
|
| 2 |
import time
|
| 3 |
from dataclasses import dataclass
|
| 4 |
from typing import Dict, List, Callable, Any, Optional
|
| 5 |
|
| 6 |
-
logger
|
| 7 |
|
| 8 |
|
| 9 |
@dataclass
|
|
|
|
|
|
|
| 1 |
import time
|
| 2 |
from dataclasses import dataclass
|
| 3 |
from typing import Dict, List, Callable, Any, Optional
|
| 4 |
|
| 5 |
+
from voice_dialogue.utils.logger import logger
|
| 6 |
|
| 7 |
|
| 8 |
@dataclass
|
src/voice_dialogue/api/middleware/logging.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
| 1 |
-
import logging
|
| 2 |
import time
|
| 3 |
|
| 4 |
-
from fastapi import Request
|
| 5 |
from starlette.middleware.base import BaseHTTPMiddleware
|
| 6 |
|
| 7 |
-
logger
|
| 8 |
|
| 9 |
|
| 10 |
class LoggingMiddleware(BaseHTTPMiddleware):
|
|
|
|
|
|
|
| 1 |
import time
|
| 2 |
|
| 3 |
+
from fastapi import Request
|
| 4 |
from starlette.middleware.base import BaseHTTPMiddleware
|
| 5 |
|
| 6 |
+
from voice_dialogue.utils.logger import logger
|
| 7 |
|
| 8 |
|
| 9 |
class LoggingMiddleware(BaseHTTPMiddleware):
|
src/voice_dialogue/api/routes/asr_routes.py
CHANGED
|
@@ -1,14 +1,12 @@
|
|
| 1 |
-
import logging
|
| 2 |
-
|
| 3 |
from fastapi import APIRouter, HTTPException, Request, BackgroundTasks
|
| 4 |
|
| 5 |
from voice_dialogue.services.speech.recognizers import asr_manager
|
|
|
|
| 6 |
from ..core.service_factories import get_asr_worker_service_definition
|
| 7 |
from ..schemas.asr_schemas import (
|
| 8 |
SupportedLanguagesResponse, ASRInstanceRequest, ASRInstanceResponse
|
| 9 |
)
|
| 10 |
|
| 11 |
-
logger = logging.getLogger(__name__)
|
| 12 |
router = APIRouter()
|
| 13 |
|
| 14 |
_asr_creation_status = {
|
|
|
|
|
|
|
|
|
|
| 1 |
from fastapi import APIRouter, HTTPException, Request, BackgroundTasks
|
| 2 |
|
| 3 |
from voice_dialogue.services.speech.recognizers import asr_manager
|
| 4 |
+
from voice_dialogue.utils.logger import logger
|
| 5 |
from ..core.service_factories import get_asr_worker_service_definition
|
| 6 |
from ..schemas.asr_schemas import (
|
| 7 |
SupportedLanguagesResponse, ASRInstanceRequest, ASRInstanceResponse
|
| 8 |
)
|
| 9 |
|
|
|
|
| 10 |
router = APIRouter()
|
| 11 |
|
| 12 |
_asr_creation_status = {
|
src/voice_dialogue/api/routes/system_routes.py
CHANGED
|
@@ -1,16 +1,15 @@
|
|
| 1 |
import asyncio
|
| 2 |
-
import logging
|
| 3 |
import time
|
| 4 |
|
| 5 |
from fastapi import APIRouter, HTTPException, BackgroundTasks, Request
|
| 6 |
|
| 7 |
from voice_dialogue.core.constants import session_manager
|
|
|
|
| 8 |
from ..core.service_factories import get_audio_capture_service_definition
|
| 9 |
from ..schemas.system_schemas import (
|
| 10 |
SystemStatusResponse, SystemResponse
|
| 11 |
)
|
| 12 |
|
| 13 |
-
logger = logging.getLogger(__name__)
|
| 14 |
router = APIRouter()
|
| 15 |
|
| 16 |
# 全局系统状态
|
|
|
|
| 1 |
import asyncio
|
|
|
|
| 2 |
import time
|
| 3 |
|
| 4 |
from fastapi import APIRouter, HTTPException, BackgroundTasks, Request
|
| 5 |
|
| 6 |
from voice_dialogue.core.constants import session_manager
|
| 7 |
+
from voice_dialogue.utils.logger import logger
|
| 8 |
from ..core.service_factories import get_audio_capture_service_definition
|
| 9 |
from ..schemas.system_schemas import (
|
| 10 |
SystemStatusResponse, SystemResponse
|
| 11 |
)
|
| 12 |
|
|
|
|
| 13 |
router = APIRouter()
|
| 14 |
|
| 15 |
# 全局系统状态
|
src/voice_dialogue/api/routes/tts_routes.py
CHANGED
|
@@ -1,16 +1,15 @@
|
|
| 1 |
-
import logging
|
| 2 |
from typing import Optional
|
| 3 |
|
| 4 |
from fastapi import APIRouter, HTTPException, BackgroundTasks, Request
|
| 5 |
|
| 6 |
from voice_dialogue.services.audio.generators import tts_config_registry
|
|
|
|
| 7 |
from ..core.service_factories import get_tts_audio_generator_service_definition
|
| 8 |
from ..schemas.tts_schemas import (
|
| 9 |
TTSModelInfo, TTSModelListResponse, TTSModelLoadRequest,
|
| 10 |
TTSModelLoadResponse, TTSModelStatusResponse, generate_model_id
|
| 11 |
)
|
| 12 |
|
| 13 |
-
logger = logging.getLogger(__name__)
|
| 14 |
router = APIRouter()
|
| 15 |
|
| 16 |
# TTS模型加载状态管理
|
|
|
|
|
|
|
| 1 |
from typing import Optional
|
| 2 |
|
| 3 |
from fastapi import APIRouter, HTTPException, BackgroundTasks, Request
|
| 4 |
|
| 5 |
from voice_dialogue.services.audio.generators import tts_config_registry
|
| 6 |
+
from voice_dialogue.utils.logger import logger
|
| 7 |
from ..core.service_factories import get_tts_audio_generator_service_definition
|
| 8 |
from ..schemas.tts_schemas import (
|
| 9 |
TTSModelInfo, TTSModelListResponse, TTSModelLoadRequest,
|
| 10 |
TTSModelLoadResponse, TTSModelStatusResponse, generate_model_id
|
| 11 |
)
|
| 12 |
|
|
|
|
| 13 |
router = APIRouter()
|
| 14 |
|
| 15 |
# TTS模型加载状态管理
|
src/voice_dialogue/api/routes/websocket_routes.py
CHANGED
|
@@ -1,13 +1,11 @@
|
|
| 1 |
-
import logging
|
| 2 |
from queue import Empty
|
| 3 |
|
| 4 |
-
|
| 5 |
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
| 6 |
|
| 7 |
from voice_dialogue.core.constants import websocket_message_queue, session_manager
|
|
|
|
| 8 |
|
| 9 |
ws = APIRouter()
|
| 10 |
-
logger = logging.getLogger(__name__)
|
| 11 |
|
| 12 |
|
| 13 |
@ws.websocket("/api/v1/ws")
|
|
|
|
|
|
|
| 1 |
from queue import Empty
|
| 2 |
|
|
|
|
| 3 |
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
| 4 |
|
| 5 |
from voice_dialogue.core.constants import websocket_message_queue, session_manager
|
| 6 |
+
from voice_dialogue.utils.logger import logger
|
| 7 |
|
| 8 |
ws = APIRouter()
|
|
|
|
| 9 |
|
| 10 |
|
| 11 |
@ws.websocket("/api/v1/ws")
|
src/voice_dialogue/api/server.py
CHANGED
|
@@ -6,6 +6,8 @@ API服务器启动器
|
|
| 6 |
|
| 7 |
import uvicorn
|
| 8 |
|
|
|
|
|
|
|
| 9 |
|
| 10 |
def launch_api_server(host: str = "0.0.0.0", port: int = 8000, reload: bool = False):
|
| 11 |
"""
|
|
@@ -16,20 +18,24 @@ def launch_api_server(host: str = "0.0.0.0", port: int = 8000, reload: bool = Fa
|
|
| 16 |
port (int): 服务器端口,默认为 8000
|
| 17 |
reload (bool): 是否启用热重载,默认为 False
|
| 18 |
"""
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
uvicorn.
|
| 27 |
"voice_dialogue.api.app:app",
|
| 28 |
host=host,
|
| 29 |
port=port,
|
| 30 |
reload=reload,
|
| 31 |
-
log_level="info"
|
|
|
|
|
|
|
| 32 |
)
|
|
|
|
|
|
|
| 33 |
|
| 34 |
|
| 35 |
if __name__ == "__main__":
|
|
|
|
| 6 |
|
| 7 |
import uvicorn
|
| 8 |
|
| 9 |
+
from voice_dialogue.utils.logger import logger
|
| 10 |
+
|
| 11 |
|
| 12 |
def launch_api_server(host: str = "0.0.0.0", port: int = 8000, reload: bool = False):
|
| 13 |
"""
|
|
|
|
| 18 |
port (int): 服务器端口,默认为 8000
|
| 19 |
reload (bool): 是否启用热重载,默认为 False
|
| 20 |
"""
|
| 21 |
+
logger.info(f'{"=" * 80}')
|
| 22 |
+
logger.info(f'正在启动API服务器...')
|
| 23 |
+
logger.info(f"服务器地址: http://{host}:{port}")
|
| 24 |
+
logger.info(f"API文档: http://{host}:{port}/docs")
|
| 25 |
+
logger.info(f"热重载: {'启用' if reload else '禁用'}")
|
| 26 |
+
logger.info(f'{"=" * 80}')
|
| 27 |
+
|
| 28 |
+
uvicorn_config = uvicorn.Config(
|
| 29 |
"voice_dialogue.api.app:app",
|
| 30 |
host=host,
|
| 31 |
port=port,
|
| 32 |
reload=reload,
|
| 33 |
+
log_level="info",
|
| 34 |
+
log_config=None, # 不使用uvicorn的日志配置文件
|
| 35 |
+
use_colors=True, # 启用彩色日志
|
| 36 |
)
|
| 37 |
+
server = uvicorn.Server(uvicorn_config)
|
| 38 |
+
server.run()
|
| 39 |
|
| 40 |
|
| 41 |
if __name__ == "__main__":
|
src/voice_dialogue/core/launcher.py
CHANGED
|
@@ -20,6 +20,7 @@ from voice_dialogue.services.audio.player import AudioStreamPlayer
|
|
| 20 |
from voice_dialogue.services.speech.monitor import SpeechStateMonitor
|
| 21 |
from voice_dialogue.services.speech.recognizer import ASRWorker
|
| 22 |
from voice_dialogue.services.text.generator import LLMResponseGenerator
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
def launch_system(
|
|
@@ -58,7 +59,7 @@ def launch_system(
|
|
| 58 |
# 导入speaker配置相关功能
|
| 59 |
|
| 60 |
threads = []
|
| 61 |
-
|
| 62 |
# 音频采集
|
| 63 |
audio_frame_probe = EchoCancellingAudioCapture(audio_frames_queue=audio_frames_queue)
|
| 64 |
audio_frame_probe.start()
|
|
@@ -74,7 +75,7 @@ def launch_system(
|
|
| 74 |
|
| 75 |
# 语音识别
|
| 76 |
whisper_worker = ASRWorker(
|
| 77 |
-
user_voice_queue=user_voice_queue,
|
| 78 |
transcribed_text_queue=transcribed_text_queue,
|
| 79 |
language=user_language
|
| 80 |
)
|
|
@@ -114,8 +115,8 @@ def launch_system(
|
|
| 114 |
while not all([thread.is_ready for thread in threads]):
|
| 115 |
time.sleep(0.1)
|
| 116 |
|
| 117 |
-
|
| 118 |
-
|
| 119 |
# 等待所有线程结束
|
| 120 |
for thread in threads:
|
| 121 |
thread.join()
|
|
|
|
| 20 |
from voice_dialogue.services.speech.monitor import SpeechStateMonitor
|
| 21 |
from voice_dialogue.services.speech.recognizer import ASRWorker
|
| 22 |
from voice_dialogue.services.text.generator import LLMResponseGenerator
|
| 23 |
+
from voice_dialogue.utils.logger import logger
|
| 24 |
|
| 25 |
|
| 26 |
def launch_system(
|
|
|
|
| 59 |
# 导入speaker配置相关功能
|
| 60 |
|
| 61 |
threads = []
|
| 62 |
+
|
| 63 |
# 音频采集
|
| 64 |
audio_frame_probe = EchoCancellingAudioCapture(audio_frames_queue=audio_frames_queue)
|
| 65 |
audio_frame_probe.start()
|
|
|
|
| 75 |
|
| 76 |
# 语音识别
|
| 77 |
whisper_worker = ASRWorker(
|
| 78 |
+
user_voice_queue=user_voice_queue,
|
| 79 |
transcribed_text_queue=transcribed_text_queue,
|
| 80 |
language=user_language
|
| 81 |
)
|
|
|
|
| 115 |
while not all([thread.is_ready for thread in threads]):
|
| 116 |
time.sleep(0.1)
|
| 117 |
|
| 118 |
+
logger.info(f'{"=" * 80}\n服务启动成功\n{"=" * 80}')
|
| 119 |
+
|
| 120 |
# 等待所有线程结束
|
| 121 |
for thread in threads:
|
| 122 |
thread.join()
|
src/voice_dialogue/services/audio/capture.py
CHANGED
|
@@ -11,6 +11,7 @@ import numpy as np
|
|
| 11 |
|
| 12 |
from voice_dialogue.config.paths import LIBRARIES_PATH
|
| 13 |
from voice_dialogue.core.base import BaseThread
|
|
|
|
| 14 |
|
| 15 |
|
| 16 |
class EchoCancellingAudioCapture(BaseThread):
|
|
@@ -66,6 +67,6 @@ class EchoCancellingAudioCapture(BaseThread):
|
|
| 66 |
# 无数据时等待
|
| 67 |
time.sleep(0.01)
|
| 68 |
except Exception as e:
|
| 69 |
-
|
| 70 |
finally:
|
| 71 |
audio_recorder.stopRecord()
|
|
|
|
| 11 |
|
| 12 |
from voice_dialogue.config.paths import LIBRARIES_PATH
|
| 13 |
from voice_dialogue.core.base import BaseThread
|
| 14 |
+
from voice_dialogue.utils.logger import logger
|
| 15 |
|
| 16 |
|
| 17 |
class EchoCancellingAudioCapture(BaseThread):
|
|
|
|
| 67 |
# 无数据时等待
|
| 68 |
time.sleep(0.01)
|
| 69 |
except Exception as e:
|
| 70 |
+
logger.error(f'回声消除音频捕获器运行时发生错误: {e}')
|
| 71 |
finally:
|
| 72 |
audio_recorder.stopRecord()
|
src/voice_dialogue/services/audio/generators/configs/__init__.py
CHANGED
|
@@ -20,9 +20,9 @@ try:
|
|
| 20 |
}
|
| 21 |
|
| 22 |
except ImportError as e:
|
| 23 |
-
import
|
| 24 |
|
| 25 |
-
|
| 26 |
__all__ = []
|
| 27 |
CONFIG_GETTERS = {}
|
| 28 |
|
|
@@ -35,8 +35,8 @@ def get_all_configs():
|
|
| 35 |
configs = getter_func()
|
| 36 |
all_configs.extend(configs)
|
| 37 |
except Exception as e:
|
| 38 |
-
import
|
| 39 |
-
|
| 40 |
return all_configs
|
| 41 |
|
| 42 |
|
|
@@ -46,7 +46,7 @@ def get_configs_by_type(tts_type: str):
|
|
| 46 |
try:
|
| 47 |
return CONFIG_GETTERS[tts_type]()
|
| 48 |
except Exception as e:
|
| 49 |
-
import
|
| 50 |
-
|
| 51 |
return []
|
| 52 |
return []
|
|
|
|
| 20 |
}
|
| 21 |
|
| 22 |
except ImportError as e:
|
| 23 |
+
from voice_dialogue.utils.logger import logger
|
| 24 |
|
| 25 |
+
logger.warning(f"Failed to import some config modules: {e}")
|
| 26 |
__all__ = []
|
| 27 |
CONFIG_GETTERS = {}
|
| 28 |
|
|
|
|
| 35 |
configs = getter_func()
|
| 36 |
all_configs.extend(configs)
|
| 37 |
except Exception as e:
|
| 38 |
+
from voice_dialogue.utils.logger import logger
|
| 39 |
+
logger.error(f"Failed to load configs from {getter_func.__name__}: {e}")
|
| 40 |
return all_configs
|
| 41 |
|
| 42 |
|
|
|
|
| 46 |
try:
|
| 47 |
return CONFIG_GETTERS[tts_type]()
|
| 48 |
except Exception as e:
|
| 49 |
+
from voice_dialogue.utils.logger import logger
|
| 50 |
+
logger.error(f"Failed to load configs for {tts_type}: {e}")
|
| 51 |
return []
|
| 52 |
return []
|
src/voice_dialogue/services/audio/generators/manager.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
| 1 |
import importlib.util
|
| 2 |
import inspect
|
| 3 |
-
import logging
|
| 4 |
import re
|
| 5 |
from dataclasses import dataclass
|
| 6 |
from pathlib import Path
|
| 7 |
from typing import Dict, Type
|
| 8 |
|
|
|
|
| 9 |
from .models.base import BaseTTSConfig
|
| 10 |
from .runtime.interface import TTSInterface
|
| 11 |
|
|
@@ -22,11 +22,11 @@ class TTSRegistryTables:
|
|
| 22 |
|
| 23 |
def print(self, key: str = None) -> None:
|
| 24 |
"""打印已注册的TTS类"""
|
| 25 |
-
|
| 26 |
headers = ["register name", "class name", "class location"]
|
| 27 |
|
| 28 |
if self.tts_classes and (key is None or "tts_classes" in key):
|
| 29 |
-
|
| 30 |
metas = []
|
| 31 |
for register_key, tts_class in self.tts_classes.items():
|
| 32 |
class_file = inspect.getfile(tts_class)
|
|
@@ -46,12 +46,12 @@ class TTSRegistryTables:
|
|
| 46 |
col_widths = [max(len(str(item)) for item in col) for col in zip(*data)]
|
| 47 |
|
| 48 |
for row in data:
|
| 49 |
-
|
| 50 |
"| "
|
| 51 |
+ " | ".join(str(item).ljust(width) for item, width in zip(row, col_widths))
|
| 52 |
+ " |"
|
| 53 |
)
|
| 54 |
-
|
| 55 |
|
| 56 |
def register(self, register_table_key: str, key: str = None) -> callable:
|
| 57 |
"""装饰器,用于注册TTS类"""
|
|
@@ -59,18 +59,18 @@ class TTSRegistryTables:
|
|
| 59 |
def decorator(target_class):
|
| 60 |
if not hasattr(self, register_table_key):
|
| 61 |
setattr(self, register_table_key, {})
|
| 62 |
-
|
| 63 |
|
| 64 |
registry = getattr(self, register_table_key)
|
| 65 |
registry_key = key if key is not None else target_class.__name__
|
| 66 |
|
| 67 |
if registry_key in registry:
|
| 68 |
-
|
| 69 |
f"Key {registry_key} already exists in {register_table_key}, re-register"
|
| 70 |
)
|
| 71 |
|
| 72 |
registry[registry_key] = target_class
|
| 73 |
-
|
| 74 |
return target_class
|
| 75 |
|
| 76 |
return decorator
|
|
@@ -160,11 +160,11 @@ def register_all_tts():
|
|
| 160 |
)
|
| 161 |
module = importlib.util.module_from_spec(spec)
|
| 162 |
spec.loader.exec_module(module)
|
| 163 |
-
|
| 164 |
except ImportError as e:
|
| 165 |
-
|
| 166 |
except Exception as e:
|
| 167 |
-
|
| 168 |
|
| 169 |
|
| 170 |
# 在模块导入时自动注册所有TTS
|
|
|
|
| 1 |
import importlib.util
|
| 2 |
import inspect
|
|
|
|
| 3 |
import re
|
| 4 |
from dataclasses import dataclass
|
| 5 |
from pathlib import Path
|
| 6 |
from typing import Dict, Type
|
| 7 |
|
| 8 |
+
from voice_dialogue.utils.logger import logger
|
| 9 |
from .models.base import BaseTTSConfig
|
| 10 |
from .runtime.interface import TTSInterface
|
| 11 |
|
|
|
|
| 22 |
|
| 23 |
def print(self, key: str = None) -> None:
|
| 24 |
"""打印已注册的TTS类"""
|
| 25 |
+
logger.info("\nTTS Registry Tables: \n")
|
| 26 |
headers = ["register name", "class name", "class location"]
|
| 27 |
|
| 28 |
if self.tts_classes and (key is None or "tts_classes" in key):
|
| 29 |
+
logger.info(f"----------- ** tts_classes ** --------------")
|
| 30 |
metas = []
|
| 31 |
for register_key, tts_class in self.tts_classes.items():
|
| 32 |
class_file = inspect.getfile(tts_class)
|
|
|
|
| 46 |
col_widths = [max(len(str(item)) for item in col) for col in zip(*data)]
|
| 47 |
|
| 48 |
for row in data:
|
| 49 |
+
logger.info(
|
| 50 |
"| "
|
| 51 |
+ " | ".join(str(item).ljust(width) for item, width in zip(row, col_widths))
|
| 52 |
+ " |"
|
| 53 |
)
|
| 54 |
+
logger.info("\n")
|
| 55 |
|
| 56 |
def register(self, register_table_key: str, key: str = None) -> callable:
|
| 57 |
"""装饰器,用于注册TTS类"""
|
|
|
|
| 59 |
def decorator(target_class):
|
| 60 |
if not hasattr(self, register_table_key):
|
| 61 |
setattr(self, register_table_key, {})
|
| 62 |
+
logger.debug(f"New TTS registry table added: {register_table_key}")
|
| 63 |
|
| 64 |
registry = getattr(self, register_table_key)
|
| 65 |
registry_key = key if key is not None else target_class.__name__
|
| 66 |
|
| 67 |
if registry_key in registry:
|
| 68 |
+
logger.debug(
|
| 69 |
f"Key {registry_key} already exists in {register_table_key}, re-register"
|
| 70 |
)
|
| 71 |
|
| 72 |
registry[registry_key] = target_class
|
| 73 |
+
logger.info(f"Registered TTS class: {registry_key} -> {target_class.__name__}")
|
| 74 |
return target_class
|
| 75 |
|
| 76 |
return decorator
|
|
|
|
| 160 |
)
|
| 161 |
module = importlib.util.module_from_spec(spec)
|
| 162 |
spec.loader.exec_module(module)
|
| 163 |
+
logger.info(f"Successfully imported TTS module: {module_name}")
|
| 164 |
except ImportError as e:
|
| 165 |
+
logger.warning(f"Failed to import TTS module {module_name}: {e}")
|
| 166 |
except Exception as e:
|
| 167 |
+
logger.error(f"Unexpected error importing TTS module {module_name}: {e}")
|
| 168 |
|
| 169 |
|
| 170 |
# 在模块导入时自动注册所有TTS
|
src/voice_dialogue/services/audio/generators/models/__init__.py
CHANGED
|
@@ -22,9 +22,9 @@ try:
|
|
| 22 |
_moyoyo_available = True
|
| 23 |
except ImportError:
|
| 24 |
_moyoyo_available = False
|
| 25 |
-
import
|
| 26 |
|
| 27 |
-
|
| 28 |
|
| 29 |
try:
|
| 30 |
from .kokoro import KokoroTTSConfig
|
|
@@ -32,9 +32,9 @@ try:
|
|
| 32 |
_kokoro_available = True
|
| 33 |
except ImportError:
|
| 34 |
_kokoro_available = False
|
| 35 |
-
import
|
| 36 |
|
| 37 |
-
|
| 38 |
|
| 39 |
# 动态构建导出列表
|
| 40 |
__all__ = [
|
|
@@ -60,8 +60,8 @@ def _auto_register_configs():
|
|
| 60 |
for config in get_moyoyo_configs():
|
| 61 |
tts_config_registry.register_config(config)
|
| 62 |
except Exception as e:
|
| 63 |
-
import
|
| 64 |
-
|
| 65 |
|
| 66 |
try:
|
| 67 |
if _kokoro_available:
|
|
@@ -69,8 +69,8 @@ def _auto_register_configs():
|
|
| 69 |
for config in get_kokoro_configs():
|
| 70 |
tts_config_registry.register_config(config)
|
| 71 |
except Exception as e:
|
| 72 |
-
import
|
| 73 |
-
|
| 74 |
|
| 75 |
|
| 76 |
# 模块加载时自动注册配置
|
|
|
|
| 22 |
_moyoyo_available = True
|
| 23 |
except ImportError:
|
| 24 |
_moyoyo_available = False
|
| 25 |
+
from voice_dialogue.utils.logger import logger
|
| 26 |
|
| 27 |
+
logger.warning("MoYoYo TTS config not available")
|
| 28 |
|
| 29 |
try:
|
| 30 |
from .kokoro import KokoroTTSConfig
|
|
|
|
| 32 |
_kokoro_available = True
|
| 33 |
except ImportError:
|
| 34 |
_kokoro_available = False
|
| 35 |
+
from voice_dialogue.utils.logger import logger
|
| 36 |
|
| 37 |
+
logger.warning("Kokoro TTS config not available")
|
| 38 |
|
| 39 |
# 动态构建导出列表
|
| 40 |
__all__ = [
|
|
|
|
| 60 |
for config in get_moyoyo_configs():
|
| 61 |
tts_config_registry.register_config(config)
|
| 62 |
except Exception as e:
|
| 63 |
+
from voice_dialogue.utils.logger import logger
|
| 64 |
+
logger.error(f"Failed to auto-register configs: {e}")
|
| 65 |
|
| 66 |
try:
|
| 67 |
if _kokoro_available:
|
|
|
|
| 69 |
for config in get_kokoro_configs():
|
| 70 |
tts_config_registry.register_config(config)
|
| 71 |
except Exception as e:
|
| 72 |
+
from voice_dialogue.utils.logger import logger
|
| 73 |
+
logger.error(f"Failed to auto-register configs: {e}")
|
| 74 |
|
| 75 |
|
| 76 |
# 模块加载时自动注册配置
|
src/voice_dialogue/services/audio/generators/models/base.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
import logging
|
| 2 |
import typing
|
| 3 |
from abc import ABC, abstractmethod
|
| 4 |
from enum import Enum
|
|
@@ -6,7 +5,7 @@ from pathlib import Path
|
|
| 6 |
|
| 7 |
from pydantic import BaseModel
|
| 8 |
|
| 9 |
-
logger
|
| 10 |
|
| 11 |
|
| 12 |
class TTSConfigType(Enum):
|
|
|
|
|
|
|
| 1 |
import typing
|
| 2 |
from abc import ABC, abstractmethod
|
| 3 |
from enum import Enum
|
|
|
|
| 5 |
|
| 6 |
from pydantic import BaseModel
|
| 7 |
|
| 8 |
+
from voice_dialogue.utils.logger import logger
|
| 9 |
|
| 10 |
|
| 11 |
class TTSConfigType(Enum):
|
src/voice_dialogue/services/audio/generators/runtime/__init__.py
CHANGED
|
@@ -21,9 +21,9 @@ try:
|
|
| 21 |
__all__.append('MoYoYoTTS')
|
| 22 |
except ImportError as e:
|
| 23 |
# 如果某些TTS实现无法导入,不影响整体功能
|
| 24 |
-
import
|
| 25 |
|
| 26 |
-
|
| 27 |
|
| 28 |
try:
|
| 29 |
from .kokoro import KokoroTTS
|
|
@@ -31,6 +31,6 @@ try:
|
|
| 31 |
__all__.append('KokoroTTS')
|
| 32 |
except ImportError as e:
|
| 33 |
# 如果某些TTS实现无法导入,不影响整体功能
|
| 34 |
-
import
|
| 35 |
|
| 36 |
-
|
|
|
|
| 21 |
__all__.append('MoYoYoTTS')
|
| 22 |
except ImportError as e:
|
| 23 |
# 如果某些TTS实现无法导入,不影响整体功能
|
| 24 |
+
from voice_dialogue.utils.logger import logger
|
| 25 |
|
| 26 |
+
logger.warning(f"Failed to import some TTS implementations: {e}")
|
| 27 |
|
| 28 |
try:
|
| 29 |
from .kokoro import KokoroTTS
|
|
|
|
| 31 |
__all__.append('KokoroTTS')
|
| 32 |
except ImportError as e:
|
| 33 |
# 如果某些TTS实现无法导入,不影响整体功能
|
| 34 |
+
from voice_dialogue.utils.logger import logger
|
| 35 |
|
| 36 |
+
logger.warning(f"Failed to import some TTS implementations: {e}")
|
src/voice_dialogue/services/audio/generators/runtime/kokoro.py
CHANGED
|
@@ -6,6 +6,7 @@ from kokoro_onnx import Kokoro
|
|
| 6 |
from voice_dialogue.services.audio.generators.configs.kokoro import KokoroTTSConfig
|
| 7 |
from voice_dialogue.services.audio.generators.manager import tts_tables
|
| 8 |
from voice_dialogue.services.audio.generators.runtime.interface import TTSInterface
|
|
|
|
| 9 |
|
| 10 |
|
| 11 |
@tts_tables.register("tts_classes", "kokoro")
|
|
@@ -35,12 +36,12 @@ class KokoroTTS(TTSInterface):
|
|
| 35 |
self.espeak_ng = en.G2P(trf=False, british=False, fallback=fallback)
|
| 36 |
|
| 37 |
def warmup(self, warmup_steps: int = 1) -> None:
|
| 38 |
-
|
| 39 |
warmup_texts = ['Warming up TTS engine.', '预热文字转音频引擎。']
|
| 40 |
for _ in range(warmup_steps):
|
| 41 |
for warmup_text in warmup_texts:
|
| 42 |
self.synthesize(warmup_text)
|
| 43 |
-
|
| 44 |
|
| 45 |
def synthesize(self, text: str, **kwargs) -> Tuple[np.ndarray, int]:
|
| 46 |
phonemes, _ = self.espeak_ng(text)
|
|
|
|
| 6 |
from voice_dialogue.services.audio.generators.configs.kokoro import KokoroTTSConfig
|
| 7 |
from voice_dialogue.services.audio.generators.manager import tts_tables
|
| 8 |
from voice_dialogue.services.audio.generators.runtime.interface import TTSInterface
|
| 9 |
+
from voice_dialogue.utils.logger import logger
|
| 10 |
|
| 11 |
|
| 12 |
@tts_tables.register("tts_classes", "kokoro")
|
|
|
|
| 36 |
self.espeak_ng = en.G2P(trf=False, british=False, fallback=fallback)
|
| 37 |
|
| 38 |
def warmup(self, warmup_steps: int = 1) -> None:
|
| 39 |
+
logger.info('[INFO:] Warming up Kokoro TTS engine...')
|
| 40 |
warmup_texts = ['Warming up TTS engine.', '预热文字转音频引擎。']
|
| 41 |
for _ in range(warmup_steps):
|
| 42 |
for warmup_text in warmup_texts:
|
| 43 |
self.synthesize(warmup_text)
|
| 44 |
+
logger.info('[INFO:] Warm up Kokoro TTS engine finished.')
|
| 45 |
|
| 46 |
def synthesize(self, text: str, **kwargs) -> Tuple[np.ndarray, int]:
|
| 47 |
phonemes, _ = self.espeak_ng(text)
|
src/voice_dialogue/services/audio/generators/runtime/moyoyo.py
CHANGED
|
@@ -8,6 +8,7 @@ from voice_dialogue.config.paths import load_third_party
|
|
| 8 |
from voice_dialogue.services.audio.generators.manager import tts_tables
|
| 9 |
from voice_dialogue.services.audio.generators.models.moyoyo import MoYoYoTTSConfig
|
| 10 |
from voice_dialogue.services.audio.generators.runtime.interface import TTSInterface
|
|
|
|
| 11 |
|
| 12 |
load_third_party()
|
| 13 |
|
|
@@ -43,12 +44,12 @@ class MoYoYoTTS(TTSInterface):
|
|
| 43 |
|
| 44 |
def warmup(self, warmup_steps: int = 1) -> None:
|
| 45 |
"""预热TTS引擎"""
|
| 46 |
-
|
| 47 |
warmup_texts = ['Warming up TTS engine.', '预热文字转音频引擎。']
|
| 48 |
for _ in range(warmup_steps):
|
| 49 |
for warmup_text in warmup_texts:
|
| 50 |
self.tts_module.generate_audio(warmup_text, warmup=True)
|
| 51 |
-
|
| 52 |
|
| 53 |
def synthesize(self, text: str, **kwargs) -> Tuple[np.ndarray, int]:
|
| 54 |
"""合成语音"""
|
|
|
|
| 8 |
from voice_dialogue.services.audio.generators.manager import tts_tables
|
| 9 |
from voice_dialogue.services.audio.generators.models.moyoyo import MoYoYoTTSConfig
|
| 10 |
from voice_dialogue.services.audio.generators.runtime.interface import TTSInterface
|
| 11 |
+
from voice_dialogue.utils.logger import logger
|
| 12 |
|
| 13 |
load_third_party()
|
| 14 |
|
|
|
|
| 44 |
|
| 45 |
def warmup(self, warmup_steps: int = 1) -> None:
|
| 46 |
"""预热TTS引擎"""
|
| 47 |
+
logger.info('[INFO:] Warming up MoYoYo TTS engine...')
|
| 48 |
warmup_texts = ['Warming up TTS engine.', '预热文字转音频引擎。']
|
| 49 |
for _ in range(warmup_steps):
|
| 50 |
for warmup_text in warmup_texts:
|
| 51 |
self.tts_module.generate_audio(warmup_text, warmup=True)
|
| 52 |
+
logger.info('[INFO:] Warm up MoYoYo TTS engine finished.')
|
| 53 |
|
| 54 |
def synthesize(self, text: str, **kwargs) -> Tuple[np.ndarray, int]:
|
| 55 |
"""合成语音"""
|
src/voice_dialogue/services/audio/player.py
CHANGED
|
@@ -12,6 +12,7 @@ from voice_dialogue.core.constants import (
|
|
| 12 |
silence_over_threshold_event
|
| 13 |
)
|
| 14 |
from voice_dialogue.models.voice_task import VoiceTask, AnswerDisplayMessage
|
|
|
|
| 15 |
|
| 16 |
|
| 17 |
class AudioStreamPlayer(BaseThread):
|
|
@@ -40,7 +41,7 @@ class AudioStreamPlayer(BaseThread):
|
|
| 40 |
task_id = voice_task.id
|
| 41 |
answer_id = voice_task.answer_id
|
| 42 |
if user_still_speaking_event.is_set():
|
| 43 |
-
|
| 44 |
voice_state_manager.drop_audio_task(task_id)
|
| 45 |
dropped_audio_cache[answer_id] = answer_id
|
| 46 |
user_still_speaking_event.clear()
|
|
@@ -84,7 +85,7 @@ class AudioStreamPlayer(BaseThread):
|
|
| 84 |
self.playing_audio(audio_data, sample_rate)
|
| 85 |
|
| 86 |
if self.audio_playing_queue.empty():
|
| 87 |
-
|
| 88 |
|
| 89 |
break
|
| 90 |
|
|
|
|
| 12 |
silence_over_threshold_event
|
| 13 |
)
|
| 14 |
from voice_dialogue.models.voice_task import VoiceTask, AnswerDisplayMessage
|
| 15 |
+
from voice_dialogue.utils.logger import logger
|
| 16 |
|
| 17 |
|
| 18 |
class AudioStreamPlayer(BaseThread):
|
|
|
|
| 41 |
task_id = voice_task.id
|
| 42 |
answer_id = voice_task.answer_id
|
| 43 |
if user_still_speaking_event.is_set():
|
| 44 |
+
logger.info('用户还有说话')
|
| 45 |
voice_state_manager.drop_audio_task(task_id)
|
| 46 |
dropped_audio_cache[answer_id] = answer_id
|
| 47 |
user_still_speaking_event.clear()
|
|
|
|
| 85 |
self.playing_audio(audio_data, sample_rate)
|
| 86 |
|
| 87 |
if self.audio_playing_queue.empty():
|
| 88 |
+
logger.info(f'回答播放完了')
|
| 89 |
|
| 90 |
break
|
| 91 |
|
src/voice_dialogue/services/speech/monitor.py
CHANGED
|
@@ -19,6 +19,7 @@ from voice_dialogue.core.constants import (
|
|
| 19 |
)
|
| 20 |
from voice_dialogue.core.enums import AudioState
|
| 21 |
from voice_dialogue.models.voice_task import VoiceTask
|
|
|
|
| 22 |
|
| 23 |
|
| 24 |
class SpeechMonitorConfig:
|
|
@@ -280,6 +281,6 @@ class SpeechStateMonitor(BaseThread):
|
|
| 280 |
|
| 281 |
except Exception as e:
|
| 282 |
# 错误处理,防止线程崩溃
|
| 283 |
-
|
| 284 |
time.sleep(0.1) # 避免错误循环
|
| 285 |
continue
|
|
|
|
| 19 |
)
|
| 20 |
from voice_dialogue.core.enums import AudioState
|
| 21 |
from voice_dialogue.models.voice_task import VoiceTask
|
| 22 |
+
from voice_dialogue.utils.logger import logger
|
| 23 |
|
| 24 |
|
| 25 |
class SpeechMonitorConfig:
|
|
|
|
| 281 |
|
| 282 |
except Exception as e:
|
| 283 |
# 错误处理,防止线程崩溃
|
| 284 |
+
logger.error(f"SpeechStateMonitor 处理错误: {e}")
|
| 285 |
time.sleep(0.1) # 避免错误循环
|
| 286 |
continue
|
src/voice_dialogue/services/speech/recognizers/manager.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
| 1 |
import importlib.util
|
| 2 |
import inspect
|
| 3 |
-
import logging
|
| 4 |
import re
|
| 5 |
from dataclasses import dataclass
|
| 6 |
from typing import Dict, Type, List, Literal, Optional
|
| 7 |
|
|
|
|
| 8 |
from .models import ASRInterface
|
| 9 |
|
| 10 |
|
|
@@ -20,11 +20,11 @@ class ASRRegistryTables:
|
|
| 20 |
|
| 21 |
def print(self, key: str = None) -> None:
|
| 22 |
"""打印已注册的ASR类"""
|
| 23 |
-
|
| 24 |
headers = ["register name", "class name", "class location", "supported languages"]
|
| 25 |
|
| 26 |
if self.asr_classes and (key is None or "asr_classes" in key):
|
| 27 |
-
|
| 28 |
metas = []
|
| 29 |
for register_key, asr_class in self.asr_classes.items():
|
| 30 |
class_file = inspect.getfile(asr_class)
|
|
@@ -53,12 +53,12 @@ class ASRRegistryTables:
|
|
| 53 |
col_widths = [max(len(str(item)) for item in col) for col in zip(*data)]
|
| 54 |
|
| 55 |
for row in data:
|
| 56 |
-
|
| 57 |
"| "
|
| 58 |
+ " | ".join(str(item).ljust(width) for item, width in zip(row, col_widths))
|
| 59 |
+ " |"
|
| 60 |
)
|
| 61 |
-
|
| 62 |
|
| 63 |
def register(self, register_table_key: str, key: str = None) -> callable:
|
| 64 |
"""装饰器,用于注册ASR类"""
|
|
@@ -66,18 +66,18 @@ class ASRRegistryTables:
|
|
| 66 |
def decorator(target_class):
|
| 67 |
if not hasattr(self, register_table_key):
|
| 68 |
setattr(self, register_table_key, {})
|
| 69 |
-
|
| 70 |
|
| 71 |
registry = getattr(self, register_table_key)
|
| 72 |
registry_key = key if key is not None else target_class.__name__
|
| 73 |
|
| 74 |
if registry_key in registry:
|
| 75 |
-
|
| 76 |
f"Key {registry_key} already exists in {register_table_key}, re-register"
|
| 77 |
)
|
| 78 |
|
| 79 |
registry[registry_key] = target_class
|
| 80 |
-
|
| 81 |
return target_class
|
| 82 |
|
| 83 |
return decorator
|
|
@@ -121,11 +121,11 @@ class ASRManager:
|
|
| 121 |
asr_class = asr_tables.asr_classes[asr_type]
|
| 122 |
instance = asr_class()
|
| 123 |
|
| 124 |
-
|
| 125 |
return instance
|
| 126 |
|
| 127 |
except Exception as e:
|
| 128 |
-
|
| 129 |
raise
|
| 130 |
|
| 131 |
def get_or_create_asr(self, language: Literal['auto', 'zh', 'en']) -> ASRInterface:
|
|
@@ -165,7 +165,7 @@ class ASRManager:
|
|
| 165 |
raise ValueError(f"ASR类型 '{asr_type}' 未注册")
|
| 166 |
|
| 167 |
self._language_to_asr_mapping[language] = asr_type
|
| 168 |
-
|
| 169 |
|
| 170 |
def list_registered_asr(self) -> Dict[str, Type[ASRInterface]]:
|
| 171 |
"""列出所有已注册的ASR类型"""
|
|
@@ -190,7 +190,7 @@ class ASRManager:
|
|
| 190 |
# languages = asr_tables._get_asr_supported_languages(asr_key)
|
| 191 |
# supported_languages[asr_key] = languages
|
| 192 |
except Exception as e:
|
| 193 |
-
|
| 194 |
supported_languages[asr_key] = ['unknown']
|
| 195 |
|
| 196 |
return supported_languages
|
|
@@ -251,9 +251,9 @@ class ASRManager:
|
|
| 251 |
|
| 252 |
def cleanup(self) -> None:
|
| 253 |
"""清理所有ASR实例"""
|
| 254 |
-
|
| 255 |
self._asr_instances.clear()
|
| 256 |
-
|
| 257 |
|
| 258 |
def print_registry(self) -> None:
|
| 259 |
"""打印注册表信息"""
|
|
@@ -301,11 +301,11 @@ def register_all_asr():
|
|
| 301 |
)
|
| 302 |
module = importlib.util.module_from_spec(spec)
|
| 303 |
spec.loader.exec_module(module)
|
| 304 |
-
|
| 305 |
except ImportError as e:
|
| 306 |
-
|
| 307 |
except Exception as e:
|
| 308 |
-
|
| 309 |
|
| 310 |
|
| 311 |
# 在模块导入时自动注册所有ASR
|
|
|
|
| 1 |
import importlib.util
|
| 2 |
import inspect
|
|
|
|
| 3 |
import re
|
| 4 |
from dataclasses import dataclass
|
| 5 |
from typing import Dict, Type, List, Literal, Optional
|
| 6 |
|
| 7 |
+
from voice_dialogue.utils.logger import logger
|
| 8 |
from .models import ASRInterface
|
| 9 |
|
| 10 |
|
|
|
|
| 20 |
|
| 21 |
def print(self, key: str = None) -> None:
|
| 22 |
"""打印已注册的ASR类"""
|
| 23 |
+
logger.info("\nASR Registry Tables: \n")
|
| 24 |
headers = ["register name", "class name", "class location", "supported languages"]
|
| 25 |
|
| 26 |
if self.asr_classes and (key is None or "asr_classes" in key):
|
| 27 |
+
logger.info(f"----------- ** asr_classes ** --------------")
|
| 28 |
metas = []
|
| 29 |
for register_key, asr_class in self.asr_classes.items():
|
| 30 |
class_file = inspect.getfile(asr_class)
|
|
|
|
| 53 |
col_widths = [max(len(str(item)) for item in col) for col in zip(*data)]
|
| 54 |
|
| 55 |
for row in data:
|
| 56 |
+
logger.info(
|
| 57 |
"| "
|
| 58 |
+ " | ".join(str(item).ljust(width) for item, width in zip(row, col_widths))
|
| 59 |
+ " |"
|
| 60 |
)
|
| 61 |
+
logger.info("\n")
|
| 62 |
|
| 63 |
def register(self, register_table_key: str, key: str = None) -> callable:
|
| 64 |
"""装饰器,用于注册ASR类"""
|
|
|
|
| 66 |
def decorator(target_class):
|
| 67 |
if not hasattr(self, register_table_key):
|
| 68 |
setattr(self, register_table_key, {})
|
| 69 |
+
logger.debug(f"New ASR registry table added: {register_table_key}")
|
| 70 |
|
| 71 |
registry = getattr(self, register_table_key)
|
| 72 |
registry_key = key if key is not None else target_class.__name__
|
| 73 |
|
| 74 |
if registry_key in registry:
|
| 75 |
+
logger.debug(
|
| 76 |
f"Key {registry_key} already exists in {register_table_key}, re-register"
|
| 77 |
)
|
| 78 |
|
| 79 |
registry[registry_key] = target_class
|
| 80 |
+
logger.info(f"Registered ASR class: {registry_key} -> {target_class.__name__}")
|
| 81 |
return target_class
|
| 82 |
|
| 83 |
return decorator
|
|
|
|
| 121 |
asr_class = asr_tables.asr_classes[asr_type]
|
| 122 |
instance = asr_class()
|
| 123 |
|
| 124 |
+
logger.info(f"成功创建ASR实例: {asr_type} for language: {language}")
|
| 125 |
return instance
|
| 126 |
|
| 127 |
except Exception as e:
|
| 128 |
+
logger.error(f"创建ASR实例失败: {e}")
|
| 129 |
raise
|
| 130 |
|
| 131 |
def get_or_create_asr(self, language: Literal['auto', 'zh', 'en']) -> ASRInterface:
|
|
|
|
| 165 |
raise ValueError(f"ASR类型 '{asr_type}' 未注册")
|
| 166 |
|
| 167 |
self._language_to_asr_mapping[language] = asr_type
|
| 168 |
+
logger.info(f"更新语言映射: {language} -> {asr_type}")
|
| 169 |
|
| 170 |
def list_registered_asr(self) -> Dict[str, Type[ASRInterface]]:
|
| 171 |
"""列出所有已注册的ASR类型"""
|
|
|
|
| 190 |
# languages = asr_tables._get_asr_supported_languages(asr_key)
|
| 191 |
# supported_languages[asr_key] = languages
|
| 192 |
except Exception as e:
|
| 193 |
+
logger.warning(f"获取ASR引擎 '{asr_key}' 支持的语言失败: {e}")
|
| 194 |
supported_languages[asr_key] = ['unknown']
|
| 195 |
|
| 196 |
return supported_languages
|
|
|
|
| 251 |
|
| 252 |
def cleanup(self) -> None:
|
| 253 |
"""清理所有ASR实例"""
|
| 254 |
+
logger.info("清理ASR实例...")
|
| 255 |
self._asr_instances.clear()
|
| 256 |
+
logger.info("ASR实例清理完成")
|
| 257 |
|
| 258 |
def print_registry(self) -> None:
|
| 259 |
"""打印注册表信息"""
|
|
|
|
| 301 |
)
|
| 302 |
module = importlib.util.module_from_spec(spec)
|
| 303 |
spec.loader.exec_module(module)
|
| 304 |
+
logger.info(f"Successfully imported ASR module: {module_name}")
|
| 305 |
except ImportError as e:
|
| 306 |
+
logger.warning(f"Failed to import ASR module {module_name}: {e}")
|
| 307 |
except Exception as e:
|
| 308 |
+
logger.error(f"Unexpected error importing ASR module {module_name}: {e}")
|
| 309 |
|
| 310 |
|
| 311 |
# 在模块导入时自动注册所有ASR
|
src/voice_dialogue/services/speech/recognizers/models/__init__.py
CHANGED
|
@@ -7,15 +7,15 @@ try:
|
|
| 7 |
|
| 8 |
__all__.append('FunASRClient')
|
| 9 |
except ImportError as e:
|
| 10 |
-
import
|
| 11 |
|
| 12 |
-
|
| 13 |
|
| 14 |
try:
|
| 15 |
from .whisper import WhisperCppClient
|
| 16 |
|
| 17 |
__all__.append('WhisperCppClient')
|
| 18 |
except ImportError as e:
|
| 19 |
-
import
|
| 20 |
|
| 21 |
-
|
|
|
|
| 7 |
|
| 8 |
__all__.append('FunASRClient')
|
| 9 |
except ImportError as e:
|
| 10 |
+
from voice_dialogue.utils.logger import logger
|
| 11 |
|
| 12 |
+
logger.warning(f"Failed to import some FunASR implementations: {e}")
|
| 13 |
|
| 14 |
try:
|
| 15 |
from .whisper import WhisperCppClient
|
| 16 |
|
| 17 |
__all__.append('WhisperCppClient')
|
| 18 |
except ImportError as e:
|
| 19 |
+
from voice_dialogue.utils.logger import logger
|
| 20 |
|
| 21 |
+
logger.warning(f"Failed to import some Whisper implementations: {e}")
|
src/voice_dialogue/services/speech/recognizers/models/funasr.py
CHANGED
|
@@ -8,6 +8,7 @@ from voice_dialogue.config import paths
|
|
| 8 |
from voice_dialogue.services.speech.recognizers.manager import asr_tables
|
| 9 |
from voice_dialogue.services.speech.recognizers.models.base import ASRInterface
|
| 10 |
from voice_dialogue.services.speech.recognizers.utils import ensure_minimum_audio_duration
|
|
|
|
| 11 |
|
| 12 |
|
| 13 |
@asr_tables.register('asr_classes', 'funasr')
|
|
@@ -29,12 +30,12 @@ class FunASRClient(ASRInterface):
|
|
| 29 |
self.punc_model = CT_Transformer(punc_model_path, quantize=True)
|
| 30 |
|
| 31 |
def warmup(self) -> None:
|
| 32 |
-
|
| 33 |
try:
|
| 34 |
self.transcribe(self.warmup_audiodata)
|
| 35 |
-
|
| 36 |
except Exception as e:
|
| 37 |
-
|
| 38 |
|
| 39 |
def _fix_spaced_uppercase(self, text: str) -> str:
|
| 40 |
"""
|
|
@@ -60,7 +61,7 @@ class FunASRClient(ASRInterface):
|
|
| 60 |
try:
|
| 61 |
content, _ = self.punc_model(content)
|
| 62 |
except UnboundLocalError as e:
|
| 63 |
-
|
| 64 |
content = self._fix_spaced_uppercase(content)
|
| 65 |
transcibed_texts.append(content)
|
| 66 |
return " ".join(transcibed_texts)
|
|
|
|
| 8 |
from voice_dialogue.services.speech.recognizers.manager import asr_tables
|
| 9 |
from voice_dialogue.services.speech.recognizers.models.base import ASRInterface
|
| 10 |
from voice_dialogue.services.speech.recognizers.utils import ensure_minimum_audio_duration
|
| 11 |
+
from voice_dialogue.utils.logger import logger
|
| 12 |
|
| 13 |
|
| 14 |
@asr_tables.register('asr_classes', 'funasr')
|
|
|
|
| 30 |
self.punc_model = CT_Transformer(punc_model_path, quantize=True)
|
| 31 |
|
| 32 |
def warmup(self) -> None:
|
| 33 |
+
logger.info('[INFO] Warming up FunASR model...')
|
| 34 |
try:
|
| 35 |
self.transcribe(self.warmup_audiodata)
|
| 36 |
+
logger.info('[INFO] FunASR model warmed up.')
|
| 37 |
except Exception as e:
|
| 38 |
+
logger.warning(f'[WARNING] FunASR model warmup failed: {e}')
|
| 39 |
|
| 40 |
def _fix_spaced_uppercase(self, text: str) -> str:
|
| 41 |
"""
|
|
|
|
| 61 |
try:
|
| 62 |
content, _ = self.punc_model(content)
|
| 63 |
except UnboundLocalError as e:
|
| 64 |
+
logger.warning(f'[WARNING] Punctuation model failed: {e}')
|
| 65 |
content = self._fix_spaced_uppercase(content)
|
| 66 |
transcibed_texts.append(content)
|
| 67 |
return " ".join(transcibed_texts)
|
src/voice_dialogue/services/speech/recognizers/models/whisper.py
CHANGED
|
@@ -7,6 +7,7 @@ from voice_dialogue.config import paths
|
|
| 7 |
from voice_dialogue.services.speech.recognizers.manager import asr_tables
|
| 8 |
from voice_dialogue.services.speech.recognizers.models.base import ASRInterface
|
| 9 |
from voice_dialogue.services.speech.recognizers.utils import ensure_minimum_audio_duration
|
|
|
|
| 10 |
|
| 11 |
|
| 12 |
@asr_tables.register('asr_classes', 'whisper')
|
|
@@ -30,12 +31,12 @@ class WhisperCppClient(ASRInterface):
|
|
| 30 |
self.whisper = Model(model=model, models_dir=models_dir)
|
| 31 |
|
| 32 |
def warmup(self) -> None:
|
| 33 |
-
|
| 34 |
try:
|
| 35 |
self.transcribe(self.warmup_audiodata)
|
| 36 |
-
|
| 37 |
except Exception as e:
|
| 38 |
-
|
| 39 |
|
| 40 |
def transcribe(self, audio_array: np.ndarray, language="en"):
|
| 41 |
if language == "zh":
|
|
|
|
| 7 |
from voice_dialogue.services.speech.recognizers.manager import asr_tables
|
| 8 |
from voice_dialogue.services.speech.recognizers.models.base import ASRInterface
|
| 9 |
from voice_dialogue.services.speech.recognizers.utils import ensure_minimum_audio_duration
|
| 10 |
+
from voice_dialogue.utils.logger import logger
|
| 11 |
|
| 12 |
|
| 13 |
@asr_tables.register('asr_classes', 'whisper')
|
|
|
|
| 31 |
self.whisper = Model(model=model, models_dir=models_dir)
|
| 32 |
|
| 33 |
def warmup(self) -> None:
|
| 34 |
+
logger.info('[INFO] Warming up Whisper model...')
|
| 35 |
try:
|
| 36 |
self.transcribe(self.warmup_audiodata)
|
| 37 |
+
logger.info('[INFO] Whisper model warmed up.')
|
| 38 |
except Exception as e:
|
| 39 |
+
logger.warning(f'[WARNING] Whisper model warmup failed: {e}')
|
| 40 |
|
| 41 |
def transcribe(self, audio_array: np.ndarray, language="en"):
|
| 42 |
if language == "zh":
|
src/voice_dialogue/services/text/generator.py
CHANGED
|
@@ -12,6 +12,7 @@ from voice_dialogue.core.constants import chat_history_cache
|
|
| 12 |
from voice_dialogue.models.voice_task import VoiceTask, QuestionDisplayMessage
|
| 13 |
from voice_dialogue.services.text.processor import preprocess_sentence_text, \
|
| 14 |
create_langchain_chat_llamacpp_instance, create_langchain_pipeline, warmup_langchain_pipeline
|
|
|
|
| 15 |
|
| 16 |
CHINESE_SYSTEM_PROMPT = (
|
| 17 |
"你是善于模拟真实的思考过程的AI助手。"
|
|
@@ -129,7 +130,7 @@ class LLMResponseGenerator(BaseThread):
|
|
| 129 |
is_first_sentence = True
|
| 130 |
|
| 131 |
user_question = voice_task.transcribed_text
|
| 132 |
-
|
| 133 |
if self.websocket_message_queue:
|
| 134 |
self.websocket_message_queue.put_nowait(
|
| 135 |
QuestionDisplayMessage(
|
|
@@ -177,7 +178,7 @@ class LLMResponseGenerator(BaseThread):
|
|
| 177 |
self._handle_remaining_chunks(voice_task, chunks, answer_index)
|
| 178 |
|
| 179 |
except Exception as e:
|
| 180 |
-
|
| 181 |
|
| 182 |
def _handle_remaining_chunks(self, voice_task: VoiceTask, chunks: list, answer_index: int) -> None:
|
| 183 |
"""处理剩余的 chunks"""
|
|
@@ -197,11 +198,11 @@ class LLMResponseGenerator(BaseThread):
|
|
| 197 |
|
| 198 |
# 打印芯片信息和优化配置
|
| 199 |
chip_summary = get_apple_silicon_summary()
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
|
| 206 |
self.model_instance = create_langchain_chat_llamacpp_instance(
|
| 207 |
local_model_path=model_path, model_params=model_params
|
|
@@ -219,4 +220,4 @@ class LLMResponseGenerator(BaseThread):
|
|
| 219 |
except Empty:
|
| 220 |
continue
|
| 221 |
except Exception as e:
|
| 222 |
-
|
|
|
|
| 12 |
from voice_dialogue.models.voice_task import VoiceTask, QuestionDisplayMessage
|
| 13 |
from voice_dialogue.services.text.processor import preprocess_sentence_text, \
|
| 14 |
create_langchain_chat_llamacpp_instance, create_langchain_pipeline, warmup_langchain_pipeline
|
| 15 |
+
from voice_dialogue.utils.logger import logger
|
| 16 |
|
| 17 |
CHINESE_SYSTEM_PROMPT = (
|
| 18 |
"你是善于模拟真实的思考过程的AI助手。"
|
|
|
|
| 130 |
is_first_sentence = True
|
| 131 |
|
| 132 |
user_question = voice_task.transcribed_text
|
| 133 |
+
logger.info(f'用户问题: {user_question}')
|
| 134 |
if self.websocket_message_queue:
|
| 135 |
self.websocket_message_queue.put_nowait(
|
| 136 |
QuestionDisplayMessage(
|
|
|
|
| 178 |
self._handle_remaining_chunks(voice_task, chunks, answer_index)
|
| 179 |
|
| 180 |
except Exception as e:
|
| 181 |
+
logger.error(f'处理语音任务时发生错误: {e}')
|
| 182 |
|
| 183 |
def _handle_remaining_chunks(self, voice_task: VoiceTask, chunks: list, answer_index: int) -> None:
|
| 184 |
"""处理剩余的 chunks"""
|
|
|
|
| 198 |
|
| 199 |
# 打印芯片信息和优化配置
|
| 200 |
chip_summary = get_apple_silicon_summary()
|
| 201 |
+
logger.info(f"检测到芯片: {chip_summary['chip_name']}")
|
| 202 |
+
logger.info(f"性能核心数: {chip_summary['performance_cores']}")
|
| 203 |
+
logger.info(f"使用线程数: {chip_summary['optimal_n_threads']} (仅使用性能核心)")
|
| 204 |
+
logger.info(f"上下文窗口: {chip_summary['optimal_n_ctx']}")
|
| 205 |
+
logger.info(f"配置说明: {chip_summary['config_note']}")
|
| 206 |
|
| 207 |
self.model_instance = create_langchain_chat_llamacpp_instance(
|
| 208 |
local_model_path=model_path, model_params=model_params
|
|
|
|
| 220 |
except Empty:
|
| 221 |
continue
|
| 222 |
except Exception as e:
|
| 223 |
+
logger.error(f'AnswerGeneratorWorker 运行时发生错误: {e}')
|
src/voice_dialogue/services/text/processor.py
CHANGED
|
@@ -9,6 +9,7 @@ from langchain_core.prompts import (
|
|
| 9 |
)
|
| 10 |
from langchain_core.runnables import RunnableWithMessageHistory
|
| 11 |
|
|
|
|
| 12 |
from voice_dialogue.utils.strings import (
|
| 13 |
remove_emojis, convert_comma_separated_numbers, convert_uppercase_words_to_lowercase
|
| 14 |
)
|
|
@@ -18,7 +19,7 @@ def create_langchain_chat_llamacpp_instance(
|
|
| 18 |
local_model_path: str,
|
| 19 |
model_params: dict | None = None
|
| 20 |
) -> ChatLlamaCpp:
|
| 21 |
-
|
| 22 |
|
| 23 |
model_path = pathlib.Path(local_model_path)
|
| 24 |
callback_manager = CallbackManager([StreamingStdOutCallbackHandler()])
|
|
@@ -55,7 +56,7 @@ def create_langchain_pipeline(langchain_instance, system_prompt: str, get_sessio
|
|
| 55 |
|
| 56 |
|
| 57 |
def warmup_langchain_pipeline(pipeline):
|
| 58 |
-
|
| 59 |
|
| 60 |
user_input = 'Hello, this is warming up step, if you understand, output "Ok".'
|
| 61 |
config = {"configurable": {"session_id": 'warmup'}}
|
|
|
|
| 9 |
)
|
| 10 |
from langchain_core.runnables import RunnableWithMessageHistory
|
| 11 |
|
| 12 |
+
from voice_dialogue.utils.logger import logger
|
| 13 |
from voice_dialogue.utils.strings import (
|
| 14 |
remove_emojis, convert_comma_separated_numbers, convert_uppercase_words_to_lowercase
|
| 15 |
)
|
|
|
|
| 19 |
local_model_path: str,
|
| 20 |
model_params: dict | None = None
|
| 21 |
) -> ChatLlamaCpp:
|
| 22 |
+
logger.info(">>>>>>> Initializing LlamaCpp Langchain instance...")
|
| 23 |
|
| 24 |
model_path = pathlib.Path(local_model_path)
|
| 25 |
callback_manager = CallbackManager([StreamingStdOutCallbackHandler()])
|
|
|
|
| 56 |
|
| 57 |
|
| 58 |
def warmup_langchain_pipeline(pipeline):
|
| 59 |
+
logger.info("Warmup chat pipeline...")
|
| 60 |
|
| 61 |
user_input = 'Hello, this is warming up step, if you understand, output "Ok".'
|
| 62 |
config = {"configurable": {"session_id": 'warmup'}}
|
src/voice_dialogue/utils/__init__.py
CHANGED
|
@@ -4,6 +4,7 @@ from .download_utils import (
|
|
| 4 |
download_model_from_huggingface, download_file_from_huggingface, check_file_exists_on_huggingface,
|
| 5 |
download_lora_from_huggingface, download_civitai_file
|
| 6 |
)
|
|
|
|
| 7 |
from .strings import remove_emojis
|
| 8 |
from .system import get_system_language, get_system_info
|
| 9 |
|
|
@@ -63,4 +64,5 @@ __all__ = (
|
|
| 63 |
'LRUCacheDict',
|
| 64 |
'get_system_language',
|
| 65 |
'get_system_info',
|
|
|
|
| 66 |
)
|
|
|
|
| 4 |
download_model_from_huggingface, download_file_from_huggingface, check_file_exists_on_huggingface,
|
| 5 |
download_lora_from_huggingface, download_civitai_file
|
| 6 |
)
|
| 7 |
+
from .logger import logger
|
| 8 |
from .strings import remove_emojis
|
| 9 |
from .system import get_system_language, get_system_info
|
| 10 |
|
|
|
|
| 64 |
'LRUCacheDict',
|
| 65 |
'get_system_language',
|
| 66 |
'get_system_info',
|
| 67 |
+
'logger',
|
| 68 |
)
|
src/voice_dialogue/utils/logger.py
CHANGED
|
@@ -1,81 +1,37 @@
|
|
| 1 |
-
import logging
|
| 2 |
import sys
|
| 3 |
-
|
| 4 |
-
from
|
|
|
|
|
|
|
| 5 |
|
| 6 |
|
| 7 |
def setup_logger(
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
log_format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
| 12 |
-
max_bytes: int = 5_242_880, # 5MB
|
| 13 |
-
backup_count: int = 3
|
| 14 |
-
) -> logging.Logger:
|
| 15 |
"""
|
| 16 |
-
Configure and return a logger with
|
| 17 |
|
| 18 |
Args:
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
level: Logging level
|
| 22 |
-
log_format: Format string for log messages
|
| 23 |
-
max_bytes: Maximum size of log file before rotation
|
| 24 |
-
backup_count: Number of backup files to keep
|
| 25 |
|
| 26 |
Returns:
|
| 27 |
-
|
| 28 |
"""
|
| 29 |
-
#
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
# Create and configure file handler with rotation
|
| 41 |
-
file_handler = RotatingFileHandler(
|
| 42 |
-
log_file,
|
| 43 |
-
maxBytes=max_bytes,
|
| 44 |
-
backupCount=backup_count,
|
| 45 |
-
encoding='utf-8'
|
| 46 |
)
|
| 47 |
-
file_handler.setFormatter(formatter)
|
| 48 |
-
file_handler.setLevel(level)
|
| 49 |
|
| 50 |
-
|
| 51 |
-
console_handler = logging.StreamHandler(sys.stdout)
|
| 52 |
-
console_handler.setFormatter(formatter)
|
| 53 |
-
console_handler.setLevel(level)
|
| 54 |
|
| 55 |
-
# Add handlers to logger if they haven't been added already
|
| 56 |
-
if not logger.handlers:
|
| 57 |
-
logger.addHandler(file_handler)
|
| 58 |
-
logger.addHandler(console_handler)
|
| 59 |
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
# Example usage
|
| 64 |
-
if __name__ == "__main__":
|
| 65 |
-
# Basic setup
|
| 66 |
-
logger = setup_logger()
|
| 67 |
-
logger.info("Basic logger initialized")
|
| 68 |
-
|
| 69 |
-
# Custom setup example
|
| 70 |
-
custom_logger = setup_logger(
|
| 71 |
-
logger_name="custom_app",
|
| 72 |
-
log_file="logs/custom.log",
|
| 73 |
-
level=logging.DEBUG,
|
| 74 |
-
log_format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
| 75 |
-
max_bytes=1_048_576, # 1MB
|
| 76 |
-
backup_count=5
|
| 77 |
-
)
|
| 78 |
-
custom_logger.debug("Custom logger initialized")
|
| 79 |
-
custom_logger.info("This is an info message")
|
| 80 |
-
custom_logger.warning("This is a warning message")
|
| 81 |
-
custom_logger.error("This is an error message")
|
|
|
|
|
|
|
| 1 |
import sys
|
| 2 |
+
|
| 3 |
+
from loguru import logger as _logger
|
| 4 |
+
|
| 5 |
+
__all__ = ("logger",)
|
| 6 |
|
| 7 |
|
| 8 |
def setup_logger(
|
| 9 |
+
level: str = "INFO",
|
| 10 |
+
log_format: str = "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
|
| 11 |
+
) -> object:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
"""
|
| 13 |
+
Configure and return a loguru logger with console output only.
|
| 14 |
|
| 15 |
Args:
|
| 16 |
+
level: 日志级别 (INFO, DEBUG, WARNING, ERROR, CRITICAL)
|
| 17 |
+
log_format: 日志格式字符串
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
Returns:
|
| 20 |
+
loguru.logger: 配置好的logger实例
|
| 21 |
"""
|
| 22 |
+
# 移除所有现有的处理器
|
| 23 |
+
_logger.remove()
|
| 24 |
+
|
| 25 |
+
# 添加控制台处理器
|
| 26 |
+
_logger.add(
|
| 27 |
+
sys.stdout,
|
| 28 |
+
level=level.upper(),
|
| 29 |
+
format=log_format,
|
| 30 |
+
colorize=True,
|
| 31 |
+
enqueue=True # 使日志线程安全
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
)
|
|
|
|
|
|
|
| 33 |
|
| 34 |
+
return _logger
|
|
|
|
|
|
|
|
|
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
+
logger = setup_logger()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|