Loki MCP Server如何自然语言查询Claude Desktop日志?
摘要:MCP定位,技术栈,架构,项目结构,基础框架搭建,开发部署及常见问题 # Loki MCP Server - CLAUDE.md > Go 实现的 MCP Server,集成 Grafana Loki 日志查询。支持
MCP定位,技术栈,架构,项目结构,基础框架搭建,开发部署及常见问题
# Loki MCP Server - CLAUDE.md
> Go 实现的 MCP Server,集成 Grafana Loki 日志查询。支持 Claude Desktop / Claude Code / Cursor 等客户端通过自然语言查询日志。
---
## 一、项目概述
### 定位
将 Grafana Loki 的日志查询能力通过 MCP (Model Context Protocol) 暴露给 AI 助手,运维人员可以用自然语言代替 LogQL 查询日志。
### 技术栈
| 层 | 技术 | 版本 |
|---|------|------|
| 语言 | Go | 1.24+ |
| MCP SDK | github.com/mark3labs/mcp-go | v0.32.0 |
| 日志存储 | Grafana Loki | 2.9.0 |
| 日志采集 | Promtail | 2.9.0 |
| 可视化 | Grafana | latest |
| 容器化 | Docker + Compose | - |
### 核心能力(3 个 MCP Tool)
| Tool | 用途 | Loki API |
|------|------|----------|
| `loki_query` | 执行 LogQL 查询 | `/loki/api/v1/query_range` |
| `loki_label_names` | 获取所有标签名 | `/loki/api/v1/labels` |
| `loki_label_values` | 获取标签值列表 | `/loki/api/v1/label/{name}/values` |
---
## 二、架构设计
### 整体架构
```
┌─────────────────┐ ┌──────────────────┐ ┌──────────────┐
│ AI Client │ │ Loki MCP Server │ │ Grafana │
│ (Claude Code/ │─────→│ :8080 │─────→│ Loki │
│ Desktop/ │ MCP │ │ HTTP │ :3100 │
│ Cursor) │ │ 3 种传输协议: │ │ │
└─────────────────┘ │ - stdio │ └──────────────┘
│ - SSE (/sse) │
│ - HTTP (/stream)│
│ │
│ /healthz (K8s) │
└──────────────────┘
```
### 三种传输协议(同端口共存)
| 协议 | 端点 | 场景 |
|------|------|------|
| stdio | 标准输入输出 | 本地 binary / Docker 集成,Claude Desktop 直接启动进程 |
| SSE | `/sse` + `/mcp` | Server-Sent Events,远程连接(旧协议) |
| Streamable HTTP | `/stream` | 新一代 MCP 远程协议(推荐) |
**设计要点**:通过 `http.ServeMux` 将 SSE 和 Streamable HTTP 注册到同一端口,stdio 作为后台 goroutine 并行运行。
### 项目结构
```
loki-mcp/
├── cmd/
│ ├── server/main.go # 入口:注册 Tool + 启动 3 种传输
│ └── client/main.go # JSON-RPC 测试客户端
├── internal/
│ └── handlers/
│ ├── loki.go # 核心:3 个 Tool 的完整实现 (993 行)
│ └── loki_test.go # 单元测试 (261 行)
├── pkg/
│ └── utils/logger.go # 简易日志工具
├── grafana/
│ └── provisioning/datasources/loki.yaml # Grafana 数据源预配置
├── promtail/
│ └── config.yml # 日志采集配置
├── examples/
│ ├── claude-desktop/ # 4 种 Claude Desktop 配置示例
│ ├── claude-code-commands/ # Slash Command 模板
│ ├── simple-sse-client.html # SSE 测试页面
│ └── sse-client.html # 完整 SSE 客户端
├── docker-compose.yml # 本地 5 服务开发环境
├── Dockerfile # 多阶段构建
├── Makefile # 构建/测试/运行
├── go.mod / go.sum
├── run-mcp-server.sh # 启动脚本
├── test-loki-query.sh # 查询测试脚本
├── insert-loki-logs.sh # 插入测试日志
└── README.md
```
---
## 三、从零实现指南
### Phase 1:基础框架搭建
#### 1.1 初始化 Go 项目
```bash
mkdir loki-mcp && cd loki-mcp
go mod init github.com/yourname/loki-mcp
go get github.com/mark3labs/mcp-go@v0.32.0
```
#### 1.2 理解 MCP Server 入口 (`cmd/server/main.go`)
核心模式:**创建 Server → 注册 Tool → 启动传输层**
```go
// 1. 创建 MCP Server 实例
s := server.NewMCPServer(
"Loki MCP Server", "0.1.0",
server.WithResourceCapabilities(true, true),
server.WithLogging(),
)
// 2. 注册工具(Tool 定义 + Handler 函数)
lokiQueryTool := handlers.NewLokiQueryTool()
s.AddTool(lokiQueryTool, handlers.HandleLokiQuery)
// 3. 创建传输层
sseServer := server.NewSSEServer(s,
server.WithSSEEndpoint("/sse"),
server.WithMessageEndpoint("/mcp"),
)
streamableServer := server.NewStreamableHTTPServer(s)
// 4. 统一路由
mux := http.NewServeMux()
mux.Handle("/sse", sseServer)
mux.Handle("/mcp", sseServer)
mux.Handle("/stream", streamableServer)
mux.HandleFunc("/healthz", healthHandler)
// 5. 并行启动 HTTP + stdio
go http.ListenAndServe(":8080", mux)
go server.ServeStdio(s)
// 6. 优雅关闭
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
<-stop
```
**关键设计决策**:
- 三种传输协议同端口:简化部署,一个端口搞定所有客户端
- stdio 后台运行:兼容 Claude Desktop 的进程模式
- `/healthz` 端点:适配 K8s readiness/liveness probe
#### 1.3 理解 Tool 定义模式
每个 Tool 由两部分组成:
1. **Tool 定义函数** (`NewXxxTool()`) — 声明参数 schema
2. **Handler 函数** (`HandleXxx()`) — 处理请求逻辑
```go
// Tool 定义:声明参数、类型、默认值、描述
func NewLokiQueryTool() mcp.Tool {
return mcp.NewTool("loki_query",
mcp.WithDescription("Run a query against Grafana Loki"),
mcp.WithString("query", mcp.Required(), mcp.Description("LogQL query string")),
mcp.WithString("url", mcp.Description("Loki server URL"), mcp.DefaultString(lokiURL)),
mcp.WithString("start", mcp.Description("Start time (default: 1h ago)")),
mcp.WithNumber("limit", mcp.Description("Max entries (default: 100)")),
mcp.WithString("format", mcp.DefaultString("raw")),
// ... 认证参数
)
}
// Handler:提取参数 → 构建请求 → 调用 Loki API → 格式化输出
func HandleLokiQuery(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
args := request.GetArguments()
// ... 处理逻辑
return mcp.NewToolResultText(formattedResult), nil
}
```
### Phase 2:核心逻辑实现
#### 2.1 请求处理流程 (`internal/handlers/loki.go`)
```
参数提取 → 环境变量回退 → 时间解析 → URL构建 → HTTP请求 → 响应解析 → 格式化输出
```
#### 2.2 参数提取模式(带环境变量回退)
每个参数都遵循:**请求参数 > 环境变量 > 默认值** 的优先级链
```go
// 统一模式:先看请求参数,再看环境变量
var lokiURL string
if urlArg, ok := args["url"].(string); ok && urlArg != "" {
lokiURL = urlArg
} else {
lokiURL = os.Getenv("LOKI_URL")
if lokiURL == "" {
lokiURL = "http://localhost:3100"
}
}
```
环境变量清单:
| 变量 | 用途 | 默认值 |
|------|------|--------|
| `LOKI_URL` | Loki 地址 | `http://localhost:3100` |
| `LOKI_ORG_ID` | 租户 ID | 空 |
| `LOKI_USERNAME` | Basic Auth 用户名 | 空 |
| `LOKI_PASSWORD` | Basic Auth 密码 | 空 |
| `LOKI_TOKEN` | Bearer Token | 空 |
| `PORT` | 服务端口 | `8080` |
#### 2.3 时间解析 (`parseTime`)
支持多种输入格式,按顺序尝试:
```go
func parseTime(timeStr string) (time.Time, error) {
// 1. "now" 关键字
if timeStr == "now" { return time.Now(), nil }
// 2. 相对时间: "-1h", "-30m"
if timeStr[0] == '-' {
duration, err := time.ParseDuration(timeStr)
if err == nil { return time.Now().Add(duration), nil }
}
// 3. RFC3339: "2024-01-15T10:30:45Z"
// 4. ISO 变体: "2006-01-02T15:04:05", "2006-01-02 15:04:05"
// 5. 纯日期: "2006-01-02"
}
```
#### 2.4 URL 构建
智能路径拼接,处理各种 base URL 格式:
```go
func buildLokiQueryURL(baseURL, query string, start, end int64, limit int) (string, error) {
u, _ := url.Parse(baseURL)
// 路径规范化:避免重复拼接
if !strings.Contains(u.Path, "loki/api/v1") {
u.Path = "/loki/api/v1/query_range"
}
// 查询参数
q := u.Query()
q.Set("query", query) // LogQL
q.Set("start", fmt.Sprintf("%d", start)) // Unix 秒
q.Set("end", fmt.Sprintf("%d", end))
q.Set("limit", fmt.Sprintf("%d", limit))
u.RawQuery = q.Encode()
return u.String(), nil
}
```
#### 2.5 认证机制
三级认证,优先级:Bearer Token > Basic Auth > 无认证
```go
if token != "" {
req.Header.Add("Authorization", "Bearer "+token)
} else if username != "" || password != "" {
req.SetBasicAuth(username, password)
}
// 多租户隔离(始终添加如果有值)
if orgID != "" {
req.Header.Add("X-Scope-OrgID", orgID)
}
```
#### 2.6 响应数据结构
```go
type LokiResult struct {
Status string `json:"status"` // "success" | "error"
Data LokiData `json:"data"`
Error string `json:"error,omitempty"`
}
type LokiData struct {
ResultType string `json:"resultType"` // "streams"
Result []LokiEntry `json:"result"`
}
type LokiEntry struct {
Stream map[string]string `json:"stream"` // 标签: {job: "xx", pod: "xx"}
Values [][]string `json:"values"` // [[纳秒时间戳, 日志行], ...]
}
```
#### 2.7 三种输出格式
| 格式 | 用途 | 示例 |
|------|------|------|
| `raw`(默认) | AI 解析友好,最紧凑 | `2024-01-15T10:30:45Z {job=api} Request received` |
| `json` | 程序化处理 | 完整 JSON 结构 |
| `text` | 人类可读 | 编号 Stream + 时间戳日志行 |
#### 2.8 已知 Bug 及修复
**时间戳 2262 年 Bug**:Loki 返回纳秒时间戳,早期实现使用 `time.Unix(ts, 0)` 将纳秒当秒处理,导致显示 2262 年。
修复:`time.Unix(0, int64(ts))` — 第一个参数为 0 秒,第二个参数为纳秒。
### Phase 3:Docker 化与本地环境
#### 3.1 Dockerfile(多阶段构建)
```dockerfile
# Stage 1: 编译
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download # 利用缓存层
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o loki-mcp-server ./cmd/server
# Stage 2: 运行(最小镜像)
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/loki-mcp-server .
EXPOSE 8080
ENTRYPOINT ["./loki-mcp-server"]
```
**关键点**:
- `CGO_ENABLED=0`:静态链接,不依赖 glibc
- 先 COPY go.mod/go.sum → `go mod download`:利用 Docker 层缓存加速构建
- 最终镜像基于 `alpine:latest`:最小化攻击面
#### 3.2 Docker Compose(5 服务完整环境)
```yaml
services:
loki-mcp-server: # MCP Server :8080
depends_on:
loki:
condition: service_healthy # 等 Loki 就绪
loki: # 日志存储 :3100
healthcheck: # /ready 端点检查
test: ["CMD-SHELL", "wget -q --spider http://localhost:3100/ready || exit 1"]
grafana: # 可视化 :3000
volumes:
- ./grafana/provisioning:/etc/grafana/provisioning # 预配置数据源
promtail: # 日志采集
volumes:
- /var/log:/var/log # 采集宿主机日志
- /var/run/docker.sock:/var/run/docker.sock # 采集容器日志
log-generator: # 测试日志生成器
command: | # 每 5 秒生成 INFO/ERROR 日志
while true; do echo "INFO: ..."; sleep 5; done
```
**服务依赖链**:`log-generator → promtail → loki → loki-mcp-server`,Grafana 独立。
### Phase 4:测试策略
#### 4.1 单元测试
重点覆盖时间戳解析(bug 高发区):
```go
func TestFormatLokiResults_NoYear2262Bug(t *testing.T) {
testCases := []struct {
name string
timestampNs string
expectedYear string
}{
{"Current", "1705312245000000000", "2024"}, // 2024-01-15
{"Recent", "1700000000000000000", "2023"}, // 2023-11-14
{"Future", "1800000000000000000", "2027"}, // 2027-01-11
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// 构建 LokiResult → formatLokiResults → 断言年份
})
}
}
```
**测试用例覆盖**:
- 正常时间戳解析
- 多条日志时间戳
- 非法时间戳回退
- 空结果处理
- 当前时间回归
- 2262 年 Bug 回归(表驱动,3 个时间点)
#### 4.2 运行测试
```bash
make test # 全部测试
go test -coverprofile=coverage.out ./... # 带覆盖率
go tool cover -func=coverage.out # 查看覆盖率
go test -race ./... # 竞态检测
```
#### 4.3 集成测试脚本
```bash
# 插入测试日志
./insert-loki-logs.sh --num 20 --job "custom-job" --app "my-app"
# 查询验证
./test-loki-query.sh '{job="varlogs"}'
./test-loki-query.sh '{job="varlogs"} |= "ERROR"' '-1h' 'now' 50
```
### Phase 5:部署与客户端集成
#### 5.1 部署方式选择
| 方式 | 场景 | 命令 |
|------|------|------|
| 本地二进制 | 开发调试 | `make run` |
| Docker 单容器 | 已有 Loki 实例 | `docker run -p 8080:8080 -e LOKI_URL=... loki-mcp-server` |
| Docker Compose | 完整本地环境 | `docker-compose up --build` |
| K8s Deployment | 生产环境 | 见下方 manifest |
| 远程 URL | 团队共享 | `https://loki-mcp.loki.com/stream` |
#### 5.2 K8s 部署建议
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: loki-mcp-server
spec:
replicas: 2 # 无状态,可水平扩展
template:
spec:
containers:
- name: loki-mcp-server
image: loki-mcp-server:v0.1.0 # 固定版本
ports:
- containerPort: 8080
env:
- name: LOKI_URL
value: "http://loki-gateway.monitoring:3100"
- name: LOKI_TOKEN
valueFrom:
secretKeyRef:
name: loki-auth
key: token
readinessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 30
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
```
#### 5.3 客户端接入
**Claude Code(推荐 Streamable HTTP)**:
```bash
claude mcp add --transport http --scope user loki https://loki-mcp.loki.com/stream
```
**Claude Desktop(本地 Docker)**:
```json
{
"mcpServers": {
"loki": {
"command": "docker",
"args": ["run", "--rm", "-i", "-e", "LOKI_URL=http://host.docker.internal:3100", "loki-mcp-server:latest"]
}
}
}
```
**Cursor**:
```json
{
"mcpServers": {
"loki": {
"command": "docker",
"args": ["run", "--rm", "-i", "-e", "LOKI_URL=http://host.docker.internal:3100", "loki-mcp-server:latest"]
}
}
}
```
---
## 四、开发命令速查
```bash
# 构建
make build # 编译本地二进制
make build-linux # 交叉编译 Linux (amd64)
docker build -t loki-mcp-server . # 构建镜像
# 运行
make run # 本地运行
docker-compose up --build # 完整本地环境
# 测试
make test # 单元测试
go test -race ./... # 竞态检测
./test-loki-query.sh # 集成测试
./insert-loki-logs.sh # 插入测试数据
# 依赖
make deps # 下载依赖
make tidy # 整理 go.mod
# 清理
make clean # 删除二进制
docker-compose down -v # 清理容器和数据卷
```
---
## 五、代码规范
### Go 规范
- `gofmt` / `goimports` 格式化
- 错误处理:`fmt.Errorf("context: %w", err)` 包装
- 环境变量用 `const` 定义名称,避免硬编码字符串
- HTTP 客户端必须设置 `Timeout`(当前 30s)
- 使用 `context.Context` 传递请求上下文
### MCP Tool 开发规范
- Tool 名称用 `snake_case`(如 `loki_query`)
- 必填参数用 `mcp.Required()` 标记
- 每个参数都要有 `mcp.Description()`,包含默认值说明
- 环境变量回退必须在 Handler 中实现(而非仅 Tool 定义中)
- 返回 `mcp.NewToolResultText()` 作为标准响应
### 新增 Tool 模板
如果需要添加新工具(如 `loki_series`),遵循以下步骤:
1. 在 `internal/handlers/loki.go` 中添加:
- `NewLokiSeriesTool()` — 定义参数
- `HandleLokiSeries()` — 实现逻辑
- `buildLokiSeriesURL()` — URL 构建
- `executeLokiSeriesQuery()` — HTTP 请求
- `formatLokiSeriesResults()` — 格式化输出
2. 在 `cmd/server/main.go` 中注册:
```go
seriesTool := handlers.NewLokiSeriesTool()
s.AddTool(seriesTool, handlers.HandleLokiSeries)
```
3. 在 `internal/handlers/loki_test.go` 中添加测试
---
## 六、运维关注点
### 可观测性
- `/healthz` 端点返回 `ok`,适配 K8s probe
- 服务本身无状态,无需持久化存储
- 启动日志输出所有端点地址
- 建议:生产环境添加 Prometheus metrics 端点(`/metrics`)
### 安全
- 认证信息通过环境变量注入,不写入镜像层
- Bearer Token 优先于 Basic Auth
- 多租户通过 `X-Scope-OrgID` 头隔离
- HTTP 超时 30s 防止慢查询阻塞
### 扩展方向
| 方向 | 说明 |
|------|------|
| 添加 `/metrics` | Prometheus 指标暴露 |
| 添加 `loki_series` Tool | 查询 series 元数据 |
| 添加 `loki_stats` Tool | 查询 ingester 统计 |
| 提取认证参数公共函数 | 消除 3 个 Handler 中的重复代码 |
| 添加请求日志中间件 | 记录每次 Tool 调用的查询和耗时 |
| 支持 TLS | HTTPS 终止或证书配置 |
| 添加 rate limiting | 防止 AI 频繁查询 |
---
## 七、学习路径建议
```
第 1 天:跑起来
├── docker-compose up --build
├── 访问 Grafana :3000 理解 Loki 数据结构
├── ./insert-loki-logs.sh 插入测试数据
└── ./test-loki-query.sh 验证查询
第 2 天:读懂入口
├── cmd/server/main.go (100 行) — MCP 注册 + 传输层
├── 理解 mcp-go SDK 的 AddTool 模式
└── 理解三协议同端口架构
第 3 天:精读核心
├── internal/handlers/loki.go (993 行)
├── 参数提取 → 环境变量回退模式
├── parseTime 多格式时间解析
├── buildLokiQueryURL 路径规范化
├── executeLokiQuery HTTP + 认证
└── formatLokiResults 三种输出格式
第 4 天:测试与 Bug
├── internal/handlers/loki_test.go
├── 纳秒时间戳 2262 年 Bug 的前因后果
└── Go 表驱动测试风格
第 5 天:Docker + 部署
├── Dockerfile 多阶段构建
├── docker-compose.yml 服务编排
├── K8s 部署清单设计
└── 客户端集成配置
第 6 天:动手扩展
├── 尝试添加 loki_series Tool
├── 提取认证参数公共函数(消除重复)
└── 添加 /metrics 端点
```
---
## 八、常见问题
### 连接失败
1. `claude mcp get loki` 检查配置
2. 确认网络连通性
3. 检查 HTTPS 证书
### 查询无结果
1. 确认 Loki 有对应时间范围的数据
2. 多租户场景检查 `org_id`
3. 用 `loki_label_names` 先查可用标签
### Docker 环境问题
1. Loki 启动需要时间,等 healthcheck 通过
2. Mac 上 Docker 访问宿主机用 `host.docker.internal`
3. `docker-compose down -v` 可清理数据重来
### 时间戳显示异常
- 确认使用 `time.Unix(0, int64(ns))` 而非 `time.Unix(ns, 0)`
- Loki 返回纳秒时间戳,不是秒
使用测试
▗ ▗ ▖ ▖ Claude Code v2.1.96
Opus 4.6 (1M context) with medium effort · API Usage Billing
▘▘ ▝▝ ~/loki-mcp
⎿ SessionStart:startup hook error
❯ /mcp
⎿ MCP dialog dismissed
❯ loki查看所有可用的标签名
⏺ loki - loki_label_names (MCP)
⏺ Loki 当前可用的标签名:
┌────────────────┬─────────────────────────┐
│ 标签 │ 常见用途 │
├────────────────┼─────────────────────────┤
│ app │ 应用名称 │
├────────────────┼─────────────────────────┤
│ broker │ 消息代理 │
├────────────────┼─────────────────────────┤
│ component │ 组件标识 │
├────────────────┼─────────────────────────┤
│ container │ 容器名 │
├────────────────┼─────────────────────────┤
│ env │ 环境 (prod/staging/dev) │
├────────────────┼─────────────────────────┤
│ filename │ 日志文件路径 │
├────────────────┼─────────────────────────┤
│ host │ 主机名 │
├────────────────┼─────────────────────────┤
│ instance │ 实例地址 │
├────────────────┼─────────────────────────┤
│ job │ Prometheus job 名称 │
├────────────────┼─────────────────────────┤
│ namespace │ K8s 命名空间 │
├────────────────┼─────────────────────────┤
│ node_name │ K8s 节点名 │
├────────────────┼─────────────────────────┤
│ pod │ Pod 名称 │
├────────────────┼─────────────────────────┤
│ request_method │ HTTP 请求方法 │
├────────────────┼─────────────────────────┤
│ service_name │ 服务名称 │
├────────────────┼─────────────────────────┤
│ site │ 站点标识 │
├────────────────┼─────────────────────────┤
│ status │ 状态码 │
├────────────────┼─────────────────────────┤
│ stream │ stdout/stderr │
├────────────────┼─────────────────────────┤
│ uri │ 请求路径 │
└────────────────┴─────────────────────────┘
需要查看某个标签的具体值,可以告诉我,比如 namespace 有哪些值。
❯ 查询app=pb-trading-engine env=prodjp 的近5分钟的错误日志,帮我分析下
⏺ loki - loki_query (MCP)(query: "{app=\"pb-trading-engine\", env=\"prodjp\"} |~ `(?i)error|exception|fatal|panic`", start: "5m", limit: 200)
⎿ Error: MCP error -32603: invalid start time: unsupported time format: 5m
⏺ loki - loki_query (MCP)(query: "{app=\"pb-trading-engine\", env=\"prodjp\"} |~ `(?i)error|exception|fatal|panic`", start: "2026-04-08T15:00:00Z", end: "2026-04-08T15:05:00Z", limit: 200)
⏺ 时间格式需要调整,让我用 UTC 时间重试(当前北京时间约 15:05,即 UTC 07:05)。
⏺ loki - loki_query (MCP)(query: "{app=\"pb-trading-engine\", env=\"prodjp\"} |~ `(?i)error|exception|fatal|panic`", start: "2026-04-08T07:00:00Z", end: "2026-04-08T07:05:00Z", limit: 200)
⏺ 近 5 分钟没有匹配到错误日志。让我先确认标签值是否正确,以及扩大时间范围看看。
⏺ loki - loki_label_values (MCP)(label: "app")
⏺ loki - loki_label_values (MCP)(label: "env")
⏺ 标签值确认正确:app=pb-trading-engine 和 env=prodjp 都存在。让我扩大时间范围查询。
⏺ loki - loki_query (MCP)(query: "{app=\"pb-trading-engine\", env=\"prodjp\"} |~ `(?i)error|exception|fatal|panic`", start: "2026-04-08T06:00:00Z", end: "2026-04-08T07:10:00Z", limit: 200)
⎿ Error: result (73,209 characters) exceeds maximum allowed tokens. Output has been saved to /Users/example/.claude/projects/-Users-example-ltp-codes-ops-agent/0d50fb2b-2b85-4322-9f3a-72d75fc427
b3/tool-results/mcp-loki-loki_query-1111111.txt.
Format: JSON array with schema: [{type: string, text: string}]
… +7 lines (ctrl+o to expand)
⏺ 查到大量错误日志,让我逐段读取分析。
Searched for 1 pattern, read 1 file, searched memories (ctrl+o to expand)
⏺ 文件是 JSON 格式,用 jq 解析。
⏺ Bash(cat /Users/example/.claude/projects/-Users-example-ltp-codes-ops-agent/0d50fb2b-2b85-4322-9f3a-72d75fc427b3/tool-results/mcp-loki-loki_query-111111.txt |…)
⎿ 2026-04-08T06:39:55Z {service_name=pb-trading-engine,app=pb-trading-engine,detected_level=info,env=prodjp,filename=/data/logs/pb-trading-engine/info.log,host=10.19.5.136} 2026-04-08 06:39:54.9
62 [disruptor-worker9] [INFO ] [TraceId=11111111] com.ltp.pb.engine.service.biz.PlacePerpOrderService - placePerpOrder TRADE_MAX_ORDERS_ERROR. oid:6666666,count:60,maxNum:6
0
… +24 lines (ctrl+o to expand)
⏺ Bash(cat /Users/example/.claude/projects/-Users-example-ltp-codes-ops-agent/0d50fb2b-2b85-4322-9f3a-72d75fc427b3/tool-results/mcp-loki-loki_query-222222`````.txt |…)
⎿ 总日志条数: 172
=== 错误类型分布 ===
… +24 lines (ctrl+o to expand)
⏺ Bash(cat /Users/example/.claude/projects/-Users-example-ltp-codes-ops-agent/0d50fb2b-2b85-4322-9f3a-72d75fc427b3/tool-results/mcp-loki-loki_query-333333.txt |…)
⎿ === 非 TRADE_MAX_ORDERS_ERROR 的日志 ===
2026-04-08T06:25:17Z {app=pb-trading-engine,detected_level=error,env=prodjp,filename=/data/logs/pb-trading-engine/warn.log,host=10.19.5.136,service_name=pb-trading-engine} 2026-04-08 06:25:12.
… +34 lines (ctrl+o to expand)
⏺ pb-trading-engine (prodjp) 错误日志分析
时间范围: 2026-04-08 06:03 ~ 06:43 UTC(约 14:03 ~ 14:43 JST)
总日志条数: 172 条
---
1. 错误类型分布
┌───────────────────────────────────────────────────────┬──────┬───────┬──────────┐
│ 错误类型 │ 数量 │ 占比 │ 严重程度 │
├───────────────────────────────────────────────────────┼──────┼───────┼──────────┤
│ TRADE_MAX_ORDERS_ERROR │ 158 │ 91.9% │ MEDIUM │
├───────────────────────────────────────────────────────┼──────┼───────┼──────────┤
│ LiqService - tryTriggerLiqHandle account status error │ 9 │ 5.2% │ HIGH │
├───────────────────────────────────────────────────────┼──────┼───────┼──────────┤
│ LiqService - cancel all/open exceed limit │ 2 │ 1.2% │ HIGH │
├───────────────────────────────────────────────────────┼──────┼───────┼──────────┤
│ PostWorkerHandler - lark alert (飞书告警) │ 2 │ 1.2% │ INFO │
├───────────────────────────────────────────────────────┼──────┼───────┼──────────┤
│ OrderMonitor - ems not exist │ 1 │ 0.6% │ MEDIUM │
└───────────────────────────────────────────────────────┴──────┴───────┴──────────┘
---
2. 逐类分析
(1) TRADE_MAX_ORDERS_ERROR — 158 条 (占 91.9%)
PlacePerpOrderService - placePerpOrder TRADE_MAX_ORDERS_ERROR. oid:xxx, count:60, maxNum:60
- 原因: 某账户的永续合约挂单数 (count=60) 达到最大限制 (maxNum=60),新单被拒绝
- 主机: 集中在 10.19.5.136(161 条)
- 时间: 呈周期性爆发,每隔约 5-6 分钟出现一波(06:03, 06:09, 06:15, 06:22, 06:26, 06:39)
- 推测: 某个做市/策略账户在高频下单,每次触发限制后短暂停顿再重试,形成周期性模式
- 影响: INFO 级别,不影响系统稳定性,但说明客户端策略未处理好限频
(2) LiqService 清算状态异常 — 9 条 (HIGH)
tryTriggerLiqHandle account status error oldAccountStatus=NORMAL/MARGIN_CALL/CANCEL_ALL/CANCEL_OPEN
- 涉及用户: userId=123456,3 个 portfolioId:
- 11111 (BINANCE) — equity ≈ 19.6 USDT
- 22222 (BINANCE) — equity ≈ 35.2 USDT
- 33333 (BINANCE) — equity ≈ 1.0~1.5 USDT
- 原因: 清算引擎尝试触发清算处理时,账户状态不符合预期(如:想从 NORMAL 转到清算状态时发现状态不一致)
- 关注点: portfolioId 33333 的 equity 极低(仅 ~1 USDT),经历了完整的清算流程 MARGIN_CALL → CANCEL_OPEN → CANCEL_ALL
(3) cancel all/open exceed limit — 2 条 (HIGH)
cancel all exceed limit, portfolioId 33333
cancel open exceed limit, portfolioId 33333
- 原因: 同一 portfolioId 33333 的清算撤单操作频率超过限制
- 关联: 与上面的 LiqService 状态异常是同一账户,清算过程中多次尝试撤单被限频
(4) OrderMonitor - ems not exist — 1 条
orderStateMonitor ems not exist. pid:11111, oid:666666, engine:OPEN
- 原因: 订单状态监控发现某订单的 EMS(执行管理系统)记录不存在
- 可能: 订单创建与 EMS 注册之间的时序问题,或 EMS 记录已被清理
---
3. 主机分布
┌──────────────┬──────┬─────────────────────────────┐
│ Host │ 条数 │ 主要错误 │
├──────────────┼──────┼─────────────────────────────┤
│ 10.19.5.136 │ 161 │ TRADE_MAX_ORDERS_ERROR 为主 │
├──────────────┼──────┼─────────────────────────────┤
│ 10.19.11.174 │ 11 │ LiqService 清算相关 │
└──────────────┴──────┴─────────────────────────────┘
---
4. 建议
┌────────┬───────────────────────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 优先级 │ 行动 │ 说明 │
├────────┼───────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────┤
│ P1 │ 检查 userId=123456 账户健康度 │ 多个 portfolio 同时触发清算,equity 极低,需确认是否需人工干预 │
├────────┼───────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────┤
│ P2 │ 排查高频下单源 │ 10.19.5.136 上每 5 分钟爆发 158 次 MAX_ORDERS_ERROR,定位是哪个 API client / 策略在重复触碰限制 │
├────────┼───────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────┤
│ P3 │ 优化清算限频逻辑 │ cancel all/open exceed limit 说明清算过程中的撤单频率控制可能需要调整,避免清算流程被自身限频阻断 │
├────────┼───────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────┤
│ P4 │ 确认 EMS 数据一致性 │ ems not exist 虽然只有 1 条,但可能代表订单生命周期管理的隐患 │
└────────┴───────────────────────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────┘
需要我针对某个具体问题深入排查吗?比如查看 userId=123456 的更多日志,或者分析下单源的 TraceId 链路。
