Initial commit with project setup and basic structure established.

This commit is contained in:
2026-01-16 17:44:35 +08:00
parent bf3ab6a241
commit 02bf619c65
40 changed files with 3363 additions and 0 deletions
+74
View File
@@ -0,0 +1,74 @@
---
name: java-bug-finder
description: 自动登录远程 Ubuntu 服务器,根据错误码或服务名提取 Java Spring Boot 项目日志,并结合本地代码进行 Bug 诊断和修复建议。适用于 Java + Spring Boot 项目,支持 Docker Compose 和文件日志模式。
---
# Java Bug Finder (Java 故障排查专家)
本 Skill 用于自动化排查部署在远程服务器上的 Java Spring Boot 应用问题。它能够连接服务器、抓取日志、解析堆栈、定位源码并提供修复方案。
## 触发场景
- 用户要求排查特定服务的错误(如:“看看 order-service 为什么报错”)。
- 用户提到特定的错误码(如:“错误码 5002 是怎么回事?”)。
- 用户需要分析远程服务器上的 Java 异常堆栈。
## 工作流
### 1. 识别服务与服务器
当收到指令后,首先读取 `references/service_inventory.md`:
- **确认所有节点**: 确定服务部署的所有服务器 IP(如 192.168.3.200 和 192.168.3.230)。**Agent 必须对所有记录的节点执行日志提取**。
- 如果用户提供了**服务名**(如 gateway),查找对应行。
- 如果用户提供了**错误码**(如 12005),根据 `错误码起始` 判定所属服务(12000-12999 为 user)。
- 确认目标 `Docker 服务名`(如 g3fo-gateway-service)或 `日志文件路径`(如 /data/logs/g3fo-user/info/info.log)。
- 确认目标 `Server IP`、`Log Method` 以及相关的 `Path/YML`。
### 2. 获取 SSH 凭据
- 检查环境中是否存在凭据(如环境变量)。
- 如果没有,以交互方式询问用户:**SSH 用户名** 和 **密码**(或提醒用户配置私钥)。
- **注意**:不要在对话中存储密码,仅用于当前会话运行脚本。
### 3. 提取远程日志
使用 Python 脚本 `scripts/fetch_logs.py` 抓取日志。
- **全节点提取**: 由于服务部署在多台服务器上(192.168.3.200, 192.168.3.230),**必须同时从所有相关服务器提取日志**,以确保不遗漏错误信息。
- **日志级别选择**:
- 默认抓取 `info` 级别。
- 如果涉及报错排查,优先抓取 `error` 级别。
- **路径拼装**: 严格遵循 `/data/logs/g3fo-{service}/{level}/{level}.log`。
命令模版示例 (需对两台机器分别运行):
```bash
# 对 Server A 运行
python scripts/fetch_logs.py --host 192.168.3.200 --username root --password afe1234 --mode docker --service [SERVICE] --yml-path [YML]
# 对 Server B 运行
python scripts/fetch_logs.py --host 192.168.3.230 --username root --password afe1234 --mode docker --service [SERVICE] --yml-path [YML]
```
### 4. 异常分析与代码定位
- **日志解析**:从获取的日志中提取 `Exception`、`Error` 或 `Caused by` 附近的堆栈信息(Stack Trace)。
- **网络问题判断**:如果日志分析结论为网络连通性问题(如 `Connection refused`, `ConnectTimeoutException`, `UnknownHostException` 等):
- **直接说明**:在报告中直接说明是网络连通性问题。
- **连通性测试**:使用 `nc -zv {IP} {Port}` 或 `telnet {IP} {Port}` 到目标服务器进行测试。
- **无需深度分析**:这种情况下不需要进一步分析源码或 Nacos 配置。
- **源码检索**:提取堆栈中的全限定类名(如 `com.example.service.OrderService`)和行号。
- **配置获取**:如需分析配置(如数据库地址、中间件端口等),使用 `scripts/fetch_configs.py`。
- 示例:`python scripts/fetch_configs.py --data-id redis.yml`
- **本地比对**:使用 `read_file` 或 `codebase_search` 查看本地对应的源码逻辑。
### 5. 输出诊断报告
报告应包含:
- **错误类型**:Java 异常类名。
- **根本原因**:根据日志和代码逻辑分析得出的结论(如果是网络问题,请务必指出)。
- **连通性测试结果**(如适用):展示服务器间网络测试的输出。
- **关联代码**:引用本地源码的相关片段。
- **修复方案**:具体的代码修改建议或配置调整建议。
## 依赖要求
- 本地 Python 环境。
- 安装 `paramiko` 库:`pip install paramiko`。
- 目标服务器支持 SSH 登录。
## 注意事项
- 如果日志文件过大,建议优先使用脚本自带的 `grep` 过滤功能。
- 始终确保本地代码分支与服务器部署版本尽量一致。
@@ -0,0 +1,56 @@
# Service Inventory (服务清单)
此文件定义了服务、错误码、端口以及远程服务器之间的映射关系。Agent 将参考此文件来决定连接哪台服务器以及如何提取日志。
## 服务详细信息表
> **提示**: 所有服务均部署在开发环境的以下两台服务器上:
> - **Server A**: `192.168.3.200` (User: `root`, Pass: `afe1234`)
> - **Server B**: `192.168.3.230` (User: `root`, Pass: `afe1234`)
| 服务名称 | 服务编号 | Redis DB | 错误码起始 | HTTP 端口 | Dubbo 端口 | Docker 服务名 | 日志路径模版 |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| `gateway` | 0 | 0 | 13000 | - | 28000 | `g3fo-gateway-service` | `/data/logs/g3fo-gateway/{level}/{level}.log` |
| `base` | 1 | 1 | 11000 | 18001 | 28001 | `g3fo-base-service` | `/data/logs/g3fo-base/{level}/{level}.log` |
| `user` | 2 | 2 | 12000 | 18002 | 28002 | `g3fo-user-service` | `/data/logs/g3fo-user/{level}/{level}.log` |
| `admin` | 3 | 3 | 15000 | 18003 | 28003 | `g3fo-admin-service` | `/data/logs/g3fo-admin/{level}/{level}.log` |
| `push` | 4 | 0 | nul | 18004 | - | `g3fo-push-service` | `/data/logs/g3fo-push/{level}/{level}.log` |
| `product` | 6 | 6 | 16000 | 18006 | 28006 | `g3fo-product-service` | `/data/logs/g3fo-product/{level}/{level}.log` |
| `trade` | 7 | 7 | 17000 | 18007 | 28007 | `g3fo-trade-service` | `/data/logs/g3fo-trade/{level}/{level}.log` |
| `notification` | 8 | 8 | 18000 | 18008 | 28008 | `g3fo-notification-service` | `/data/logs/g3fo-notification/{level}/{level}.log` |
| `dx` | 9 | 9 | 19000 | 18009 | 28009 | `g3fo-dx-service` | `/data/logs/g3fo-dx/{level}/{level}.log` |
| `margin` | 10 | 10 | 20000 | 18010 | 28010 | `g3fo-margin-service` | `/data/logs/g3fo-margin/{level}/{level}.log` |
| `utility` | - | - | 21000 | 18012 | 28012 | `g3fo-utility-service` | `/data/logs/g3fo-utility/{level}/{level}.log` |
## 日志获取规则
- **开发环境 (Dev)**: 包含 `192.168.3.200` 和 `192.168.3.230`。
- **Docker 模式**: 服务名遵循 `g3fo-{service}-service` 规则。
- 例如: `gateway` -> `g3fo-gateway-service`
- **文件模式**: 日志路径遵循 `/data/logs/g3fo-{service}/{level}/{level}.log` 规则。
- **支持级别**: `debug`, `error`, `info`, `warn`
## 运维配置参考
- **错误码范围**: 每个服务的错误码通常从“起始编号”开始,步长为 1000(例如 gateway 为 10000-10999)。
- **执行逻辑**: 由于无法确定报错发生的具体机器,**Agent 在接收到指令后应自动依次连接 Server A 和 Server B 提取日志**,并汇总分析。
## 默认服务器配置
- **SSH 目标**: `192.168.3.200`, `192.168.3.230`
- **凭据**: `root / afe1234`
- **Docker Compose 路径**: `/data/docker-compose/g3fo-docker-compose.yml` (默认路径)
## Nacos 配置获取 (HTTP 方式)
可以通过 Nacos 的 OpenAPI 获取配置信息,用于分析配置项(如数据库连接、Redis 地址等)。
- **基础 URL 示例**: `http://{IP}:8848/nacos/v1/cs/configs`
- **默认参数**:
- `tenant`: `dev`
- `group`: `DEFAULT_GROUP`
- **常用 Data ID 列表**:
- 服务专用配置: `g3fo-user-dev.yml`, `g3fo-gateway-dev.yml`, `g3fo-notification-dev.yml` 等
- 公共组件配置: `common.yml`, `dubbo.yml`, `mysql.yml`, `rocketmq.yml`, `redis.yml`
- **获取逻辑**:
1. 依次尝试 Server A (`192.168.3.200`) 和 Server B (`192.168.3.230`)。
2. 如果第一台机器获取成功,则不再尝试第二台。
3. 如果两台都获取失败,则报错。
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
import sys
import argparse
import urllib.request
import urllib.error
def fetch_config(ip, data_id, group, tenant):
url = f"http://{ip}:8848/nacos/v1/cs/configs?dataId={data_id}&group={group}&tenant={tenant}"
print(f"Trying to fetch config from {url}...")
try:
with urllib.request.urlopen(url, timeout=5) as response:
if response.status == 200:
return response.read().decode('utf-8')
except urllib.error.URLError as e:
print(f"Failed to connect to {ip}: {e}")
except Exception as e:
print(f"Error fetching config from {ip}: {e}")
return None
def main():
parser = argparse.ArgumentParser(description="Fetch configuration from Nacos via HTTP.")
parser.add_argument("--hosts", nargs='+', default=["192.168.3.200", "192.168.3.230"], help="List of Nacos server IPs")
parser.add_argument("--data-id", required=True, help="Nacos Data ID (e.g., redis.yml)")
parser.add_argument("--group", default="DEFAULT_GROUP", help="Nacos Group (default: DEFAULT_GROUP)")
parser.add_argument("--tenant", default="dev", help="Nacos Tenant (default: dev)")
args = parser.parse_args()
config_content = None
for ip in args.hosts:
config_content = fetch_config(ip, args.data_id, args.group, args.tenant)
if config_content:
print(f"Successfully fetched config from {ip}")
break
if config_content:
print("-" * 40)
print(f"CONTENT OF {args.data_id}:")
print("-" * 40)
print(config_content)
print("-" * 40)
else:
print(f"Failed to fetch config '{args.data_id}' from all provided hosts.")
sys.exit(1)
if __name__ == "__main__":
main()
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
import sys
import argparse
import paramiko
import os
def fetch_logs(args):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
# Connect to the server
print(f"Connecting to {args.host}...")
ssh.connect(
hostname=args.host,
username=args.username,
password=args.password,
timeout=10
)
command = ""
if args.mode == "docker":
since_flag = f'--since "{args.since}"' if args.since else ""
until_flag = f'--until "{args.until}"' if args.until else ""
tail_flag = f"--tail={args.lines}" if not args.since else ""
# docker-compose logs command
command = f"docker-compose -f {args.yml_path} logs --no-log-prefix {since_flag} {until_flag} {tail_flag} {args.service}"
else:
# File mode
if args.since or args.until:
# Time-based filtering for file mode using awk
since_val = args.since if args.since else ""
until_val = args.until if args.until else ""
# Convert relative time (e.g., 10m) to absolute timestamp on server
if since_val and any(since_val.endswith(suffix) for suffix in ['s', 'm', 'h', 'd']) and since_val[:-1].isdigit():
unit_map = {'s': 'seconds', 'm': 'minutes', 'h': 'hours', 'd': 'days'}
unit = unit_map[since_val[-1]]
num = since_val[:-1]
since_expr = f"\"$(date -d '{num} {unit} ago' '+%Y-%m-%d %H:%M:%S')\""
elif since_val:
since_expr = f"'{since_val}'"
else:
since_expr = "''"
until_expr = f"'{until_val}'" if until_val else "''"
# awk script that extracts timestamp and compares
# It maintains 'in_range' state for multi-line logs (like stack traces)
awk_script = (
f"awk -v since={since_expr} -v until={until_expr} '"
"BEGIN { in_range = (since == \"\"); } "
"{ "
" if (match($0, /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/)) { "
" current_time = substr($0, RSTART, 19); "
" in_range = (since == \"\" || current_time >= since) && (until == \"\" || current_time <= until); "
" } "
" if (in_range) print $0; "
"}' " + args.log_path
)
command = awk_script
else:
# Default tail + grep for errors
command = f"tail -n {args.lines} {args.log_path} | grep -E 'Exception|Error|Caused by' -C 50"
print(f"Executing command: {command}")
stdin, stdout, stderr = ssh.exec_command(command)
output = stdout.read().decode('utf-8', errors='replace')
error = stderr.read().decode('utf-8', errors='replace')
if error and not output:
print(f"Error from server:\n{error}")
return
if not output:
print("No matching logs found.")
return
# Print a header for the output
print("-" * 40)
print(f"LOGS FROM {args.host} ({args.service if args.mode == 'docker' else args.log_path})")
print("-" * 40)
print(output)
print("-" * 40)
except Exception as e:
print(f"Failed to fetch logs: {str(e)}")
finally:
ssh.close()
def main():
parser = argparse.ArgumentParser(description="Fetch logs from a remote server via SSH.")
parser.add_argument("--host", required=True, help="Server IP or hostname")
parser.add_argument("--username", required=True, help="SSH username")
parser.add_argument("--password", help="SSH password")
parser.add_argument("--mode", choices=["docker", "file"], required=True, help="Log extraction mode")
parser.add_argument("--service", help="Docker service name (for docker mode)")
parser.add_argument("--yml-path", help="Path to docker-compose.yml (for docker mode)")
parser.add_argument("--log-path", help="Path to log file (for file mode)")
parser.add_argument("--lines", type=int, default=1000, help="Number of lines to fetch/analyze (default 1000)")
parser.add_argument("--since", help="Filter logs since this time (e.g., '10m', '2026-01-16 10:10:00')")
parser.add_argument("--until", help="Filter logs until this time (e.g., '2026-01-16 10:20:00')")
args = parser.parse_args()
if args.mode == "docker" and not (args.service and args.yml_path):
parser.error("docker mode requires --service and --yml-path")
if args.mode == "file" and not args.log_path:
parser.error("file mode requires --log-path")
fetch_logs(args)
if __name__ == "__main__":
main()