@cvtoolman/huawei-lts-cli 1.0.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +140 -149
- package/dist/cli/commands/common.js +111 -0
- package/dist/cli/commands/config.js +9 -2
- package/dist/cli/commands/context.js +40 -0
- package/dist/cli/commands/fields.js +108 -0
- package/dist/cli/commands/init.js +76 -3
- package/dist/cli/commands/query.js +39 -94
- package/dist/cli/commands/streams.js +81 -0
- package/dist/cli/commands/trace.js +76 -0
- package/dist/cli/index.js +134 -11
- package/dist/config/profile.js +108 -0
- package/dist/config/store.js +7 -2
- package/dist/lts/client.js +171 -11
- package/dist/lts/pagination.js +56 -0
- package/dist/output/index.js +1 -1
- package/dist/output/table.js +2 -2
- package/package.json +11 -9
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -24,11 +24,26 @@ AI 编程助手的上下文窗口很宝贵——一边要装代码库、一边
|
|
|
24
24
|
|---|---|---|
|
|
25
25
|
| **上下文开销** | **低** — skill 就一个短 markdown 文件,CLI 输出是纯文本 | **高** — 工具 schema + 快照数据常驻上下文 |
|
|
26
26
|
| **AI 使用方式** | 执行 shell 命令,读取结果 | 通过协议调用结构化工具 |
|
|
27
|
-
| **配置难度** | `npm install -g`、一个 skill
|
|
27
|
+
| **配置难度** | `npm install -g`、一个 skill 目录 | 服务进程、配置连线 |
|
|
28
28
|
| **适用场景** | 编码过程中顺手查日志 | 长时间运行的自动化循环 |
|
|
29
29
|
|
|
30
30
|
> **一句话:** 如果你想让 AI 在写代码时顺嘴帮你查日志——CLI + SKILL 是更轻、更省钱的方案。
|
|
31
31
|
|
|
32
|
+
## 核心设计:先发现,再查询
|
|
33
|
+
|
|
34
|
+
字段名、字段类型、是否大小写敏感、分词符——**这些全是每条日志流各自的配置**,不是 LTS 的通用事实。任何写死在文档里的字段表,换个环境就是错的,而且不会报错,只会返回空结果。
|
|
35
|
+
|
|
36
|
+
所以这个工具的答案永远来自运行时:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
lts-cli groups # 我有哪些日志组
|
|
40
|
+
lts-cli streams --group <组名> # 这个组下有哪些流
|
|
41
|
+
lts-cli fields # 这条流有哪些字段、什么类型、大小写敏感吗
|
|
42
|
+
lts-cli query --last 2h -q '...' # 这才开始查
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`fields` 是 2.0 新增的命令,也是 skill 敢不写任何字段表的前提。SKILL.md 里明确要求 AI:任何 `field:value` 里的字段,必须来自 `fields` 的实际输出;凭印象构造的字段查询在 LTS 上不会报错,只会返回空——然后 AI 会误判"没有这条日志"。
|
|
46
|
+
|
|
32
47
|
## 快速开始
|
|
33
48
|
|
|
34
49
|
### 1. 安装 CLI
|
|
@@ -37,208 +52,183 @@ AI 编程助手的上下文窗口很宝贵——一边要装代码库、一边
|
|
|
37
52
|
npm install -g @cvtoolman/huawei-lts-cli
|
|
38
53
|
```
|
|
39
54
|
|
|
55
|
+
需要 Node.js >= 18。
|
|
56
|
+
|
|
40
57
|
### 2. 配置凭证
|
|
41
58
|
|
|
42
59
|
```bash
|
|
43
60
|
lts-cli init
|
|
44
61
|
```
|
|
45
62
|
|
|
46
|
-
向导会依次询问 AK、SK、Project ID、Region
|
|
63
|
+
向导会依次询问 AK、SK、Project ID、Region,一次配好。同时会生成两个文件:
|
|
47
64
|
|
|
48
|
-
|
|
65
|
+
| 文件 | 内容 | 谁读 |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `~/.lts-cli/config.json` | 凭证、默认日志组/流、`traceField`(权限 0600) | 仅 CLI |
|
|
68
|
+
| `~/.lts-cli/profile.md` | 业务词表:把「订单服务」映射到日志里的实际取值 | 仅 AI |
|
|
49
69
|
|
|
50
|
-
**
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
curl -o .claude/skills/huawei-lts-log-query/SKILL.md \
|
|
54
|
-
https://raw.githubusercontent.com/jcxy/huawei-lts-cli/main/skills/huawei-lts-log-query/SKILL.md
|
|
55
|
-
```
|
|
70
|
+
**CLI 不读 `profile.md`,AI 不读 `config.json`。** 后者含明文 AK/SK,任何让 AI 读它的设计都是错的。
|
|
71
|
+
|
|
72
|
+
拿到控制台日志搜索页的 URL 时,可以直接导入 region / groupId / streamId:
|
|
56
73
|
|
|
57
|
-
**Qoder:**
|
|
58
74
|
```bash
|
|
59
|
-
|
|
60
|
-
curl -o .qoder/skills/huawei-lts-log-query/SKILL.md \
|
|
61
|
-
https://raw.githubusercontent.com/jcxy/huawei-lts-cli/main/skills/huawei-lts-log-query/SKILL.md
|
|
75
|
+
lts-cli init --from-url "<控制台URL>"
|
|
62
76
|
```
|
|
63
77
|
|
|
64
|
-
|
|
78
|
+
### 3. 安装 Skill(给 AI 工具用)
|
|
65
79
|
|
|
66
|
-
|
|
80
|
+
skill 目录含 `SKILL.md` 和 2 个参考文档,整目录复制:
|
|
67
81
|
|
|
68
82
|
```bash
|
|
69
|
-
#
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z -k "ERROR"
|
|
74
|
-
|
|
75
|
-
# 高级查询:多字段组合(推荐)
|
|
76
|
-
lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z \
|
|
77
|
-
-q "content:error AND appName:myapp"
|
|
78
|
-
|
|
79
|
-
# 复杂逻辑查询
|
|
80
|
-
lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z \
|
|
81
|
-
-q "(level:ERROR OR level:WARN) NOT env:test"
|
|
82
|
-
|
|
83
|
-
# SQL 分析查询
|
|
84
|
-
lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z \
|
|
85
|
-
-q "SELECT count(*) as cnt, level FROM log GROUP BY level" \
|
|
86
|
-
-f json
|
|
83
|
+
# Claude Code
|
|
84
|
+
git clone --depth 1 https://github.com/jcxy/huawei-lts-cli.git /tmp/huawei-lts-cli
|
|
85
|
+
mkdir -p .claude/skills/huawei-lts-log-query
|
|
86
|
+
cp -r /tmp/huawei-lts-cli/skills/huawei-lts-log-query/. .claude/skills/huawei-lts-log-query/
|
|
87
87
|
|
|
88
|
-
#
|
|
89
|
-
|
|
88
|
+
# Qoder:把 .claude/skills/ 换成 .qoder/skills/
|
|
89
|
+
# 其他工具:换成对应目录(.cursor/skills/ 等)
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
## 命令参考
|
|
95
|
-
|
|
96
|
-
### `lts-cli query` — 查询日志
|
|
92
|
+
Windows PowerShell:
|
|
97
93
|
|
|
98
|
-
```
|
|
99
|
-
lts-cli
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
-g, --group-id <groupId> 日志组 ID(可在配置中预设)
|
|
103
|
-
-s, --stream-id <streamId> 日志流 ID(可在配置中预设)
|
|
104
|
-
--start-time, --st <time> 开始时间,ISO 8601 格式(必填)
|
|
105
|
-
--end-time, --et <time> 结束时间,ISO 8601 格式(必填)
|
|
106
|
-
-k, --keyword <keyword> 简单关键词搜索(旧版,不推荐)
|
|
107
|
-
-q, --query <query> 高级查询表达式(推荐):
|
|
108
|
-
- 字段查询: content:error AND appName:myapp
|
|
109
|
-
- 比较运算: time>60 AND region:r
|
|
110
|
-
- SQL语法: SELECT * FROM log WHERE ...
|
|
111
|
-
- 逻辑运算符: AND, OR, NOT(需空格分隔)
|
|
112
|
-
-l, --limit <limit> 每页条数(默认: 100)
|
|
113
|
-
-o, --offset <offset> 分页偏移量(默认: 0)
|
|
114
|
-
-r, --reverse 按时间倒序
|
|
115
|
-
-f, --format <format> 输出格式: pretty、json、table(默认: pretty)
|
|
116
|
-
--no-paginate 关闭自动翻页
|
|
94
|
+
```powershell
|
|
95
|
+
git clone --depth 1 https://github.com/jcxy/huawei-lts-cli.git $env:TEMP\huawei-lts-cli
|
|
96
|
+
New-Item -ItemType Directory -Force .claude\skills\huawei-lts-log-query | Out-Null
|
|
97
|
+
Copy-Item -Recurse -Force $env:TEMP\huawei-lts-cli\skills\huawei-lts-log-query\* .claude\skills\huawei-lts-log-query\
|
|
117
98
|
```
|
|
118
99
|
|
|
119
|
-
|
|
100
|
+
### 4. 命令行直接使用(不依赖 AI)
|
|
120
101
|
|
|
121
|
-
**字段查询:**
|
|
122
102
|
```bash
|
|
123
|
-
#
|
|
124
|
-
lts-cli query --
|
|
125
|
-
-q "content:error"
|
|
126
|
-
|
|
127
|
-
# 多字段组合(且关系)
|
|
128
|
-
lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z \
|
|
129
|
-
-q "content:error AND appName:myapp AND node:server1"
|
|
130
|
-
|
|
131
|
-
# 多字段组合(或关系)
|
|
132
|
-
lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z \
|
|
133
|
-
-q "level:ERROR OR level:WARN"
|
|
103
|
+
# 最近 2 小时最新 50 条
|
|
104
|
+
lts-cli query --last 2h -r -l 50
|
|
134
105
|
|
|
135
|
-
#
|
|
136
|
-
lts-cli query --
|
|
137
|
-
-q "level:DEBUG NOT module:test"
|
|
106
|
+
# 组合条件
|
|
107
|
+
lts-cli query --last 2h --app <appName> --content timeout -r
|
|
138
108
|
|
|
139
|
-
#
|
|
140
|
-
lts-cli query --
|
|
141
|
-
-q "response_time>1000 AND status_code!=200"
|
|
109
|
+
# 任意表达式
|
|
110
|
+
lts-cli query --last 1d -q '(level:ERROR OR level:WARN) NOT env:test' -r
|
|
142
111
|
|
|
143
|
-
#
|
|
144
|
-
lts-cli query --
|
|
145
|
-
-q "(level:ERROR OR level:WARN) NOT env:test"
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
**SQL 查询:**
|
|
149
|
-
```bash
|
|
150
|
-
# 统计各应用错误数量
|
|
151
|
-
lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z \
|
|
152
|
-
-q "SELECT count(*) as cnt, appName FROM log WHERE content LIKE '%error%' GROUP BY appName"
|
|
112
|
+
# 管道分析:先过滤再统计
|
|
113
|
+
lts-cli query --last 1d -q 'level:ERROR | SELECT count(*) AS cnt, appName GROUP BY appName' -f json
|
|
153
114
|
|
|
154
|
-
#
|
|
155
|
-
lts-cli query --
|
|
156
|
-
-q "SELECT avg(response_time) as avg_time FROM log WHERE status_code=200"
|
|
115
|
+
# 机器处理
|
|
116
|
+
lts-cli query --last 1h -f json | jq '.logs[].content'
|
|
157
117
|
```
|
|
158
118
|
|
|
159
|
-
>
|
|
119
|
+
> 💡 语法细节见 [查询语法](skills/huawei-lts-log-query/references/query-syntax.md),命令全貌见 [CLI 参考](skills/huawei-lts-log-query/references/cli-reference.md)。
|
|
160
120
|
|
|
161
|
-
|
|
162
|
-
- [查询语法速查表](skills/huawei-lts-log-query/references/cheatsheet.md)
|
|
163
|
-
- [高级查询指南](skills/huawei-lts-log-query/references/advanced-query-guide.md)
|
|
164
|
-
- [控制台 vs CLI 对照](skills/huawei-lts-log-query/references/query-comparison.md)
|
|
121
|
+
## 命令参考
|
|
165
122
|
|
|
166
|
-
|
|
123
|
+
| 命令 | 作用 |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `lts-cli groups` | 列出日志组 |
|
|
126
|
+
| `lts-cli streams` | 列出日志流(`--group <组名>` 过滤) |
|
|
127
|
+
| `lts-cli fields` | 读取日志流的索引配置 |
|
|
128
|
+
| `lts-cli query` | 查询日志 |
|
|
129
|
+
| `lts-cli trace <id>` | 按 ID 还原完整请求链路 |
|
|
130
|
+
| `lts-cli context` | 查看某条日志的前后文 |
|
|
131
|
+
| `lts-cli config` | 管理配置 |
|
|
132
|
+
| `lts-cli init` | 配置向导 |
|
|
133
|
+
| `lts-cli completion` | 生成 shell 补全 |
|
|
134
|
+
|
|
135
|
+
### `lts-cli query`
|
|
167
136
|
|
|
168
137
|
```
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
138
|
+
-g, --group-id <id> 日志组 ID(可在配置中预设)
|
|
139
|
+
-s, --stream-id <id> 日志流 ID(可在配置中预设)
|
|
140
|
+
--last <时长> 30m / 2h / 1d / 7d,与 --st/--et 互斥
|
|
141
|
+
--st, --start-time <ISO> 开始时间
|
|
142
|
+
--et, --end-time <ISO> 结束时间
|
|
143
|
+
-q, --query <expr> 查询表达式或管道分析
|
|
144
|
+
--app <取值> appName:"<取值>",与 -q 用 AND 合并
|
|
145
|
+
--content <关键词> content:"<关键词>",与 -q 用 AND 合并
|
|
146
|
+
--request-id <id> REQUEST_ID:"<id>",按时间正序输出
|
|
147
|
+
-l, --limit <n> 每页条数(默认 100),--all 时即单页大小
|
|
148
|
+
-r, --reverse 最新优先
|
|
149
|
+
-f, --format <fmt> pretty(默认)/ table / json
|
|
150
|
+
--all 沿游标读到末尾(上限 10000 条)
|
|
176
151
|
```
|
|
177
152
|
|
|
178
|
-
|
|
153
|
+
`--app` / `--content` / `--request-id` 自动为值加双引号——含连字符的 UUID 不加引号会被分词导致匹配不到。
|
|
179
154
|
|
|
180
|
-
|
|
181
|
-
lts-cli init
|
|
155
|
+
### `lts-cli trace`
|
|
182
156
|
|
|
183
|
-
|
|
184
|
-
|
|
157
|
+
```bash
|
|
158
|
+
lts-cli trace <id> [--field <name>] [--last 24h] [-l 200] [-f fmt]
|
|
185
159
|
```
|
|
186
160
|
|
|
187
|
-
|
|
161
|
+
按时间正序拉出一条 ID 的完整链路。默认走全文检索(不依赖任何特定字段命名);如果你的日志流有专用字段,配一次即可:
|
|
188
162
|
|
|
189
|
-
```
|
|
190
|
-
lts-cli
|
|
191
|
-
|
|
192
|
-
生成 shell 补全脚本。
|
|
163
|
+
```bash
|
|
164
|
+
lts-cli config --set traceField=REQUEST_ID
|
|
193
165
|
```
|
|
194
166
|
|
|
195
167
|
## 配置说明
|
|
196
168
|
|
|
197
|
-
|
|
169
|
+
配置文件 `~/.lts-cli/config.json`(Windows 为 `%USERPROFILE%\.lts-cli\config.json`):
|
|
198
170
|
|
|
199
171
|
| 键 | 必填 | 说明 |
|
|
200
172
|
|---|---|---|
|
|
201
173
|
| `ak` | 是 | 华为云访问密钥 |
|
|
202
174
|
| `sk` | 是 | 华为云私有密钥 |
|
|
203
175
|
| `projectId` | 是 | 项目 ID |
|
|
204
|
-
| `region` | 否 | 如 `cn-
|
|
176
|
+
| `region` | 否 | 如 `cn-north-4` |
|
|
205
177
|
| `endpoint` | 否 | 需包含 `https://` |
|
|
206
178
|
| `groupId` | 否 | 默认日志组 ID |
|
|
207
179
|
| `streamId` | 否 | 默认日志流 ID |
|
|
180
|
+
| `traceField` | 否 | `trace` 默认匹配的字段名,缺省为 `content` |
|
|
208
181
|
|
|
209
|
-
|
|
182
|
+
`lts-cli config --list` 会把 `ak` / `sk` 脱敏输出,避免凭证进入终端滚屏、shell 历史或 AI 上下文。
|
|
210
183
|
|
|
211
|
-
|
|
184
|
+
### 环境变量
|
|
212
185
|
|
|
213
|
-
|
|
214
|
-
|---|---|
|
|
215
|
-
| `ak` | `LTS_AK` |
|
|
216
|
-
| `sk` | `LTS_SK` |
|
|
217
|
-
| `projectId` | `LTS_PROJECT_ID` |
|
|
218
|
-
| `region` | `LTS_REGION` |
|
|
219
|
-
| `endpoint` | `LTS_ENDPOINT` |
|
|
220
|
-
| `groupId` | `LTS_GROUP_ID` |
|
|
221
|
-
| `streamId` | `LTS_STREAM_ID` |
|
|
186
|
+
所有配置项都支持环境变量(适用于 CI/CD),优先级高于配置文件:
|
|
222
187
|
|
|
223
|
-
|
|
188
|
+
`LTS_AK` `LTS_SK` `LTS_PROJECT_ID` `LTS_REGION` `LTS_ENDPOINT` `LTS_GROUP_ID` `LTS_STREAM_ID`
|
|
224
189
|
|
|
225
190
|
## 支持的区域
|
|
226
191
|
|
|
227
|
-
|
|
192
|
+
以下是 `lts-cli init` 内置的 region → endpoint 映射:
|
|
193
|
+
|
|
194
|
+
| 区域 ID | Endpoint |
|
|
195
|
+
|---|---|
|
|
196
|
+
| `cn-north-1` | `lts.cn-north-1.myhuaweicloud.com` |
|
|
197
|
+
| `cn-north-2` | `lts.cn-north-2.myhuaweicloud.com` |
|
|
198
|
+
| `cn-north-4` | `lts.cn-north-4.myhuaweicloud.com` |
|
|
199
|
+
| `cn-north-5` | `lts.cn-north-5.myhuaweicloud.com` |
|
|
200
|
+
| `cn-north-9` | `lts.cn-north-9.myhuaweicloud.com` |
|
|
201
|
+
| `cn-east-2` | `lts.cn-east-2.myhuaweicloud.com` |
|
|
202
|
+
| `cn-east-3` | `lts.cn-east-3.myhuaweicloud.com` |
|
|
203
|
+
| `cn-east-4` | `lts.cn-east-4.myhuaweicloud.com` |
|
|
204
|
+
| `cn-south-1` | `lts.cn-south-1.myhuaweicloud.com` |
|
|
205
|
+
| `cn-south-2` | `lts.cn-south-2.myhuaweicloud.com` |
|
|
206
|
+
| `cn-south-4` | `lts.cn-south-4.myhuaweicloud.com` |
|
|
207
|
+
| `cn-southwest-2` | `lts.cn-southwest-2.myhuaweicloud.com` |
|
|
208
|
+
| `ap-southeast-1` | `lts.ap-southeast-1.myhuaweicloud.com` |
|
|
209
|
+
| `ap-southeast-2` | `lts.ap-southeast-2.myhuaweicloud.com` |
|
|
210
|
+
| `ap-southeast-3` | `lts.ap-southeast-3.myhuaweicloud.com` |
|
|
211
|
+
| `af-south-1` | `lts.af-south-1.myhuaweicloud.com` |
|
|
212
|
+
| `la-south-2` | `lts.la-south-2.myhuaweicloud.com` |
|
|
213
|
+
| `la-north-2` | `lts.la-north-2.myhuaweicloud.com` |
|
|
214
|
+
| `sa-brazil-1` | `lts.sa-brazil-1.myhuaweicloud.com` |
|
|
215
|
+
| `na-mexico-1` | `lts.na-mexico-1.myhuaweicloud.com` |
|
|
216
|
+
|
|
217
|
+
不在表里时用 `--set endpoint=https://...` 手动指定,或参考 [华为云 LTS 终端节点](https://support.huaweicloud.com/api-lts/lts_api_0011.html)。
|
|
218
|
+
|
|
219
|
+
## 从 1.x 升级到 2.0
|
|
220
|
+
|
|
221
|
+
2.0 修了三个会静默出错的问题,因此有破坏性变更:
|
|
222
|
+
|
|
223
|
+
| 变更 | 原因 | 迁移 |
|
|
228
224
|
|---|---|---|
|
|
229
|
-
| `
|
|
230
|
-
| `
|
|
231
|
-
|
|
|
232
|
-
| `
|
|
233
|
-
| `
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
| `ap-southeast-1` | 亚太-新加坡 | `lts.ap-southeast-1.myhuaweicloud.com` |
|
|
237
|
-
| `ap-southeast-2` | 亚太-曼谷 | `lts.ap-southeast-2.myhuaweicloud.com` |
|
|
238
|
-
| `ap-southeast-3` | 亚太-雅加达 | `lts.ap-southeast-3.myhuaweicloud.com` |
|
|
239
|
-
| `af-south-1` | 非洲-约翰内斯堡 | `lts.af-south-1.myhuaweicloud.com` |
|
|
240
|
-
|
|
241
|
-
> 完整列表请参考 [华为云 LTS 终端节点](https://support.huaweicloud.com/api-lts/lts_api_0011.html)。
|
|
225
|
+
| 删除 `--offset` | LTS 的 SDK 根本没有 offset 字段,该参数一直静默失效;`--all` 会重复拉同一页直到塞满 10000 条 | 用 `--all`,或缩小时间范围 |
|
|
226
|
+
| 删除 `-k, --keyword` | 与 `-q` 行为完全等价 | 改成 `-q "<关键词>"` |
|
|
227
|
+
| 删除 `--no-paginate` | 单页本来就是默认行为,该参数从未被读取 | 直接删掉 |
|
|
228
|
+
| `trace` 默认字段由 `REQUEST_ID` 改为 `content` | `REQUEST_ID` 只是某类应用的命名约定,不是 LTS 通用概念 | 需要精确字段时 `lts-cli config --set traceField=REQUEST_ID` |
|
|
229
|
+
| `-f json` 输出字段 `logTime` 改名为 `lineNum` | LTS 的日志记录只有 `content` / `line_num` / `labels`,没有时间戳,原名是错的 | 更新解析脚本 |
|
|
230
|
+
|
|
231
|
+
此外,结果不完整时会向 stderr 输出警告(`isQueryComplete=false` 或触达 10000 条上限)。**这些警告不能忽略**——此时结果就是残缺的。
|
|
242
232
|
|
|
243
233
|
## 开发指南
|
|
244
234
|
|
|
@@ -246,16 +236,17 @@ lts-cli completion <bash|zsh|powershell>
|
|
|
246
236
|
git clone https://github.com/jcxy/huawei-lts-cli.git
|
|
247
237
|
cd huawei-lts-cli
|
|
248
238
|
npm install
|
|
249
|
-
npm run build
|
|
250
239
|
|
|
251
|
-
#
|
|
252
|
-
npm run dev -- query --
|
|
253
|
-
|
|
254
|
-
#
|
|
255
|
-
npm link
|
|
256
|
-
lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z
|
|
240
|
+
npm run build # 编译到 dist/
|
|
241
|
+
npm run dev -- query --last 2h # 直接从源码跑
|
|
242
|
+
npm test # node:test,覆盖纯函数与游标推进
|
|
243
|
+
npm run typecheck # tsc --noEmit
|
|
257
244
|
```
|
|
258
245
|
|
|
246
|
+
测试使用 Node 内置的 `node:test` + `tsx`,不引入额外测试依赖。跑测试需要 Node >= 20.6(`--import` 的要求)。
|
|
247
|
+
|
|
248
|
+
架构约定见 [CONTEXT.md](CONTEXT.md) 与 [docs/adr/](docs/adr/)。
|
|
249
|
+
|
|
259
250
|
## License
|
|
260
251
|
|
|
261
252
|
[MIT](LICENSE)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadCheckedConfig = loadCheckedConfig;
|
|
4
|
+
exports.resolveGroupStream = resolveGroupStream;
|
|
5
|
+
exports.resolveTimeParams = resolveTimeParams;
|
|
6
|
+
exports.buildQueryExpression = buildQueryExpression;
|
|
7
|
+
exports.resolveFormat = resolveFormat;
|
|
8
|
+
exports.warnIfIncomplete = warnIfIncomplete;
|
|
9
|
+
exports.handleCommandError = handleCommandError;
|
|
10
|
+
const config_1 = require("../../config");
|
|
11
|
+
/** 加载配置并校验凭证,缺失时打印引导并退出 */
|
|
12
|
+
function loadCheckedConfig() {
|
|
13
|
+
const config = (0, config_1.loadConfig)();
|
|
14
|
+
if (!config.ak || !config.sk || !config.projectId) {
|
|
15
|
+
console.error('Error: Missing required credentials. Please configure them first:');
|
|
16
|
+
console.error('');
|
|
17
|
+
console.error(' Quick setup (recommended):');
|
|
18
|
+
console.error(' lts-cli init');
|
|
19
|
+
console.error('');
|
|
20
|
+
console.error(' Or set individually:');
|
|
21
|
+
console.error(' lts-cli config --set ak=<your-access-key>');
|
|
22
|
+
console.error(' lts-cli config --set sk=<your-secret-key>');
|
|
23
|
+
console.error(' lts-cli config --set projectId=<your-project-id>');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
return config;
|
|
27
|
+
}
|
|
28
|
+
/** groupId/streamId:CLI 参数优先,其次配置默认值,都没有则报错退出 */
|
|
29
|
+
function resolveGroupStream(options, config) {
|
|
30
|
+
const groupId = options.groupId || config.groupId;
|
|
31
|
+
const streamId = options.streamId || config.streamId;
|
|
32
|
+
if (!groupId || !streamId) {
|
|
33
|
+
console.error('Error: groupId and streamId are required. Provide them via:');
|
|
34
|
+
console.error(' lts-cli config --set groupId=<your-group-id>');
|
|
35
|
+
console.error(' lts-cli config --set streamId=<your-stream-id>');
|
|
36
|
+
console.error(' or use -g <groupId> -s <streamId> flags');
|
|
37
|
+
console.error(' Tip: run "lts-cli groups" / "lts-cli streams" to discover IDs');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
return { groupId, streamId };
|
|
41
|
+
}
|
|
42
|
+
/** 解析时间选项:--last 与 --st/--et 互斥,至少提供一组 */
|
|
43
|
+
function resolveTimeParams(options, defaultLast) {
|
|
44
|
+
const startTime = options.startTime || options.st;
|
|
45
|
+
const endTime = options.endTime || options.et;
|
|
46
|
+
const last = options.last || (!startTime && !endTime ? defaultLast : undefined);
|
|
47
|
+
if (last && (startTime || endTime)) {
|
|
48
|
+
console.error('Error: --last is mutually exclusive with --start-time/--end-time. Provide only one style.');
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
if (!last && (!startTime || !endTime)) {
|
|
52
|
+
console.error('Error: time range required. Use --last (e.g. --last 2h) or both --start-time and --end-time.');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
return last ? { last } : { startTime, endTime };
|
|
56
|
+
}
|
|
57
|
+
/** 将 -q 表达式与便捷筛选 flag 合并为一个查询表达式(AND 连接)
|
|
58
|
+
* 字段值统一加双引号:含连字符等分词符的值(如 UUID)不加引号会匹配不到 */
|
|
59
|
+
function buildQueryExpression(options) {
|
|
60
|
+
const parts = [];
|
|
61
|
+
if (options.query) {
|
|
62
|
+
parts.push(options.query);
|
|
63
|
+
}
|
|
64
|
+
if (options.app) {
|
|
65
|
+
parts.push(`appName:"${options.app}"`);
|
|
66
|
+
}
|
|
67
|
+
if (options.content) {
|
|
68
|
+
parts.push(`content:"${options.content}"`);
|
|
69
|
+
}
|
|
70
|
+
if (options.requestId) {
|
|
71
|
+
parts.push(`REQUEST_ID:"${options.requestId}"`);
|
|
72
|
+
}
|
|
73
|
+
return parts.length > 0 ? parts.join(' AND ') : undefined;
|
|
74
|
+
}
|
|
75
|
+
/** 解析输出格式,非法值回退 pretty */
|
|
76
|
+
function resolveFormat(value) {
|
|
77
|
+
return ['json', 'table', 'pretty'].includes(value) ? value : 'pretty';
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* LTS 在时间跨度过大时会返回不完整结果(isQueryComplete=false)。
|
|
81
|
+
* 结果照样输出(残缺比没有好),但必须在 stderr 明确告警——安静地给出半个答案,
|
|
82
|
+
* 比直接报错更危险:调用方会基于残缺数据下结论。
|
|
83
|
+
*/
|
|
84
|
+
function warnIfIncomplete(result) {
|
|
85
|
+
if (result.isQueryComplete === false) {
|
|
86
|
+
console.error('Warning: LTS reported an incomplete result set (isQueryComplete=false). ' +
|
|
87
|
+
'Narrow the time range (e.g. --last 1h) or add a filter before trusting totals.');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** 统一错误处理:常见错误给出友好提示 */
|
|
91
|
+
function handleCommandError(error) {
|
|
92
|
+
const msg = error.message || String(error);
|
|
93
|
+
if (msg.includes('Invalid URL')) {
|
|
94
|
+
console.error('Error: Invalid endpoint URL. Make sure it starts with https://');
|
|
95
|
+
console.error(' Example: https://lts.cn-south-1.myhuaweicloud.com');
|
|
96
|
+
}
|
|
97
|
+
else if (msg.includes('Authentication failed') || msg.includes('401') || msg.includes('403')) {
|
|
98
|
+
console.error('Error: Authentication failed. Please check your AK/SK credentials:');
|
|
99
|
+
console.error(' lts-cli config --list');
|
|
100
|
+
}
|
|
101
|
+
else if (msg.includes('ENOTFOUND') || msg.includes('ECONNREFUSED')) {
|
|
102
|
+
console.error('Error: Cannot reach the LTS service. Please check your endpoint and network connection.');
|
|
103
|
+
}
|
|
104
|
+
else if (msg.includes('Request failed after retries')) {
|
|
105
|
+
console.error('Error: The service is temporarily unavailable. Please try again later.');
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
console.error('Error:', msg);
|
|
109
|
+
}
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
@@ -3,6 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createConfigCommand = createConfigCommand;
|
|
4
4
|
const commander_1 = require("commander");
|
|
5
5
|
const config_1 = require("../../config");
|
|
6
|
+
/** 凭证类字段一律脱敏:`config --list` 会把输出留在终端滚屏、shell 历史、agent 上下文里。 */
|
|
7
|
+
const SECRET_KEYS = new Set(['ak', 'sk']);
|
|
8
|
+
function maskSecret(value) {
|
|
9
|
+
if (value.length <= 4)
|
|
10
|
+
return '****';
|
|
11
|
+
return `${value.slice(0, 2)}${'*'.repeat(Math.max(4, value.length - 4))}${value.slice(-2)}`;
|
|
12
|
+
}
|
|
6
13
|
function createConfigCommand() {
|
|
7
14
|
const cmd = new commander_1.Command('config');
|
|
8
15
|
cmd.description('Manage LTS CLI configuration')
|
|
@@ -24,7 +31,7 @@ function createConfigCommand() {
|
|
|
24
31
|
else if (options.get) {
|
|
25
32
|
const value = (0, config_1.getConfig)(options.get);
|
|
26
33
|
if (value !== undefined) {
|
|
27
|
-
console.log(value);
|
|
34
|
+
console.log(SECRET_KEYS.has(options.get) ? maskSecret(value) : value);
|
|
28
35
|
}
|
|
29
36
|
else {
|
|
30
37
|
console.error(`Config key '${options.get}' not found`);
|
|
@@ -39,7 +46,7 @@ function createConfigCommand() {
|
|
|
39
46
|
}
|
|
40
47
|
else {
|
|
41
48
|
for (const [key, value] of entries) {
|
|
42
|
-
console.log(`${key}=${value}`);
|
|
49
|
+
console.log(`${key}=${SECRET_KEYS.has(key) ? maskSecret(value) : value}`);
|
|
43
50
|
}
|
|
44
51
|
}
|
|
45
52
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createContextCommand = createContextCommand;
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const client_1 = require("../../lts/client");
|
|
6
|
+
const output_1 = require("../../output");
|
|
7
|
+
const common_1 = require("./common");
|
|
8
|
+
function createContextCommand() {
|
|
9
|
+
const cmd = new commander_1.Command('context');
|
|
10
|
+
cmd.description('View logs before/after a specific log entry (like "View Context" in the console)')
|
|
11
|
+
.option('-g, --group-id <groupId>', 'Log group ID (can also be set via config)')
|
|
12
|
+
.option('-s, --stream-id <streamId>', 'Log stream ID (can also be set via config)')
|
|
13
|
+
.requiredOption('--line-num <lineNum>', 'line_num of the anchor log entry')
|
|
14
|
+
.requiredOption('--time <time>', 'Timestamp (ms) of the anchor log entry')
|
|
15
|
+
.option('--before <count>', 'Number of logs before the anchor', (v) => parseInt(v, 10), 20)
|
|
16
|
+
.option('--after <count>', 'Number of logs after the anchor', (v) => parseInt(v, 10), 20)
|
|
17
|
+
.option('-f, --format <format>', 'Output format: json, table, pretty', 'pretty')
|
|
18
|
+
.action(async (options) => {
|
|
19
|
+
const config = (0, common_1.loadCheckedConfig)();
|
|
20
|
+
const { groupId, streamId } = (0, common_1.resolveGroupStream)(options, config);
|
|
21
|
+
const format = (0, common_1.resolveFormat)(options.format);
|
|
22
|
+
const client = new client_1.LTSClient(config);
|
|
23
|
+
const params = {
|
|
24
|
+
groupId,
|
|
25
|
+
streamId,
|
|
26
|
+
lineNum: options.lineNum,
|
|
27
|
+
time: options.time,
|
|
28
|
+
before: options.before,
|
|
29
|
+
after: options.after,
|
|
30
|
+
};
|
|
31
|
+
try {
|
|
32
|
+
const result = await client.queryContext(params);
|
|
33
|
+
console.log((0, output_1.formatOutput)(result, format));
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
(0, common_1.handleCommandError)(error);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return cmd;
|
|
40
|
+
}
|