Loki 日志查询技能
概述
通过 Grafana Loki API 查询线上日志。支持:
-
多环境管理:5 个 Grafana 日志服务,按需切换
-
traceId 查询:通过日志ID获取完整请求链路
-
接口路径查询:通过 API 接口路径查询相关日志
-
关键词搜索:错误信息、类名、自定义关键词
多环境配置
配置文件:.claude/skills/loki-log-query/environments.json
环境列表
环境别名 名称 URL 快捷词
test13
测试13(主测试环境) https://test13.xnzn.net/grafana
test13, 13
monitor-test
Monitor 测试环境 https://monitor-test.xnzn.net/grafana
mtest
monitor-dev
Monitor 开发环境 https://monitor-dev.xnzn.net/grafana
mdev, dev
monitor02-dev
Monitor02 开发环境 https://monitor02-dev.xnzn.net/grafana
m02, monitor02
monitor-tyy-dev
Monitor 体验园开发环境 https://monitor-tyy-dev.xnzn.net/grafana
tyy, 体验园
环境匹配规则
用户说的话 → 匹配环境:
-
"查 test13 的日志" → test13
-
"去 monitor-dev 查" → monitor-dev
-
"切到体验园" → monitor-tyy-dev
-
"去 m02 查一下" → monitor02-dev
-
未指定环境 → 使用 active 字段指定的默认环境
读取配置
SKILL_DIR="$CLAUDE_PROJECT_DIR/.claude/skills/loki-log-query" ENV_FILE="${SKILL_DIR}/environments.json"
读取指定环境(参数: 环境别名)
read_env() { local ENV_KEY="${1:-$(python3 -c "import json; print(json.load(open('${ENV_FILE}'))['active'])")}" GRAFANA_URL=$(python3 -c "import json; print(json.load(open('${ENV_FILE}'))['environments']['${ENV_KEY}']['url'])") TOKEN=$(python3 -c "import json; print(json.load(open('${ENV_FILE}'))['environments']['${ENV_KEY}']['token'])") API="${GRAFANA_URL}/api/datasources/proxy/uid/loki/loki/api/v1" echo "Environment: ${ENV_KEY} → ${GRAFANA_URL}" }
通过别名查找环境 key
find_env() { python3 -c " import json data = json.load(open('${ENV_FILE}')) alias = '${1}'.lower() for key, env in data['environments'].items(): if alias == key or alias in env.get('aliases', []): print(key) break else: print(data['active']) " }
切换活跃环境
切换默认环境为 monitor-dev
python3 -c " import json data = json.load(open('${ENV_FILE}')) data['active'] = 'monitor-dev' json.dump(data, open('${ENV_FILE}', 'w'), indent=2, ensure_ascii=False) print('Switched to:', data['active']) "
更新 Token
为某个环境设置 Token
python3 -c " import json data = json.load(open('${ENV_FILE}')) data['environments']['monitor-dev']['token'] = 'glsa_新的token值' json.dump(data, open('${ENV_FILE}', 'w'), indent=2, ensure_ascii=False) print('Token updated for monitor-dev') "
API 基础
项目 值
数据源 Loki(uid: loki )
API 路径 {GRAFANA_URL}/api/datasources/proxy/uid/loki/loki/api/v1/query_range
认证 Authorization: Bearer {TOKEN}
默认标签 app="yunshitang"
重要:Loki API 必须通过 Grafana datasource proxy 访问,直接 /loki/api/v1/ 会返回 404。 查询时不限 project 标签({app="yunshitang"} ),可覆盖该 Grafana 下所有环境。
日志格式
2026-03-07 09:16:53.039,bcf6d955-fa26-45a5-9628-748f7ac4eed2,,, INFO 1 --- [线程名] 类名 : 行号 : 消息内容
位置 字段 示例
第1段 时间戳 2026-03-07 09:16:53.039
第2段 traceId bcf6d955-fa26-45a5-9628-748f7ac4eed2 或 a53dd0b0cc62bf4a79a63e77444f6f3f
第3段 商户ID 553722740746489856
第4段 用户ID 553723188689768448
第5段 日志内容 INFO 1 --- [thread] Class : 123 : msg
traceId 可能是 UUID 格式(带横线)或 32位hex(不带横线),都支持。
查询场景
场景 1:按 traceId 查完整链路(最常用)
用户说:"用 a53dd0b0cc62bf4a79a63e77444f6f3f 查日志"
TRACE_ID="a53dd0b0cc62bf4a79a63e77444f6f3f" END=$(date +%s)000000000 START=$(( $(date +%s) - 21600 ))000000000 # 最近6小时
curl -s "${API}/query_range"
-H "Authorization: Bearer ${TOKEN}"
--data-urlencode "query={app="yunshitang"} |= "${TRACE_ID}""
--data-urlencode "start=${START}"
--data-urlencode "end=${END}"
--data-urlencode "limit=500"
--data-urlencode "direction=forward"
| python3 -c "
import sys, json
data = json.load(sys.stdin)
if data.get('status') == 'success':
for stream in data['data']['result']:
labels = stream.get('stream', {})
print(f'--- {labels.get("app","?")}/{labels.get("project","?")} ---')
for ts, line in stream['values']:
print(line)
else:
print('Error:', data)
"
场景 2:按接口路径查日志
用户说:"查 /security/summary/order/mealtime/classify/page 这个接口的日志"
接口路径出现在请求日志的 RequestLoggingFilter 中(>>> POST /xxx 或 >>> GET /xxx )。
API_PATH="/security/summary/order/mealtime/classify/page" END=$(date +%s)000000000 START=$(( $(date +%s) - 21600 ))000000000 # 最近6小时
curl -s "${API}/query_range"
-H "Authorization: Bearer ${TOKEN}"
--data-urlencode "query={app="yunshitang"} |= "${API_PATH}""
--data-urlencode "start=${START}"
--data-urlencode "end=${END}"
--data-urlencode "limit=200"
--data-urlencode "direction=forward"
| python3 -c "
import sys, json, re
data = json.load(sys.stdin)
if data.get('status') != 'success':
print('Error:', data); sys.exit()
从匹配的请求日志中提取 traceId
trace_ids = set() all_lines = [] for stream in data['data']['result']: for ts, line in stream['values']: all_lines.append(line) # 提取 traceId(第2个逗号分隔字段) parts = line.split(',') if len(parts) >= 2: tid = parts[1].strip() if len(tid) >= 32: trace_ids.add(tid)
print(f'Found {len(all_lines)} lines, {len(trace_ids)} unique traceIds') print() for tid in list(trace_ids)[:10]: print(f' traceId: {tid}') print() for line in all_lines[:30]: print(line) if len(all_lines) > 30: print(f'... and {len(all_lines)-30} more lines') "
进阶:找到 traceId 后,再用场景 1 查该 traceId 的完整链路。
场景 3:关键词搜索
用户说:"搜一下 LeException" 或 "查 ERROR 日志"
KEYWORD="LeException" END=$(date +%s)000000000 START=$(( $(date +%s) - 21600 ))000000000 # 最近6小时
curl -s "${API}/query_range"
-H "Authorization: Bearer ${TOKEN}"
--data-urlencode "query={app="yunshitang"} |= "${KEYWORD}""
--data-urlencode "start=${START}"
--data-urlencode "end=${END}"
--data-urlencode "limit=100"
--data-urlencode "direction=backward"
常用关键词组合:
所有 ERROR 日志
{app="yunshitang"} |= "ERROR"
业务异常
{app="yunshitang"} |= "LeException"
SQL 错误
{app="yunshitang"} |~ "SQLSyntaxError|DataAccessException|BadSqlGrammar"
空指针
{app="yunshitang"} |= "NullPointerException"
按类名搜索
{app="yunshitang"} |= "OrderInfoService"
组合:ERROR + 特定类
{app="yunshitang"} |= "ERROR" |= "OrderInfoService"
排除健康检查噪音
{app="yunshitang"} |= "ERROR" != "health" != "actuator"
场景 4:按接口查完整链路(组合查询)
用户说:"查 /api/v2/web/order/list 接口的全链路日志"
两步走:
-
先按接口路径搜索,拿到 traceId
-
再按 traceId 查完整链路
Step 1: 找 traceId
API_PATH="/api/v2/web/order/list" END=$(date +%s)000000000 START=$(( $(date +%s) - 21600 ))000000000 # 最近6小时
TRACE_IDS=$(curl -s "${API}/query_range"
-H "Authorization: Bearer ${TOKEN}"
--data-urlencode "query={app="yunshitang"} |= "${API_PATH}" |= ">>>""
--data-urlencode "start=${START}"
--data-urlencode "end=${END}"
--data-urlencode "limit=10"
--data-urlencode "direction=backward"
| python3 -c "
import sys, json
data = json.load(sys.stdin)
for stream in data.get('data',{}).get('result',[]):
for ts, line in stream['values']:
parts = line.split(',')
if len(parts) >= 2 and len(parts[1].strip()) >= 32:
print(parts[1].strip())
")
echo "Found traceIds:" echo "${TRACE_IDS}" | head -5
Step 2: 用第一个 traceId 查完整链路
FIRST_TID=$(echo "${TRACE_IDS}" | head -1)
if [ -n "${FIRST_TID}" ]; then
curl -s "${API}/query_range"
-H "Authorization: Bearer ${TOKEN}"
--data-urlencode "query={app="yunshitang"} |= "${FIRST_TID}""
--data-urlencode "start=${START}"
--data-urlencode "end=${END}"
--data-urlencode "limit=500"
--data-urlencode "direction=forward"
| python3 -c "
import sys, json
data = json.load(sys.stdin)
for stream in data.get('data',{}).get('result',[]):
for ts, line in stream['values']:
print(line)
"
fi
场景 5:指定 project 环境查询
如果用户指定了具体的 project(如 test20):
{app="yunshitang",project="test20"} |= "traceId值"
LogQL 语法速查
流选择器
操作 语法 示例
精确匹配
{app="yunshitang"}
不等于 !=
{app!="test"}
正则匹配 =~
{project=~"test2.*"}
行过滤
操作 语法 示例
包含 |=
|= "ERROR"
不包含 !=
!= "DEBUG"
正则匹配 |~
|~ "Exception|Error"
正则排除 !~
!~ "health|ping"
查看可用标签值
列出所有 project
curl -s "${API}/label/project/values" -H "Authorization: Bearer ${TOKEN}"
列出所有 app
curl -s "${API}/label/app/values" -H "Authorization: Bearer ${TOKEN}"
Bug 修复工作流
- 获取线索(traceId / 接口路径 / 错误关键词) ↓
- 确定环境(哪个 Grafana?读取 environments.json) ↓
- 查询日志(场景 1-4 选择合适的查询方式) ↓
- 分析日志(ERROR/异常堆栈/SQL 错误/耗时) ↓
- 定位代码(类名:行号 → 项目搜索) ↓
- 修复 + 更新云效任务状态
日志分析重点
-
ERROR 级别日志 — 异常根因
-
异常堆栈 — at net.xnzn.core.xxx 开头的行
-
SQL 错误 — SQLSyntaxErrorException 、DataAccessException
-
业务异常 — LeException 抛出的位置
-
耗时 — Completed xxx in Nms ,识别慢请求
-
请求/响应 — RequestLoggingFilter 的 >>> 和 <<< 日志
联动技能
场景 联动技能
排查 Bug bug-detective
查询云效任务 yunxiao-task-management
查数据库 mysql-debug
性能问题 performance-doctor
Grafana Service Account Token 创建
每个 Grafana 环境需要独立创建 Token:
-
登录对应 Grafana URL
-
左侧菜单 → Administration → Service accounts
-
Add service account(名称:claude-loki-reader ),角色:Viewer
-
Add service account token → 复制
-
更新 environments.json 中对应环境的 token 字段
注意
-
如果 Token 为空会报 invalid API key ,需要先为该环境创建 Token
-
查询默认不限 project,可覆盖该 Grafana 下所有项目环境
-
direction=forward 按时间正序,direction=backward 按时间倒序(最新优先)
-
如果是 Bug 排查完整流程,同时使用 bug-detective