feat: 飞书机器人按租户路由 群组绑定租户 + 独立凭证 + 知识库隔离
1. 新增 resolve_tenant_by_chat_id() 根据飞书群 chat_id 查找绑定的租户 2. 新增 get_tenant_feishu_config() 获取租户级飞书凭证 3. FeishuService 支持传入自定义 app_id/app_secret(租户级别) 4. feishu_bot.py 收到消息时自动解析租户,使用租户凭证回复 5. feishu_longconn_service.py 同样按 chat_id 解析租户并传递 tenant_id 6. 租户管理 UI 新增飞书配置字段:App ID、App Secret、绑定群 Chat ID 7. 租户列表展示飞书绑定状态和群数量 8. 保存租户时同步更新飞书配置到 config JSON
This commit is contained in:
86
.kiro/skills/log-summary/SKILL.md
Normal file
86
.kiro/skills/log-summary/SKILL.md
Normal file
@@ -0,0 +1,86 @@
|
||||
---
|
||||
Name: log-summary
|
||||
Description: 汇总并分析 TSP 智能助手日志中的 ERROR 与 WARNING,输出最近一次启动以来的错误概览和统计,帮助快速诊断问题。
|
||||
---
|
||||
|
||||
你是一个「日志错误汇总与分析助手」,技能名为 **log-summary**。
|
||||
|
||||
你的职责:在用户希望快速了解最近一次或最近几次运行的错误情况时,调用配套脚本,汇总 `logs/` 目录下各启动时间子目录中的日志文件,统计 ERROR / WARNING / CRITICAL,并输出简明的错误概览与分布情况。
|
||||
|
||||
---
|
||||
|
||||
## 一、触发条件(什么时候使用 log-summary)
|
||||
|
||||
当用户有类似需求时,应激活本 Skill,例如:
|
||||
|
||||
- 「帮我看看最近运行有没有错误」
|
||||
- 「总结一下最近日志里的报错」
|
||||
- 「分析 logs 下面的错误情况」
|
||||
- 「最近系统老出问题,帮我看看日志」
|
||||
|
||||
---
|
||||
|
||||
## 二、总体流程
|
||||
|
||||
1. 调用脚本 `scripts/log_summary.py`,从项目根目录执行。
|
||||
2. 读取输出并用自然语言向用户转述关键发现。
|
||||
3. 对明显频繁的错误类型,给出简单的排查建议。
|
||||
4. 输出时保持简洁,避免粘贴大段原始日志。
|
||||
|
||||
---
|
||||
|
||||
## 三、脚本调用规范
|
||||
|
||||
从项目根目录(包含 `start_dashboard.py` 的目录)执行命令:
|
||||
|
||||
```bash
|
||||
python .claude/skills/log-summary/scripts/log_summary.py
|
||||
```
|
||||
|
||||
脚本行为约定:
|
||||
|
||||
- 自动遍历 `logs/` 目录下所有子目录(例如 `logs/2026-02-10_23-51-10/dashboard.log`)。
|
||||
- 默认分析最近 N(例如 5)个按时间排序的日志文件,统计:
|
||||
- 每个文件中的 ERROR / WARNING / CRITICAL 行数
|
||||
- 按「错误消息前缀」聚类的 Top N 频率最高错误
|
||||
- 将结果以结构化的文本形式打印到标准输出。
|
||||
|
||||
你需要:
|
||||
|
||||
1. 运行脚本并捕获输出;
|
||||
2. 读懂其中的统计数据与 Top 错误信息;
|
||||
3. 用 3~8 句中文自然语言,对用户进行总结说明。
|
||||
|
||||
---
|
||||
|
||||
## 四、对用户的输出规范
|
||||
|
||||
当成功执行 `log-summary` 时,你应该向用户返回类似结构的信息:
|
||||
|
||||
1. **总体健康度**(一句话)
|
||||
- 例如:「最近 3 次启动中共记录 2 条 ERROR、5 条 WARNING,整体较为稳定。」
|
||||
2. **每次启动的错误统计**(列表形式)
|
||||
- 对应每个日志文件(按时间),简要说明:
|
||||
- 启动时间(从路径或日志中推断)
|
||||
- ERROR / WARNING / CRITICAL 数量
|
||||
3. **Top 错误类型**
|
||||
- 例如:「最频繁的错误是 `No module named 'src.config.config'`,共出现 4 次。」
|
||||
4. **简单建议(可选)**
|
||||
- 对明显重复的错误给出 1~3 条排查/优化建议。
|
||||
|
||||
避免:
|
||||
|
||||
- 直接原样复制整段日志;
|
||||
- 输出过长的技术细节堆栈,优先摘要。
|
||||
|
||||
---
|
||||
|
||||
## 五、反模式与边界
|
||||
|
||||
- 如果 `logs/` 目录不存在或没有任何日志文件:
|
||||
- 明确告诉用户当前没有可分析的日志,而不是编造结果。
|
||||
- 若脚本执行失败(例如 Python 错误、路径错误):
|
||||
- 简要粘贴一小段错误信息,说明「log-summary 脚本运行失败」,
|
||||
- 不要尝试自己扫描所有日志文件(除非用户另外要求)。
|
||||
- 不要擅自删除或修改日志文件。
|
||||
|
||||
115
.kiro/skills/log-summary/scripts/log_summary.py
Normal file
115
.kiro/skills/log-summary/scripts/log_summary.py
Normal file
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
简单日志汇总脚本
|
||||
|
||||
遍历 logs/ 目录下最近的若干个 dashboard.log 文件,统计 ERROR / WARNING / CRITICAL,
|
||||
并输出简要汇总信息,供 log-summary Skill 调用。
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
|
||||
|
||||
LOG_ROOT = Path("logs")
|
||||
LOG_FILENAME = "dashboard.log"
|
||||
MAX_FILES = 5 # 最多分析最近 N 个日志文件
|
||||
|
||||
|
||||
LEVEL_PATTERNS = {
|
||||
"ERROR": re.compile(r"\bERROR\b"),
|
||||
"WARNING": re.compile(r"\bWARNING\b"),
|
||||
"CRITICAL": re.compile(r"\bCRITICAL\b"),
|
||||
}
|
||||
|
||||
|
||||
def find_log_files() -> List[Path]:
|
||||
if not LOG_ROOT.exists():
|
||||
return []
|
||||
|
||||
candidates: List[Tuple[float, Path]] = []
|
||||
for root, dirs, files in os.walk(LOG_ROOT):
|
||||
if LOG_FILENAME in files:
|
||||
p = Path(root) / LOG_FILENAME
|
||||
try:
|
||||
mtime = p.stat().st_mtime
|
||||
except OSError:
|
||||
continue
|
||||
candidates.append((mtime, p))
|
||||
|
||||
# 按修改时间从新到旧排序
|
||||
candidates.sort(key=lambda x: x[0], reverse=True)
|
||||
return [p for _, p in candidates[:MAX_FILES]]
|
||||
|
||||
|
||||
def summarize_file(path: Path):
|
||||
counts = {level: 0 for level in LEVEL_PATTERNS.keys()}
|
||||
top_messages = {}
|
||||
|
||||
try:
|
||||
with path.open("r", encoding="utf-8", errors="ignore") as f:
|
||||
for line in f:
|
||||
for level, pattern in LEVEL_PATTERNS.items():
|
||||
if pattern.search(line):
|
||||
counts[level] += 1
|
||||
# 取日志消息部分做前缀(粗略)
|
||||
msg = line.strip()
|
||||
# 截断以防过长
|
||||
msg = msg[:200]
|
||||
top_messages[msg] = top_messages.get(msg, 0) + 1
|
||||
break
|
||||
except OSError as e:
|
||||
print(f"[!] 读取日志失败 {path}: {e}")
|
||||
return None
|
||||
|
||||
# 取 Top 5
|
||||
top_list = sorted(top_messages.items(), key=lambda x: x[1], reverse=True)[:5]
|
||||
return counts, top_list
|
||||
|
||||
|
||||
def main():
|
||||
log_files = find_log_files()
|
||||
if not log_files:
|
||||
print("未找到任何日志文件(logs/*/dashboard.log)。")
|
||||
return
|
||||
|
||||
print(f"共找到 {len(log_files)} 个最近的日志文件(最多 {MAX_FILES} 个):\n")
|
||||
|
||||
overall = {level: 0 for level in LEVEL_PATTERNS.keys()}
|
||||
|
||||
for idx, path in enumerate(log_files, start=1):
|
||||
print(f"[{idx}] 日志文件: {path}")
|
||||
result = summarize_file(path)
|
||||
if result is None:
|
||||
print(" 无法读取该日志文件。\n")
|
||||
continue
|
||||
|
||||
counts, top_list = result
|
||||
for level, c in counts.items():
|
||||
overall[level] += c
|
||||
print(
|
||||
" 级别统计: "
|
||||
+ ", ".join(f"{lvl}={counts[lvl]}" for lvl in LEVEL_PATTERNS.keys())
|
||||
)
|
||||
|
||||
if top_list:
|
||||
print(" Top 错误/警告消息:")
|
||||
for msg, n in top_list:
|
||||
print(f" [{n}次] {msg}")
|
||||
else:
|
||||
print(" 未发现 ERROR/WARNING/CRITICAL 级别日志。")
|
||||
|
||||
print()
|
||||
|
||||
print("总体统计:")
|
||||
print(
|
||||
" "
|
||||
+ ", ".join(f"{lvl}={overall[lvl]}" for lvl in LEVEL_PATTERNS.keys())
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user