realtimex-deeptutor 0.5.0.post1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- realtimex_deeptutor/__init__.py +67 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/METADATA +1612 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/RECORD +276 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/WHEEL +5 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/entry_points.txt +2 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/licenses/LICENSE +661 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/top_level.txt +2 -0
- src/__init__.py +40 -0
- src/agents/__init__.py +24 -0
- src/agents/base_agent.py +657 -0
- src/agents/chat/__init__.py +24 -0
- src/agents/chat/chat_agent.py +435 -0
- src/agents/chat/prompts/en/chat_agent.yaml +35 -0
- src/agents/chat/prompts/zh/chat_agent.yaml +35 -0
- src/agents/chat/session_manager.py +311 -0
- src/agents/co_writer/__init__.py +0 -0
- src/agents/co_writer/edit_agent.py +260 -0
- src/agents/co_writer/narrator_agent.py +423 -0
- src/agents/co_writer/prompts/en/edit_agent.yaml +113 -0
- src/agents/co_writer/prompts/en/narrator_agent.yaml +88 -0
- src/agents/co_writer/prompts/zh/edit_agent.yaml +113 -0
- src/agents/co_writer/prompts/zh/narrator_agent.yaml +88 -0
- src/agents/guide/__init__.py +16 -0
- src/agents/guide/agents/__init__.py +11 -0
- src/agents/guide/agents/chat_agent.py +104 -0
- src/agents/guide/agents/interactive_agent.py +223 -0
- src/agents/guide/agents/locate_agent.py +149 -0
- src/agents/guide/agents/summary_agent.py +150 -0
- src/agents/guide/guide_manager.py +500 -0
- src/agents/guide/prompts/en/chat_agent.yaml +41 -0
- src/agents/guide/prompts/en/interactive_agent.yaml +202 -0
- src/agents/guide/prompts/en/locate_agent.yaml +68 -0
- src/agents/guide/prompts/en/summary_agent.yaml +157 -0
- src/agents/guide/prompts/zh/chat_agent.yaml +41 -0
- src/agents/guide/prompts/zh/interactive_agent.yaml +626 -0
- src/agents/guide/prompts/zh/locate_agent.yaml +68 -0
- src/agents/guide/prompts/zh/summary_agent.yaml +157 -0
- src/agents/ideagen/__init__.py +12 -0
- src/agents/ideagen/idea_generation_workflow.py +426 -0
- src/agents/ideagen/material_organizer_agent.py +173 -0
- src/agents/ideagen/prompts/en/idea_generation.yaml +187 -0
- src/agents/ideagen/prompts/en/material_organizer.yaml +69 -0
- src/agents/ideagen/prompts/zh/idea_generation.yaml +187 -0
- src/agents/ideagen/prompts/zh/material_organizer.yaml +69 -0
- src/agents/question/__init__.py +24 -0
- src/agents/question/agents/__init__.py +18 -0
- src/agents/question/agents/generate_agent.py +381 -0
- src/agents/question/agents/relevance_analyzer.py +207 -0
- src/agents/question/agents/retrieve_agent.py +239 -0
- src/agents/question/coordinator.py +718 -0
- src/agents/question/example.py +109 -0
- src/agents/question/prompts/en/coordinator.yaml +75 -0
- src/agents/question/prompts/en/generate_agent.yaml +77 -0
- src/agents/question/prompts/en/relevance_analyzer.yaml +41 -0
- src/agents/question/prompts/en/retrieve_agent.yaml +32 -0
- src/agents/question/prompts/zh/coordinator.yaml +75 -0
- src/agents/question/prompts/zh/generate_agent.yaml +77 -0
- src/agents/question/prompts/zh/relevance_analyzer.yaml +39 -0
- src/agents/question/prompts/zh/retrieve_agent.yaml +30 -0
- src/agents/research/agents/__init__.py +23 -0
- src/agents/research/agents/decompose_agent.py +507 -0
- src/agents/research/agents/manager_agent.py +228 -0
- src/agents/research/agents/note_agent.py +180 -0
- src/agents/research/agents/rephrase_agent.py +263 -0
- src/agents/research/agents/reporting_agent.py +1333 -0
- src/agents/research/agents/research_agent.py +714 -0
- src/agents/research/data_structures.py +451 -0
- src/agents/research/main.py +188 -0
- src/agents/research/prompts/en/decompose_agent.yaml +89 -0
- src/agents/research/prompts/en/manager_agent.yaml +24 -0
- src/agents/research/prompts/en/note_agent.yaml +121 -0
- src/agents/research/prompts/en/rephrase_agent.yaml +58 -0
- src/agents/research/prompts/en/reporting_agent.yaml +380 -0
- src/agents/research/prompts/en/research_agent.yaml +173 -0
- src/agents/research/prompts/zh/decompose_agent.yaml +89 -0
- src/agents/research/prompts/zh/manager_agent.yaml +24 -0
- src/agents/research/prompts/zh/note_agent.yaml +121 -0
- src/agents/research/prompts/zh/rephrase_agent.yaml +58 -0
- src/agents/research/prompts/zh/reporting_agent.yaml +380 -0
- src/agents/research/prompts/zh/research_agent.yaml +173 -0
- src/agents/research/research_pipeline.py +1309 -0
- src/agents/research/utils/__init__.py +60 -0
- src/agents/research/utils/citation_manager.py +799 -0
- src/agents/research/utils/json_utils.py +98 -0
- src/agents/research/utils/token_tracker.py +297 -0
- src/agents/solve/__init__.py +80 -0
- src/agents/solve/analysis_loop/__init__.py +14 -0
- src/agents/solve/analysis_loop/investigate_agent.py +414 -0
- src/agents/solve/analysis_loop/note_agent.py +190 -0
- src/agents/solve/main_solver.py +862 -0
- src/agents/solve/memory/__init__.py +34 -0
- src/agents/solve/memory/citation_memory.py +353 -0
- src/agents/solve/memory/investigate_memory.py +226 -0
- src/agents/solve/memory/solve_memory.py +340 -0
- src/agents/solve/prompts/en/analysis_loop/investigate_agent.yaml +55 -0
- src/agents/solve/prompts/en/analysis_loop/note_agent.yaml +54 -0
- src/agents/solve/prompts/en/solve_loop/manager_agent.yaml +67 -0
- src/agents/solve/prompts/en/solve_loop/precision_answer_agent.yaml +62 -0
- src/agents/solve/prompts/en/solve_loop/response_agent.yaml +90 -0
- src/agents/solve/prompts/en/solve_loop/solve_agent.yaml +75 -0
- src/agents/solve/prompts/en/solve_loop/tool_agent.yaml +38 -0
- src/agents/solve/prompts/zh/analysis_loop/investigate_agent.yaml +53 -0
- src/agents/solve/prompts/zh/analysis_loop/note_agent.yaml +54 -0
- src/agents/solve/prompts/zh/solve_loop/manager_agent.yaml +66 -0
- src/agents/solve/prompts/zh/solve_loop/precision_answer_agent.yaml +62 -0
- src/agents/solve/prompts/zh/solve_loop/response_agent.yaml +90 -0
- src/agents/solve/prompts/zh/solve_loop/solve_agent.yaml +76 -0
- src/agents/solve/prompts/zh/solve_loop/tool_agent.yaml +41 -0
- src/agents/solve/solve_loop/__init__.py +22 -0
- src/agents/solve/solve_loop/citation_manager.py +74 -0
- src/agents/solve/solve_loop/manager_agent.py +274 -0
- src/agents/solve/solve_loop/precision_answer_agent.py +96 -0
- src/agents/solve/solve_loop/response_agent.py +301 -0
- src/agents/solve/solve_loop/solve_agent.py +325 -0
- src/agents/solve/solve_loop/tool_agent.py +470 -0
- src/agents/solve/utils/__init__.py +64 -0
- src/agents/solve/utils/config_validator.py +313 -0
- src/agents/solve/utils/display_manager.py +223 -0
- src/agents/solve/utils/error_handler.py +363 -0
- src/agents/solve/utils/json_utils.py +98 -0
- src/agents/solve/utils/performance_monitor.py +407 -0
- src/agents/solve/utils/token_tracker.py +541 -0
- src/api/__init__.py +0 -0
- src/api/main.py +240 -0
- src/api/routers/__init__.py +1 -0
- src/api/routers/agent_config.py +69 -0
- src/api/routers/chat.py +296 -0
- src/api/routers/co_writer.py +337 -0
- src/api/routers/config.py +627 -0
- src/api/routers/dashboard.py +18 -0
- src/api/routers/guide.py +337 -0
- src/api/routers/ideagen.py +436 -0
- src/api/routers/knowledge.py +821 -0
- src/api/routers/notebook.py +247 -0
- src/api/routers/question.py +537 -0
- src/api/routers/research.py +394 -0
- src/api/routers/settings.py +164 -0
- src/api/routers/solve.py +305 -0
- src/api/routers/system.py +252 -0
- src/api/run_server.py +61 -0
- src/api/utils/history.py +172 -0
- src/api/utils/log_interceptor.py +21 -0
- src/api/utils/notebook_manager.py +415 -0
- src/api/utils/progress_broadcaster.py +72 -0
- src/api/utils/task_id_manager.py +100 -0
- src/config/__init__.py +0 -0
- src/config/accessors.py +18 -0
- src/config/constants.py +34 -0
- src/config/defaults.py +18 -0
- src/config/schema.py +38 -0
- src/config/settings.py +50 -0
- src/core/errors.py +62 -0
- src/knowledge/__init__.py +23 -0
- src/knowledge/add_documents.py +606 -0
- src/knowledge/config.py +65 -0
- src/knowledge/example_add_documents.py +236 -0
- src/knowledge/extract_numbered_items.py +1039 -0
- src/knowledge/initializer.py +621 -0
- src/knowledge/kb.py +22 -0
- src/knowledge/manager.py +782 -0
- src/knowledge/progress_tracker.py +182 -0
- src/knowledge/start_kb.py +535 -0
- src/logging/__init__.py +103 -0
- src/logging/adapters/__init__.py +17 -0
- src/logging/adapters/lightrag.py +184 -0
- src/logging/adapters/llamaindex.py +141 -0
- src/logging/config.py +80 -0
- src/logging/handlers/__init__.py +20 -0
- src/logging/handlers/console.py +75 -0
- src/logging/handlers/file.py +201 -0
- src/logging/handlers/websocket.py +127 -0
- src/logging/logger.py +709 -0
- src/logging/stats/__init__.py +16 -0
- src/logging/stats/llm_stats.py +179 -0
- src/services/__init__.py +56 -0
- src/services/config/__init__.py +61 -0
- src/services/config/knowledge_base_config.py +210 -0
- src/services/config/loader.py +260 -0
- src/services/config/unified_config.py +603 -0
- src/services/embedding/__init__.py +45 -0
- src/services/embedding/adapters/__init__.py +22 -0
- src/services/embedding/adapters/base.py +106 -0
- src/services/embedding/adapters/cohere.py +127 -0
- src/services/embedding/adapters/jina.py +99 -0
- src/services/embedding/adapters/ollama.py +116 -0
- src/services/embedding/adapters/openai_compatible.py +96 -0
- src/services/embedding/client.py +159 -0
- src/services/embedding/config.py +156 -0
- src/services/embedding/provider.py +119 -0
- src/services/llm/__init__.py +152 -0
- src/services/llm/capabilities.py +313 -0
- src/services/llm/client.py +302 -0
- src/services/llm/cloud_provider.py +530 -0
- src/services/llm/config.py +200 -0
- src/services/llm/error_mapping.py +103 -0
- src/services/llm/exceptions.py +152 -0
- src/services/llm/factory.py +450 -0
- src/services/llm/local_provider.py +347 -0
- src/services/llm/providers/anthropic.py +95 -0
- src/services/llm/providers/base_provider.py +93 -0
- src/services/llm/providers/open_ai.py +83 -0
- src/services/llm/registry.py +71 -0
- src/services/llm/telemetry.py +40 -0
- src/services/llm/types.py +27 -0
- src/services/llm/utils.py +333 -0
- src/services/prompt/__init__.py +25 -0
- src/services/prompt/manager.py +206 -0
- src/services/rag/__init__.py +64 -0
- src/services/rag/components/__init__.py +29 -0
- src/services/rag/components/base.py +59 -0
- src/services/rag/components/chunkers/__init__.py +18 -0
- src/services/rag/components/chunkers/base.py +34 -0
- src/services/rag/components/chunkers/fixed.py +71 -0
- src/services/rag/components/chunkers/numbered_item.py +94 -0
- src/services/rag/components/chunkers/semantic.py +97 -0
- src/services/rag/components/embedders/__init__.py +14 -0
- src/services/rag/components/embedders/base.py +32 -0
- src/services/rag/components/embedders/openai.py +63 -0
- src/services/rag/components/indexers/__init__.py +18 -0
- src/services/rag/components/indexers/base.py +35 -0
- src/services/rag/components/indexers/graph.py +172 -0
- src/services/rag/components/indexers/lightrag.py +156 -0
- src/services/rag/components/indexers/vector.py +146 -0
- src/services/rag/components/parsers/__init__.py +18 -0
- src/services/rag/components/parsers/base.py +35 -0
- src/services/rag/components/parsers/markdown.py +52 -0
- src/services/rag/components/parsers/pdf.py +115 -0
- src/services/rag/components/parsers/text.py +86 -0
- src/services/rag/components/retrievers/__init__.py +18 -0
- src/services/rag/components/retrievers/base.py +34 -0
- src/services/rag/components/retrievers/dense.py +200 -0
- src/services/rag/components/retrievers/hybrid.py +164 -0
- src/services/rag/components/retrievers/lightrag.py +169 -0
- src/services/rag/components/routing.py +286 -0
- src/services/rag/factory.py +234 -0
- src/services/rag/pipeline.py +215 -0
- src/services/rag/pipelines/__init__.py +32 -0
- src/services/rag/pipelines/academic.py +44 -0
- src/services/rag/pipelines/lightrag.py +43 -0
- src/services/rag/pipelines/llamaindex.py +313 -0
- src/services/rag/pipelines/raganything.py +384 -0
- src/services/rag/service.py +244 -0
- src/services/rag/types.py +73 -0
- src/services/search/__init__.py +284 -0
- src/services/search/base.py +87 -0
- src/services/search/consolidation.py +398 -0
- src/services/search/providers/__init__.py +128 -0
- src/services/search/providers/baidu.py +188 -0
- src/services/search/providers/exa.py +194 -0
- src/services/search/providers/jina.py +161 -0
- src/services/search/providers/perplexity.py +153 -0
- src/services/search/providers/serper.py +209 -0
- src/services/search/providers/tavily.py +161 -0
- src/services/search/types.py +114 -0
- src/services/setup/__init__.py +34 -0
- src/services/setup/init.py +285 -0
- src/services/tts/__init__.py +16 -0
- src/services/tts/config.py +99 -0
- src/tools/__init__.py +91 -0
- src/tools/code_executor.py +536 -0
- src/tools/paper_search_tool.py +171 -0
- src/tools/query_item_tool.py +310 -0
- src/tools/question/__init__.py +15 -0
- src/tools/question/exam_mimic.py +616 -0
- src/tools/question/pdf_parser.py +211 -0
- src/tools/question/question_extractor.py +397 -0
- src/tools/rag_tool.py +173 -0
- src/tools/tex_chunker.py +339 -0
- src/tools/tex_downloader.py +253 -0
- src/tools/web_search.py +71 -0
- src/utils/config_manager.py +206 -0
- src/utils/document_validator.py +168 -0
- src/utils/error_rate_tracker.py +111 -0
- src/utils/error_utils.py +82 -0
- src/utils/json_parser.py +110 -0
- src/utils/network/circuit_breaker.py +79 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
LightRAG Log Forwarder
|
|
4
|
+
======================
|
|
5
|
+
|
|
6
|
+
Forwards LightRAG and RAG-Anything logs to DeepTutor's unified logging system.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from contextlib import contextmanager
|
|
10
|
+
import logging
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LightRAGLogForwarder(logging.Handler):
|
|
16
|
+
"""
|
|
17
|
+
Handler that forwards LightRAG logger messages to DeepTutor logger.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, ai_tutor_logger, add_prefix: bool = True):
|
|
21
|
+
"""
|
|
22
|
+
Args:
|
|
23
|
+
ai_tutor_logger: DeepTutor Logger instance
|
|
24
|
+
add_prefix: Whether to add [LightRAG] prefix to messages
|
|
25
|
+
"""
|
|
26
|
+
super().__init__()
|
|
27
|
+
self.ai_tutor_logger = ai_tutor_logger
|
|
28
|
+
self.add_prefix = add_prefix
|
|
29
|
+
# Capture all log levels
|
|
30
|
+
self.setLevel(logging.DEBUG)
|
|
31
|
+
|
|
32
|
+
def emit(self, record: logging.LogRecord):
|
|
33
|
+
"""
|
|
34
|
+
Forward log record to DeepTutor logger.
|
|
35
|
+
All logs are forwarded as info level to maintain consistent format.
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
# Get the original message directly without adding [LightRAG] prefix
|
|
39
|
+
# LightRAG already formats messages appropriately (e.g., "DEBUG: xxx" for debug logs)
|
|
40
|
+
message = record.getMessage()
|
|
41
|
+
|
|
42
|
+
# Use info() for all levels to maintain consistent format
|
|
43
|
+
# This ensures all logs appear as [RAGTool] ... (or [RAGTool] DEBUG: ... for debug)
|
|
44
|
+
self.ai_tutor_logger.info(message)
|
|
45
|
+
|
|
46
|
+
except Exception:
|
|
47
|
+
# Avoid errors in forwarding from affecting main flow
|
|
48
|
+
self.handleError(record)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_lightrag_forwarding_config() -> dict:
|
|
52
|
+
"""
|
|
53
|
+
Load LightRAG forwarding configuration from main.yaml.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
dict: Configuration dictionary with defaults if not found
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
from src.services.config import load_config_with_main
|
|
60
|
+
|
|
61
|
+
# Use resolve() to get absolute path, ensuring correct project root regardless of working directory
|
|
62
|
+
project_root = Path(__file__).resolve().parent.parent.parent.parent
|
|
63
|
+
config = load_config_with_main("solve_config.yaml", project_root)
|
|
64
|
+
|
|
65
|
+
forwarding_config = config.get("logging", {}).get("lightrag_forwarding", {})
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
"enabled": forwarding_config.get("enabled", True),
|
|
69
|
+
"min_level": forwarding_config.get("min_level", "INFO"),
|
|
70
|
+
"add_prefix": forwarding_config.get("add_prefix", True),
|
|
71
|
+
"logger_names": forwarding_config.get(
|
|
72
|
+
"logger_names", {"knowledge_init": "KnowledgeInit", "rag_tool": "RAGTool"}
|
|
73
|
+
),
|
|
74
|
+
}
|
|
75
|
+
except Exception:
|
|
76
|
+
# Return defaults if config loading fails
|
|
77
|
+
return {
|
|
78
|
+
"enabled": True,
|
|
79
|
+
"min_level": "INFO",
|
|
80
|
+
"add_prefix": True,
|
|
81
|
+
"logger_names": {"knowledge_init": "KnowledgeInit", "rag_tool": "RAGTool"},
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@contextmanager
|
|
86
|
+
def LightRAGLogContext(logger_name: Optional[str] = None, scene: Optional[str] = None):
|
|
87
|
+
"""
|
|
88
|
+
Context manager for LightRAG log forwarding.
|
|
89
|
+
|
|
90
|
+
Automatically sets up and tears down log forwarding.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
logger_name: Explicit logger name (overrides scene-based lookup)
|
|
94
|
+
scene: Scene name ('knowledge_init' or 'rag_tool') for logger name lookup
|
|
95
|
+
|
|
96
|
+
Usage:
|
|
97
|
+
with LightRAGLogContext("RAGTool"):
|
|
98
|
+
# RAG operations
|
|
99
|
+
rag = RAGAnything(...)
|
|
100
|
+
"""
|
|
101
|
+
from ..logger import get_logger
|
|
102
|
+
|
|
103
|
+
# Get configuration
|
|
104
|
+
config = get_lightrag_forwarding_config()
|
|
105
|
+
|
|
106
|
+
# Check if forwarding is enabled
|
|
107
|
+
if not config.get("enabled", True):
|
|
108
|
+
# If disabled, just pass through without forwarding
|
|
109
|
+
yield
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
# Debug: Log that forwarding is being set up (only if we have a logger)
|
|
113
|
+
# This helps verify the context manager is being called
|
|
114
|
+
try:
|
|
115
|
+
debug_logger = get_logger("RAGForward")
|
|
116
|
+
debug_logger.debug(
|
|
117
|
+
f"Setting up LightRAG log forwarding (scene={scene}, logger_name={logger_name})"
|
|
118
|
+
)
|
|
119
|
+
except:
|
|
120
|
+
pass # Ignore if logger setup fails
|
|
121
|
+
|
|
122
|
+
# Determine logger name
|
|
123
|
+
if logger_name is None:
|
|
124
|
+
if scene:
|
|
125
|
+
logger_names = config.get("logger_names", {})
|
|
126
|
+
logger_name = logger_names.get(scene, "Main")
|
|
127
|
+
else:
|
|
128
|
+
logger_name = "Main"
|
|
129
|
+
|
|
130
|
+
# Get DeepTutor logger
|
|
131
|
+
ai_tutor_logger = get_logger(logger_name)
|
|
132
|
+
|
|
133
|
+
# Get forwarding settings
|
|
134
|
+
add_prefix = config.get("add_prefix", True)
|
|
135
|
+
min_level_str = config.get("min_level", "INFO")
|
|
136
|
+
min_level = getattr(logging, min_level_str.upper(), logging.INFO)
|
|
137
|
+
|
|
138
|
+
# Get LightRAG logger
|
|
139
|
+
lightrag_logger = logging.getLogger("lightrag")
|
|
140
|
+
|
|
141
|
+
# Store original handlers and level to restore later if needed
|
|
142
|
+
original_handlers = lightrag_logger.handlers[:] # Copy list
|
|
143
|
+
original_level = lightrag_logger.level
|
|
144
|
+
|
|
145
|
+
# Temporarily remove existing console handlers to avoid duplicate output
|
|
146
|
+
# We'll forward all logs through our handler instead
|
|
147
|
+
console_handlers_to_remove = []
|
|
148
|
+
for handler in original_handlers:
|
|
149
|
+
if isinstance(handler, logging.StreamHandler):
|
|
150
|
+
console_handlers_to_remove.append(handler)
|
|
151
|
+
|
|
152
|
+
for handler in console_handlers_to_remove:
|
|
153
|
+
lightrag_logger.removeHandler(handler)
|
|
154
|
+
|
|
155
|
+
# Ensure LightRAG logger level is set low enough to capture all logs
|
|
156
|
+
# The logger level controls which logs are created, handler level controls which are processed
|
|
157
|
+
# Set to DEBUG to ensure we capture everything, then filter at handler level
|
|
158
|
+
if lightrag_logger.level > logging.DEBUG:
|
|
159
|
+
lightrag_logger.setLevel(logging.DEBUG)
|
|
160
|
+
|
|
161
|
+
# Create and add forwarder
|
|
162
|
+
forwarder = LightRAGLogForwarder(ai_tutor_logger, add_prefix=add_prefix)
|
|
163
|
+
forwarder.setLevel(min_level)
|
|
164
|
+
lightrag_logger.addHandler(forwarder)
|
|
165
|
+
|
|
166
|
+
# Test that forwarding works by sending a test log
|
|
167
|
+
try:
|
|
168
|
+
test_msg = "LightRAG log forwarding enabled"
|
|
169
|
+
lightrag_logger.info(test_msg)
|
|
170
|
+
except:
|
|
171
|
+
pass # Ignore test log errors
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
yield
|
|
175
|
+
finally:
|
|
176
|
+
# Clean up: remove our forwarder
|
|
177
|
+
if forwarder in lightrag_logger.handlers:
|
|
178
|
+
lightrag_logger.removeHandler(forwarder)
|
|
179
|
+
forwarder.close()
|
|
180
|
+
|
|
181
|
+
# Restore original console handlers if they were removed
|
|
182
|
+
for handler in console_handlers_to_remove:
|
|
183
|
+
if handler not in lightrag_logger.handlers:
|
|
184
|
+
lightrag_logger.addHandler(handler)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
LlamaIndex Log Forwarder
|
|
4
|
+
========================
|
|
5
|
+
|
|
6
|
+
Forwards LlamaIndex logs to DeepTutor's unified logging system.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from contextlib import contextmanager
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LlamaIndexLogForwarder(logging.Handler):
|
|
15
|
+
"""
|
|
16
|
+
Handler that forwards LlamaIndex logger messages to DeepTutor logger.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, ai_tutor_logger, add_prefix: bool = True):
|
|
20
|
+
"""
|
|
21
|
+
Args:
|
|
22
|
+
ai_tutor_logger: DeepTutor Logger instance
|
|
23
|
+
add_prefix: Whether to add prefix to messages
|
|
24
|
+
"""
|
|
25
|
+
super().__init__()
|
|
26
|
+
self.ai_tutor_logger = ai_tutor_logger
|
|
27
|
+
self.add_prefix = add_prefix
|
|
28
|
+
# Capture all log levels
|
|
29
|
+
self.setLevel(logging.DEBUG)
|
|
30
|
+
|
|
31
|
+
def emit(self, record: logging.LogRecord):
|
|
32
|
+
"""
|
|
33
|
+
Forward log record to DeepTutor logger.
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
message = record.getMessage()
|
|
37
|
+
|
|
38
|
+
# Map log levels
|
|
39
|
+
level = record.levelno
|
|
40
|
+
if level >= logging.ERROR:
|
|
41
|
+
self.ai_tutor_logger.error(message)
|
|
42
|
+
elif level >= logging.WARNING:
|
|
43
|
+
self.ai_tutor_logger.warning(message)
|
|
44
|
+
elif level >= logging.INFO:
|
|
45
|
+
self.ai_tutor_logger.info(message)
|
|
46
|
+
else:
|
|
47
|
+
self.ai_tutor_logger.debug(message)
|
|
48
|
+
|
|
49
|
+
except Exception:
|
|
50
|
+
self.handleError(record)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@contextmanager
|
|
54
|
+
def LlamaIndexLogContext(
|
|
55
|
+
logger_name: Optional[str] = None,
|
|
56
|
+
scene: str = "llamaindex",
|
|
57
|
+
min_level: str = "INFO",
|
|
58
|
+
):
|
|
59
|
+
"""
|
|
60
|
+
Context manager for LlamaIndex log forwarding.
|
|
61
|
+
|
|
62
|
+
Automatically sets up and tears down log forwarding.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
logger_name: Explicit logger name (defaults to scene name)
|
|
66
|
+
scene: Scene name for logger identification
|
|
67
|
+
min_level: Minimum log level to forward
|
|
68
|
+
|
|
69
|
+
Usage:
|
|
70
|
+
with LlamaIndexLogContext("VectorSearch"):
|
|
71
|
+
# LlamaIndex operations
|
|
72
|
+
index.query(query)
|
|
73
|
+
"""
|
|
74
|
+
from ..logger import get_logger
|
|
75
|
+
|
|
76
|
+
# Determine logger name
|
|
77
|
+
if logger_name is None:
|
|
78
|
+
logger_name = scene.title().replace("_", "")
|
|
79
|
+
|
|
80
|
+
# Get DeepTutor logger
|
|
81
|
+
ai_tutor_logger = get_logger(logger_name)
|
|
82
|
+
|
|
83
|
+
# Get LlamaIndex loggers
|
|
84
|
+
llama_loggers = [
|
|
85
|
+
logging.getLogger("llama_index"),
|
|
86
|
+
logging.getLogger("llama_index.core"),
|
|
87
|
+
logging.getLogger("llama_index.vector_stores"),
|
|
88
|
+
logging.getLogger("llama_index.embeddings"),
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
# Parse min level
|
|
92
|
+
min_level_int = getattr(logging, min_level.upper(), logging.INFO)
|
|
93
|
+
|
|
94
|
+
# Store original state
|
|
95
|
+
original_states: List[Dict[str, Any]] = []
|
|
96
|
+
forwarders: List[Tuple[logging.Logger, LlamaIndexLogForwarder]] = []
|
|
97
|
+
|
|
98
|
+
for llama_logger in llama_loggers:
|
|
99
|
+
original_states.append(
|
|
100
|
+
{
|
|
101
|
+
"logger": llama_logger,
|
|
102
|
+
"handlers": llama_logger.handlers[:],
|
|
103
|
+
"level": llama_logger.level,
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Temporarily remove console handlers
|
|
108
|
+
console_handlers_to_remove = []
|
|
109
|
+
for handler in llama_logger.handlers:
|
|
110
|
+
if isinstance(handler, logging.StreamHandler):
|
|
111
|
+
console_handlers_to_remove.append(handler)
|
|
112
|
+
|
|
113
|
+
for handler in console_handlers_to_remove:
|
|
114
|
+
llama_logger.removeHandler(handler)
|
|
115
|
+
|
|
116
|
+
# Set level
|
|
117
|
+
if llama_logger.level > logging.DEBUG:
|
|
118
|
+
llama_logger.setLevel(logging.DEBUG)
|
|
119
|
+
|
|
120
|
+
# Add forwarder
|
|
121
|
+
forwarder = LlamaIndexLogForwarder(ai_tutor_logger)
|
|
122
|
+
forwarder.setLevel(min_level_int)
|
|
123
|
+
llama_logger.addHandler(forwarder)
|
|
124
|
+
forwarders.append((llama_logger, forwarder))
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
yield
|
|
128
|
+
finally:
|
|
129
|
+
# Clean up forwarders
|
|
130
|
+
for llama_logger, forwarder in forwarders:
|
|
131
|
+
if forwarder in llama_logger.handlers:
|
|
132
|
+
llama_logger.removeHandler(forwarder)
|
|
133
|
+
forwarder.close()
|
|
134
|
+
|
|
135
|
+
# Restore original state
|
|
136
|
+
for state in original_states:
|
|
137
|
+
llama_logger = state["logger"]
|
|
138
|
+
# Restore handlers that were removed
|
|
139
|
+
for handler in state["handlers"]:
|
|
140
|
+
if handler not in llama_logger.handlers:
|
|
141
|
+
llama_logger.addHandler(handler)
|
src/logging/config.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logging Configuration
|
|
3
|
+
=====================
|
|
4
|
+
|
|
5
|
+
Configuration settings for the logging system.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class LoggingConfig:
|
|
15
|
+
"""Configuration for the logging system."""
|
|
16
|
+
|
|
17
|
+
# Output settings
|
|
18
|
+
console_output: bool = True
|
|
19
|
+
file_output: bool = True
|
|
20
|
+
|
|
21
|
+
# Log levels
|
|
22
|
+
console_level: str = "INFO"
|
|
23
|
+
file_level: str = "DEBUG"
|
|
24
|
+
|
|
25
|
+
# Log directory (relative to project root or absolute)
|
|
26
|
+
log_dir: Optional[str] = None
|
|
27
|
+
|
|
28
|
+
# File rotation settings
|
|
29
|
+
max_bytes: int = 10 * 1024 * 1024 # 10MB
|
|
30
|
+
backup_count: int = 5
|
|
31
|
+
|
|
32
|
+
# WebSocket streaming
|
|
33
|
+
websocket_enabled: bool = True
|
|
34
|
+
websocket_queue_size: int = 1000
|
|
35
|
+
|
|
36
|
+
# LightRAG forwarding
|
|
37
|
+
lightrag_forwarding_enabled: bool = True
|
|
38
|
+
lightrag_min_level: str = "INFO"
|
|
39
|
+
lightrag_add_prefix: bool = True
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_default_log_dir() -> Path:
|
|
43
|
+
"""Get the default log directory."""
|
|
44
|
+
project_root = Path(__file__).resolve().parent.parent.parent
|
|
45
|
+
return project_root / "data" / "user" / "logs"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def load_logging_config() -> LoggingConfig:
|
|
49
|
+
"""
|
|
50
|
+
Load logging configuration from config files.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
LoggingConfig instance with loaded or default values.
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
from src.services.config import get_path_from_config, load_config_with_main
|
|
57
|
+
|
|
58
|
+
project_root = Path(__file__).resolve().parent.parent.parent
|
|
59
|
+
config = load_config_with_main("solve_config.yaml", project_root)
|
|
60
|
+
|
|
61
|
+
logging_config = config.get("logging", {})
|
|
62
|
+
|
|
63
|
+
return LoggingConfig(
|
|
64
|
+
console_output=logging_config.get("console_output", True),
|
|
65
|
+
file_output=logging_config.get("file_output", True),
|
|
66
|
+
console_level=logging_config.get("console_level", "INFO"),
|
|
67
|
+
file_level=logging_config.get("file_level", "DEBUG"),
|
|
68
|
+
log_dir=get_path_from_config(config, "user_log_dir"),
|
|
69
|
+
lightrag_forwarding_enabled=logging_config.get("lightrag_forwarding", {}).get(
|
|
70
|
+
"enabled", True
|
|
71
|
+
),
|
|
72
|
+
lightrag_min_level=logging_config.get("lightrag_forwarding", {}).get(
|
|
73
|
+
"min_level", "INFO"
|
|
74
|
+
),
|
|
75
|
+
lightrag_add_prefix=logging_config.get("lightrag_forwarding", {}).get(
|
|
76
|
+
"add_prefix", True
|
|
77
|
+
),
|
|
78
|
+
)
|
|
79
|
+
except Exception:
|
|
80
|
+
return LoggingConfig()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Log Handlers
|
|
3
|
+
============
|
|
4
|
+
|
|
5
|
+
Custom logging handlers for various output destinations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .console import ConsoleHandler
|
|
9
|
+
from .file import FileHandler, JSONFileHandler, RotatingFileHandler, create_task_logger
|
|
10
|
+
from .websocket import LogInterceptor, WebSocketLogHandler
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"ConsoleHandler",
|
|
14
|
+
"FileHandler",
|
|
15
|
+
"JSONFileHandler",
|
|
16
|
+
"RotatingFileHandler",
|
|
17
|
+
"WebSocketLogHandler",
|
|
18
|
+
"LogInterceptor",
|
|
19
|
+
"create_task_logger",
|
|
20
|
+
]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Console Log Handler
|
|
4
|
+
===================
|
|
5
|
+
|
|
6
|
+
Color-coded console output with symbols.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ConsoleFormatter(logging.Formatter):
|
|
14
|
+
"""
|
|
15
|
+
Clean console formatter with colors and symbols.
|
|
16
|
+
Format: [Module] Symbol Message
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# ANSI color codes
|
|
20
|
+
COLORS = {
|
|
21
|
+
"DEBUG": "\033[90m", # Gray
|
|
22
|
+
"INFO": "\033[37m", # White
|
|
23
|
+
"SUCCESS": "\033[32m", # Green
|
|
24
|
+
"WARNING": "\033[33m", # Yellow
|
|
25
|
+
"ERROR": "\033[31m", # Red
|
|
26
|
+
"CRITICAL": "\033[35m", # Magenta
|
|
27
|
+
}
|
|
28
|
+
RESET = "\033[0m"
|
|
29
|
+
BOLD = "\033[1m"
|
|
30
|
+
DIM = "\033[2m"
|
|
31
|
+
|
|
32
|
+
# Symbols for different log types
|
|
33
|
+
SYMBOLS = {
|
|
34
|
+
"DEBUG": "·",
|
|
35
|
+
"INFO": "●",
|
|
36
|
+
"SUCCESS": "✓",
|
|
37
|
+
"WARNING": "⚠",
|
|
38
|
+
"ERROR": "✗",
|
|
39
|
+
"CRITICAL": "✗",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
43
|
+
# Get module name (padded to 12 chars for alignment)
|
|
44
|
+
module = getattr(record, "module_name", record.name)
|
|
45
|
+
module_padded = f"[{module}]".ljust(14)
|
|
46
|
+
|
|
47
|
+
# Get symbol (can be overridden via record.symbol)
|
|
48
|
+
symbol = getattr(record, "symbol", self.SYMBOLS.get(record.levelname, "●"))
|
|
49
|
+
|
|
50
|
+
# Get color
|
|
51
|
+
level = getattr(record, "display_level", record.levelname)
|
|
52
|
+
color = self.COLORS.get(level, self.COLORS["INFO"])
|
|
53
|
+
|
|
54
|
+
# Format message
|
|
55
|
+
message = record.getMessage()
|
|
56
|
+
|
|
57
|
+
# Build output: [Module] ● Message
|
|
58
|
+
return f"{self.DIM}{module_padded}{self.RESET} {color}{symbol}{self.RESET} {message}"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ConsoleHandler(logging.StreamHandler):
|
|
62
|
+
"""
|
|
63
|
+
Console handler with color-coded output.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __init__(self, level: int = logging.INFO):
|
|
67
|
+
"""
|
|
68
|
+
Initialize console handler.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
level: Minimum log level to display
|
|
72
|
+
"""
|
|
73
|
+
super().__init__(sys.stdout)
|
|
74
|
+
self.setLevel(level)
|
|
75
|
+
self.setFormatter(ConsoleFormatter())
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File Log Handlers
|
|
3
|
+
=================
|
|
4
|
+
|
|
5
|
+
File-based logging with rotation support.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
from logging.handlers import RotatingFileHandler as BaseRotatingFileHandler
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FileFormatter(logging.Formatter):
|
|
18
|
+
"""
|
|
19
|
+
Detailed file formatter for log files.
|
|
20
|
+
Format: TIMESTAMP [LEVEL] [Module] Message
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
super().__init__(
|
|
25
|
+
fmt="%(asctime)s [%(levelname)-8s] [%(module_name)-12s] %(message)s",
|
|
26
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
30
|
+
# Ensure module_name exists
|
|
31
|
+
if not hasattr(record, "module_name"):
|
|
32
|
+
record.module_name = record.name
|
|
33
|
+
return super().format(record)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class FileHandler(logging.FileHandler):
|
|
37
|
+
"""
|
|
38
|
+
File handler with detailed formatting.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
filename: str,
|
|
44
|
+
level: int = logging.DEBUG,
|
|
45
|
+
encoding: str = "utf-8",
|
|
46
|
+
):
|
|
47
|
+
"""
|
|
48
|
+
Initialize file handler.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
filename: Path to log file
|
|
52
|
+
level: Minimum log level
|
|
53
|
+
encoding: File encoding
|
|
54
|
+
"""
|
|
55
|
+
# Ensure directory exists
|
|
56
|
+
Path(filename).parent.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
|
|
58
|
+
super().__init__(filename, encoding=encoding)
|
|
59
|
+
self.setLevel(level)
|
|
60
|
+
self.setFormatter(FileFormatter())
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class RotatingFileHandler(BaseRotatingFileHandler):
|
|
64
|
+
"""
|
|
65
|
+
Rotating file handler with size-based rotation.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
filename: str,
|
|
71
|
+
level: int = logging.DEBUG,
|
|
72
|
+
max_bytes: int = 10 * 1024 * 1024, # 10MB
|
|
73
|
+
backup_count: int = 5,
|
|
74
|
+
encoding: str = "utf-8",
|
|
75
|
+
):
|
|
76
|
+
"""
|
|
77
|
+
Initialize rotating file handler.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
filename: Path to log file
|
|
81
|
+
level: Minimum log level
|
|
82
|
+
max_bytes: Maximum file size before rotation
|
|
83
|
+
backup_count: Number of backup files to keep
|
|
84
|
+
encoding: File encoding
|
|
85
|
+
"""
|
|
86
|
+
# Ensure directory exists
|
|
87
|
+
Path(filename).parent.mkdir(parents=True, exist_ok=True)
|
|
88
|
+
|
|
89
|
+
super().__init__(
|
|
90
|
+
filename,
|
|
91
|
+
maxBytes=max_bytes,
|
|
92
|
+
backupCount=backup_count,
|
|
93
|
+
encoding=encoding,
|
|
94
|
+
)
|
|
95
|
+
self.setLevel(level)
|
|
96
|
+
self.setFormatter(FileFormatter())
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class JSONFileHandler(logging.Handler):
|
|
100
|
+
"""
|
|
101
|
+
A logging handler that writes structured JSON logs to a file.
|
|
102
|
+
Each line is a valid JSON object (JSONL format).
|
|
103
|
+
|
|
104
|
+
Useful for:
|
|
105
|
+
- LLM call logging
|
|
106
|
+
- Structured analysis
|
|
107
|
+
- Log parsing and analysis
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
filepath: str,
|
|
113
|
+
level: int = logging.DEBUG,
|
|
114
|
+
encoding: str = "utf-8",
|
|
115
|
+
):
|
|
116
|
+
"""
|
|
117
|
+
Initialize JSON file handler.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
filepath: Path to log file
|
|
121
|
+
level: Minimum log level
|
|
122
|
+
encoding: File encoding
|
|
123
|
+
"""
|
|
124
|
+
super().__init__()
|
|
125
|
+
|
|
126
|
+
# Ensure directory exists
|
|
127
|
+
Path(filepath).parent.mkdir(parents=True, exist_ok=True)
|
|
128
|
+
|
|
129
|
+
self.filepath = filepath
|
|
130
|
+
self.encoding = encoding
|
|
131
|
+
self.setLevel(level)
|
|
132
|
+
self.setFormatter(logging.Formatter("%(message)s"))
|
|
133
|
+
|
|
134
|
+
def emit(self, record: logging.LogRecord):
|
|
135
|
+
"""Emit a log record as JSON."""
|
|
136
|
+
try:
|
|
137
|
+
# Build JSON entry
|
|
138
|
+
entry = {
|
|
139
|
+
"timestamp": datetime.fromtimestamp(record.created).isoformat(),
|
|
140
|
+
"level": record.levelname,
|
|
141
|
+
"module": getattr(record, "module_name", record.name),
|
|
142
|
+
"message": self.format(record),
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# Add extra fields if present
|
|
146
|
+
for key in ["symbol", "display_level", "tool_name", "elapsed_ms", "tokens"]:
|
|
147
|
+
if hasattr(record, key):
|
|
148
|
+
entry[key] = getattr(record, key)
|
|
149
|
+
|
|
150
|
+
# Write to file
|
|
151
|
+
with open(self.filepath, "a", encoding=self.encoding) as f:
|
|
152
|
+
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
153
|
+
|
|
154
|
+
except Exception:
|
|
155
|
+
self.handleError(record)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def create_task_logger(
|
|
159
|
+
task_id: str,
|
|
160
|
+
module_name: str,
|
|
161
|
+
log_dir: str,
|
|
162
|
+
queue: Optional["asyncio.Queue"] = None,
|
|
163
|
+
) -> logging.Logger:
|
|
164
|
+
"""
|
|
165
|
+
Create a logger for a specific task with file and optional WebSocket output.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
task_id: Unique task identifier
|
|
169
|
+
module_name: Module name (e.g., "Solver", "Research")
|
|
170
|
+
log_dir: Directory for log files
|
|
171
|
+
queue: Optional asyncio.Queue for WebSocket streaming
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Configured logger
|
|
175
|
+
"""
|
|
176
|
+
from .websocket import WebSocketLogHandler
|
|
177
|
+
|
|
178
|
+
# Create log directory
|
|
179
|
+
log_path = Path(log_dir)
|
|
180
|
+
log_path.mkdir(parents=True, exist_ok=True)
|
|
181
|
+
|
|
182
|
+
# Create logger
|
|
183
|
+
logger = logging.getLogger(f"ai_tutor.{module_name}.{task_id}")
|
|
184
|
+
logger.setLevel(logging.DEBUG)
|
|
185
|
+
logger.handlers.clear()
|
|
186
|
+
logger.propagate = False
|
|
187
|
+
|
|
188
|
+
# File handler
|
|
189
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
190
|
+
log_file = log_path / f"{module_name}_{task_id}_{timestamp}.log"
|
|
191
|
+
|
|
192
|
+
file_handler = FileHandler(str(log_file))
|
|
193
|
+
logger.addHandler(file_handler)
|
|
194
|
+
|
|
195
|
+
# WebSocket handler if queue provided
|
|
196
|
+
if queue is not None:
|
|
197
|
+
ws_handler = WebSocketLogHandler(queue)
|
|
198
|
+
ws_handler.setLevel(logging.INFO)
|
|
199
|
+
logger.addHandler(ws_handler)
|
|
200
|
+
|
|
201
|
+
return logger
|