mirror of
https://github.com/Nezumi-2711/astrbot_plugin_qq_group_daily_analysis.git
synced 2026-09-22 13:38:43 +00:00
docs: update documentation and project structure
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
# TraceID 实现代码示例
|
||||
|
||||
这个文件展示如何在插件中实现 contextvars + logging.Filter 方案。
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
astrbot_plugin_qq_group_daily_analysis/
|
||||
├── src/
|
||||
│ ├── utils/
|
||||
│ │ ├── trace.py # ← 新增:TraceID 相关
|
||||
│ │ └── helpers.py
|
||||
│ ├── core/
|
||||
│ │ └── config.py
|
||||
│ └── ...
|
||||
├── main.py # ← 需要修改:注册 Filter
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 1. 新增文件:src/utils/trace.py
|
||||
|
||||
```python
|
||||
"""
|
||||
TraceID 追踪工具模块
|
||||
提供分布式追踪的 trace_id 上下文管理
|
||||
"""
|
||||
|
||||
import contextvars
|
||||
import logging
|
||||
import time
|
||||
from astrbot.api import logger
|
||||
|
||||
# ============ ContextVar 定义 ============
|
||||
_trace_id: contextvars.ContextVar[str] = contextvars.ContextVar(
|
||||
'trace_id',
|
||||
default=''
|
||||
)
|
||||
|
||||
|
||||
# ============ Filter 实现 ============
|
||||
class TraceIDFilter(logging.Filter):
|
||||
"""
|
||||
将 trace_id 自动注入到日志记录
|
||||
|
||||
使用方式:
|
||||
logger.addFilter(TraceIDFilter())
|
||||
# 之后所有日志的 record 对象都会有 trace_id 属性
|
||||
"""
|
||||
|
||||
def filter(self, record):
|
||||
"""添加 trace_id 到日志记录"""
|
||||
trace_id = _trace_id.get('')
|
||||
record.trace_id = trace_id if trace_id else 'no-trace'
|
||||
return True
|
||||
|
||||
|
||||
# ============ 接口函数 ============
|
||||
def set_trace_id(group_id: str, timestamp: int = None) -> str:
|
||||
"""
|
||||
设置当前协程的 trace_id
|
||||
|
||||
Args:
|
||||
group_id: 群 ID
|
||||
timestamp: 时间戳(如果为 None 则使用当前时间)
|
||||
|
||||
Returns:
|
||||
生成的 trace_id 字符串
|
||||
|
||||
Example:
|
||||
>>> trace_id = set_trace_id("123456789")
|
||||
>>> logger.info("分析开始") # 日志自动包含 trace_id
|
||||
"""
|
||||
if timestamp is None:
|
||||
timestamp = int(time.time())
|
||||
|
||||
trace_id = f"{group_id}-{timestamp}"
|
||||
_trace_id.set(trace_id)
|
||||
return trace_id
|
||||
|
||||
|
||||
def get_trace_id() -> str:
|
||||
"""获取当前协程的 trace_id"""
|
||||
return _trace_id.get('no-trace')
|
||||
|
||||
|
||||
def clear_trace_id():
|
||||
"""清理当前协程的 trace_id"""
|
||||
_trace_id.set('')
|
||||
|
||||
|
||||
def with_trace_id(group_id: str):
|
||||
"""
|
||||
上下文管理器:自动管理 trace_id 的生命周期
|
||||
|
||||
Example:
|
||||
>>> from src.utils.trace import with_trace_id
|
||||
>>>
|
||||
>>> async def analyze_group(group_id: str):
|
||||
... with with_trace_id(group_id):
|
||||
... logger.info("开始分析") # 自动包含 trace_id
|
||||
... # ...
|
||||
... logger.info("分析完成")
|
||||
... # 退出时自动清理 trace_id
|
||||
"""
|
||||
class TraceContextManager:
|
||||
def __init__(self, group_id):
|
||||
self.group_id = group_id
|
||||
self.trace_id = None
|
||||
|
||||
def __enter__(self):
|
||||
self.trace_id = set_trace_id(self.group_id)
|
||||
return self.trace_id
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
clear_trace_id()
|
||||
return False
|
||||
|
||||
return TraceContextManager(group_id)
|
||||
|
||||
|
||||
# ============ 初始化函数 ============
|
||||
def setup_trace_logging():
|
||||
"""
|
||||
初始化 TraceID 日志追踪
|
||||
|
||||
在插件启动时调用一次即可:
|
||||
from src.utils.trace import setup_trace_logging
|
||||
setup_trace_logging()
|
||||
"""
|
||||
# 注册 Filter 到 AstrBot logger
|
||||
trace_filter = TraceIDFilter()
|
||||
|
||||
# 检查是否已注册(避免重复注册)
|
||||
for existing_filter in logger.filters:
|
||||
if isinstance(existing_filter, TraceIDFilter):
|
||||
return # 已经注册过了
|
||||
|
||||
logger.addFilter(trace_filter)
|
||||
logger.info("[Trace] TraceID 日志追踪已启用")
|
||||
```
|
||||
|
||||
## 2. 修改文件:main.py(插件主文件)
|
||||
|
||||
在 `__init__` 方法中添加初始化:
|
||||
|
||||
```python
|
||||
# main.py
|
||||
from src.utils.trace import setup_trace_logging
|
||||
|
||||
class QQGroupDailyAnalysis(Star):
|
||||
def __init__(self, context: Context, config: AstrBotConfig):
|
||||
super().__init__(context)
|
||||
# ... 其他初始化代码 ...
|
||||
|
||||
# ← 新增:初始化 TraceID 追踪
|
||||
setup_trace_logging()
|
||||
|
||||
# ... 其他初始化代码 ...
|
||||
```
|
||||
|
||||
## 3. 修改文件:src/scheduler/auto_scheduler.py
|
||||
|
||||
在群分析方法中使用 TraceID:
|
||||
|
||||
```python
|
||||
# auto_scheduler.py
|
||||
from src.utils.trace import set_trace_id, clear_trace_id, with_trace_id
|
||||
|
||||
class AutoScheduler:
|
||||
# ... 其他方法 ...
|
||||
|
||||
async def _perform_auto_analysis_for_group(self, group_id: str):
|
||||
"""为指定群执行自动分析"""
|
||||
|
||||
# ← 方案 1:使用上下文管理器(推荐)
|
||||
with with_trace_id(group_id):
|
||||
try:
|
||||
logger.info(f"开始为群 {group_id} 执行自动分析")
|
||||
# ... 分析逻辑,所有 logger 调用都自动包含 trace_id ...
|
||||
except Exception as e:
|
||||
logger.error(f"群 {group_id} 自动分析失败: {e}")
|
||||
|
||||
# ← 方案 2:手动设置/清理(传统方式)
|
||||
# set_trace_id(group_id)
|
||||
# try:
|
||||
# logger.info(f"开始为群 {group_id} 执行自动分析")
|
||||
# # ... 分析逻辑 ...
|
||||
# finally:
|
||||
# clear_trace_id()
|
||||
```
|
||||
|
||||
## 4. 修改文件:src/utils/helpers.py
|
||||
|
||||
在 `MessageAnalyzer` 中使用 TraceID:
|
||||
|
||||
```python
|
||||
# helpers.py
|
||||
from src.utils.trace import with_trace_id
|
||||
|
||||
class MessageAnalyzer:
|
||||
async def analyze_messages(
|
||||
self,
|
||||
messages: list[dict],
|
||||
group_id: str,
|
||||
unified_msg_origin: str = None
|
||||
) -> dict:
|
||||
"""完整的消息分析流程"""
|
||||
|
||||
with with_trace_id(group_id):
|
||||
try:
|
||||
logger.info("开始消息分析")
|
||||
|
||||
# 基础统计
|
||||
statistics = await asyncio.to_thread(...)
|
||||
logger.info(f"统计完成:{len(messages)} 条消息")
|
||||
|
||||
# LLM 分析
|
||||
topics, user_titles, golden_quotes, token_usage = \
|
||||
await self.llm_analyzer.analyze_all_concurrent(...)
|
||||
logger.info(f"LLM 分析完成")
|
||||
|
||||
return {
|
||||
"statistics": statistics,
|
||||
"topics": topics,
|
||||
"user_titles": user_titles,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"消息分析失败: {e}")
|
||||
return None
|
||||
```
|
||||
|
||||
## 5. 日志输出效果
|
||||
|
||||
启动后,日志会自动包含 trace_id:
|
||||
|
||||
```bash
|
||||
$ python main.py
|
||||
|
||||
[10:30:45] [Plug] [INFO ] [auto_scheduler:100]: [123456789-1707292800] 开始为群 123456789 执行自动分析
|
||||
[10:30:45] [Plug] [INFO ] [message_handler:50]: [123456789-1707292800] 开始获取消息
|
||||
[10:30:46] [Plug] [INFO ] [message_handler:100]: [123456789-1707292800] 获取成功,共 256 条消息
|
||||
[10:30:47] [Plug] [INFO ] [helpers:200]: [123456789-1707292800] 开始消息分析
|
||||
[10:30:48] [Plug] [INFO ] [llm_analyzer:300]: [123456789-1707292800] 开始 LLM 分析
|
||||
[10:30:50] [Plug] [INFO ] [llm_analyzer:350]: [123456789-1707292800] LLM 分析完成
|
||||
[10:30:51] [Plug] [INFO ] [report_generator:400]: [123456789-1707292800] 报告生成中
|
||||
[10:30:53] [Plug] [INFO ] [auto_scheduler:450]: [123456789-1707292800] 分析完成,耗时 8s
|
||||
```
|
||||
|
||||
## 6. 查看日志
|
||||
|
||||
### 终端实时查看
|
||||
|
||||
```bash
|
||||
# 看所有日志
|
||||
tail -f logs/astrbot.log
|
||||
|
||||
# 看特定群的日志
|
||||
tail -f logs/astrbot.log | grep "123456789"
|
||||
|
||||
# 或使用 grep 搜索
|
||||
grep "123456789-1707292800" logs/astrbot.log
|
||||
```
|
||||
|
||||
### 按 TraceID 追踪完整链路
|
||||
|
||||
```bash
|
||||
# 获取某个 trace_id 的所有日志
|
||||
grep "123456789-1707292800" logs/astrbot.log
|
||||
|
||||
# 按时间戳统计分析耗时
|
||||
# trace_id 格式 {group_id}-{start_timestamp}
|
||||
# 可从日志时间戳对比,计算耗时
|
||||
```
|
||||
|
||||
## 7. 成本统计
|
||||
|
||||
| 项目 | 工作量 | 难度 |
|
||||
|------|--------|------|
|
||||
| 新增 trace.py | ~80 行 | 低 |
|
||||
| 修改 main.py | 3 行 | 低 |
|
||||
| 修改 auto_scheduler.py | ~10 行 | 低 |
|
||||
| 修改 helpers.py | ~5 行 | 低 |
|
||||
| 修改其他文件 | 可选(向后兼容) | 低 |
|
||||
| **总计** | **~100 行** | **低** |
|
||||
|
||||
## 8. 向后兼容性
|
||||
|
||||
✅ **完全向后兼容**
|
||||
|
||||
- 现有代码无需改动
|
||||
- 新增代码仅在初始化时注册 Filter
|
||||
- 所有日志输出不变,仅在 record 对象中添加 trace_id 字段
|
||||
- 如果日志格式未修改,trace_id 不会显示,但在代码中可以访问
|
||||
|
||||
## 9. 扩展应用
|
||||
|
||||
### 应用 1:性能分析
|
||||
|
||||
```python
|
||||
from src.utils.trace import get_trace_id
|
||||
import time
|
||||
|
||||
start = time.time()
|
||||
# ... 某个操作 ...
|
||||
elapsed = time.time() - start
|
||||
logger.info(f"操作耗时 {elapsed:.2f}s")
|
||||
# 日志自动包含 trace_id,可后续统计所有群的平均耗时
|
||||
```
|
||||
|
||||
### 应用 2:错误统计
|
||||
|
||||
```bash
|
||||
# 找出所有失败的 trace_id
|
||||
grep "ERROR" logs/astrbot.log | awk -F'[]' '{print $2}' | sort | uniq -c
|
||||
|
||||
# 输出:
|
||||
# 3 123456789-1707292800
|
||||
# 2 987654321-1707292801
|
||||
# → 表示这两个分析任务各失败了 3 次和 2 次
|
||||
```
|
||||
|
||||
### 应用 3:分布式追踪
|
||||
|
||||
如果以后扩展到分布式部署,trace_id 可以传递到其他服务:
|
||||
|
||||
```python
|
||||
# 跨服务调用时传递 trace_id
|
||||
async def call_external_service(url, data):
|
||||
headers = {"X-Trace-ID": get_trace_id()}
|
||||
return await http_client.post(url, json=data, headers=headers)
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user