@cvtoolman/huawei-lts-cli 2.1.0 → 2.1.1
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 +21 -21
- package/README.md +14 -5
- package/dist/cli/commands/common.js +21 -2
- package/dist/cli/commands/config.js +20 -6
- package/dist/cli/commands/context.js +9 -2
- package/dist/cli/commands/query.js +18 -4
- package/dist/cli/commands/trace.js +51 -11
- package/dist/cli/index.js +208 -186
- package/dist/lts/client.js +70 -1
- package/dist/lts/pagination.js +126 -27
- package/package.json +57 -57
- package/dist/cli/options.js +0 -15
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024 cvtoolman
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 cvtoolman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -153,10 +153,10 @@ lts-cli query --last 1h -f json | jq '.logs[].content'
|
|
|
153
153
|
--app <取值> appName:"<取值>",与 -q 用 AND 合并
|
|
154
154
|
--content <关键词> content:"<关键词>",与 -q 用 AND 合并
|
|
155
155
|
--request-id <id> REQUEST_ID:"<id>",按时间正序输出
|
|
156
|
-
-l, --limit <n> 每页条数(默认 100),--all
|
|
156
|
+
-l, --limit <n> 每页条数(默认 100,上限 5000),--all 时同时是总数上限
|
|
157
157
|
-r, --reverse 最新优先
|
|
158
158
|
-f, --format <fmt> pretty(默认)/ table / json
|
|
159
|
-
--all
|
|
159
|
+
--all 沿游标读到末尾(默认上限 10000 条,显式 -l 则为 -l)
|
|
160
160
|
```
|
|
161
161
|
|
|
162
162
|
`--app` / `--content` / `--request-id` 自动为值加双引号——含连字符的 UUID 不加引号会被分词导致匹配不到。
|
|
@@ -167,12 +167,21 @@ lts-cli query --last 1h -f json | jq '.logs[].content'
|
|
|
167
167
|
lts-cli trace <id> [--field <name>] [--last 24h] [-l 200] [-f fmt]
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
-
按时间正序拉出一条 ID
|
|
170
|
+
按时间正序拉出一条 ID 的完整链路。字段选择顺序是 `--field` → `config.traceField` → 运行时读一次流的索引配置:把 `REQUEST_ID` / `trace_id` / `span_id` 这类 ID 字段和全文检索 `content` 一起 OR 上。结构化日志的 ID 住在 labels 里,全文索引只覆盖正文,所以不能只靠 `content`。零命中时 stderr 会列出试过哪些字段。
|
|
171
171
|
|
|
172
172
|
```bash
|
|
173
|
-
lts-cli
|
|
173
|
+
lts-cli trace <id> --field REQUEST_ID # 临时指定
|
|
174
|
+
lts-cli config --set traceField=REQUEST_ID # 这条流长期如此才值得配
|
|
174
175
|
```
|
|
175
176
|
|
|
177
|
+
### `lts-cli context`
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
lts-cli context --line-num <lineNum> [--time <毫秒>] [--before 20] [--after 20]
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`--time` 可省:`line_num` 本身就是纳秒时间戳,前 13 位即毫秒,CLI 自己推。
|
|
184
|
+
|
|
176
185
|
## 配置说明
|
|
177
186
|
|
|
178
187
|
配置文件 `~/.lts-cli/config.json`(Windows 为 `%USERPROFILE%\.lts-cli\config.json`):
|
|
@@ -186,7 +195,7 @@ lts-cli config --set traceField=REQUEST_ID
|
|
|
186
195
|
| `endpoint` | 否 | 需包含 `https://` |
|
|
187
196
|
| `groupId` | 否 | 默认日志组 ID |
|
|
188
197
|
| `streamId` | 否 | 默认日志流 ID |
|
|
189
|
-
| `traceField` | 否 | `trace`
|
|
198
|
+
| `traceField` | 否 | `trace` 的匹配字段名;不配则自动从流索引里发现 ID 字段 |
|
|
190
199
|
|
|
191
200
|
`lts-cli config --list` 会把 `ak` / `sk` 脱敏输出,避免凭证进入终端滚屏、shell 历史或 AI 上下文。
|
|
192
201
|
|
|
@@ -8,6 +8,7 @@ exports.resolveFormat = resolveFormat;
|
|
|
8
8
|
exports.warnIfIncomplete = warnIfIncomplete;
|
|
9
9
|
exports.handleCommandError = handleCommandError;
|
|
10
10
|
const config_1 = require("../../config");
|
|
11
|
+
const client_1 = require("../../lts/client");
|
|
11
12
|
/** 加载配置并校验凭证,缺失时打印引导并退出 */
|
|
12
13
|
function loadCheckedConfig() {
|
|
13
14
|
const config = (0, config_1.loadConfig)();
|
|
@@ -39,7 +40,14 @@ function resolveGroupStream(options, config) {
|
|
|
39
40
|
}
|
|
40
41
|
return { groupId, streamId };
|
|
41
42
|
}
|
|
42
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* 解析时间选项:`--last` 与 `--st/--et` 互斥,至少提供一组。
|
|
45
|
+
*
|
|
46
|
+
* 返回值一律是绝对区间(ISO 8601)。相对时长必须在这里就换算掉、只算一次:
|
|
47
|
+
* 若把 `last` 传下去,每翻一页都会重算一次 `Date.now()`,窗口随时间往前滑,
|
|
48
|
+
* 游标(line_num)一旦滑出 [start, end],服务端直接报
|
|
49
|
+
* “line_num must be between them”,`--all` 会中途失败。
|
|
50
|
+
*/
|
|
43
51
|
function resolveTimeParams(options, defaultLast) {
|
|
44
52
|
const startTime = options.startTime || options.st;
|
|
45
53
|
const endTime = options.endTime || options.et;
|
|
@@ -52,7 +60,18 @@ function resolveTimeParams(options, defaultLast) {
|
|
|
52
60
|
console.error('Error: time range required. Use --last (e.g. --last 2h) or both --start-time and --end-time.');
|
|
53
61
|
process.exit(1);
|
|
54
62
|
}
|
|
55
|
-
|
|
63
|
+
let range;
|
|
64
|
+
try {
|
|
65
|
+
range = (0, client_1.resolveTimeRange)(last ? { last } : { startTime, endTime });
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.error(`Error: ${error.message}`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
startTime: new Date(range.startMs).toISOString(),
|
|
73
|
+
endTime: new Date(range.endMs).toISOString(),
|
|
74
|
+
};
|
|
56
75
|
}
|
|
57
76
|
/** 将 -q 表达式与便捷筛选 flag 合并为一个查询表达式(AND 连接)
|
|
58
77
|
* 字段值统一加双引号:含连字符等分词符的值(如 UUID)不加引号会匹配不到 */
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.maskSecret = maskSecret;
|
|
4
|
+
exports.displayValue = displayValue;
|
|
5
|
+
exports.parseAssignment = parseAssignment;
|
|
3
6
|
exports.createConfigCommand = createConfigCommand;
|
|
4
7
|
const commander_1 = require("commander");
|
|
5
8
|
const config_1 = require("../../config");
|
|
@@ -10,6 +13,17 @@ function maskSecret(value) {
|
|
|
10
13
|
return '****';
|
|
11
14
|
return `${value.slice(0, 2)}${'*'.repeat(Math.max(4, value.length - 4))}${value.slice(-2)}`;
|
|
12
15
|
}
|
|
16
|
+
/** 输出任何配置值时都经过这里:密钥只看得到首尾,其余原样。 */
|
|
17
|
+
function displayValue(key, value) {
|
|
18
|
+
return SECRET_KEYS.has(key) ? maskSecret(value) : value;
|
|
19
|
+
}
|
|
20
|
+
/** 拆 `key=value`。值里可能含 `=`(base64 密钥、URL 参数),只切第一个等号。 */
|
|
21
|
+
function parseAssignment(input) {
|
|
22
|
+
const eq = input.indexOf('=');
|
|
23
|
+
if (eq <= 0)
|
|
24
|
+
return undefined;
|
|
25
|
+
return { key: input.slice(0, eq), value: input.slice(eq + 1) };
|
|
26
|
+
}
|
|
13
27
|
function createConfigCommand() {
|
|
14
28
|
const cmd = new commander_1.Command('config');
|
|
15
29
|
cmd.description('Manage LTS CLI configuration')
|
|
@@ -19,19 +33,19 @@ function createConfigCommand() {
|
|
|
19
33
|
.option('--unset <key>', 'Remove a configuration value')
|
|
20
34
|
.action((options) => {
|
|
21
35
|
if (options.set) {
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
36
|
+
const assignment = parseAssignment(options.set);
|
|
37
|
+
if (!assignment) {
|
|
24
38
|
console.error('Usage: config --set key=value');
|
|
25
39
|
process.exit(1);
|
|
26
40
|
}
|
|
27
|
-
const
|
|
41
|
+
const { key, value } = assignment;
|
|
28
42
|
(0, config_1.setConfig)(key, value);
|
|
29
|
-
console.log(`Set ${key}=${value}`);
|
|
43
|
+
console.log(`Set ${key}=${displayValue(key, value)}`);
|
|
30
44
|
}
|
|
31
45
|
else if (options.get) {
|
|
32
46
|
const value = (0, config_1.getConfig)(options.get);
|
|
33
47
|
if (value !== undefined) {
|
|
34
|
-
console.log(
|
|
48
|
+
console.log(displayValue(options.get, value));
|
|
35
49
|
}
|
|
36
50
|
else {
|
|
37
51
|
console.error(`Config key '${options.get}' not found`);
|
|
@@ -46,7 +60,7 @@ function createConfigCommand() {
|
|
|
46
60
|
}
|
|
47
61
|
else {
|
|
48
62
|
for (const [key, value] of entries) {
|
|
49
|
-
console.log(`${key}=${
|
|
63
|
+
console.log(`${key}=${displayValue(key, value)}`);
|
|
50
64
|
}
|
|
51
65
|
}
|
|
52
66
|
}
|
|
@@ -11,7 +11,7 @@ function createContextCommand() {
|
|
|
11
11
|
.option('-g, --group-id <groupId>', 'Log group ID (can also be set via config)')
|
|
12
12
|
.option('-s, --stream-id <streamId>', 'Log stream ID (can also be set via config)')
|
|
13
13
|
.requiredOption('--line-num <lineNum>', 'line_num of the anchor log entry')
|
|
14
|
-
.
|
|
14
|
+
.option('--time <time>', 'Timestamp (ms) of the anchor log entry (derived from --line-num when omitted)')
|
|
15
15
|
.option('--before <count>', 'Number of logs before the anchor', (v) => parseInt(v, 10), 20)
|
|
16
16
|
.option('--after <count>', 'Number of logs after the anchor', (v) => parseInt(v, 10), 20)
|
|
17
17
|
.option('-f, --format <format>', 'Output format: json, table, pretty', 'pretty')
|
|
@@ -19,12 +19,19 @@ function createContextCommand() {
|
|
|
19
19
|
const config = (0, common_1.loadCheckedConfig)();
|
|
20
20
|
const { groupId, streamId } = (0, common_1.resolveGroupStream)(options, config);
|
|
21
21
|
const format = (0, common_1.resolveFormat)(options.format);
|
|
22
|
+
// line_num 本身就是纳秒时间戳,前 13 位即目标日志的毫秒时间。
|
|
23
|
+
// 让用户再抄一遍 --time 是纯粹的重复劳动,还会抄错。
|
|
24
|
+
const time = options.time ?? (0, client_1.anchorTimeFromLineNum)(options.lineNum);
|
|
25
|
+
if (!time) {
|
|
26
|
+
console.error(`Cannot derive the anchor time from line_num '${options.lineNum}'. Pass --time <ms>.`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
22
29
|
const client = new client_1.LTSClient(config);
|
|
23
30
|
const params = {
|
|
24
31
|
groupId,
|
|
25
32
|
streamId,
|
|
26
33
|
lineNum: options.lineNum,
|
|
27
|
-
time
|
|
34
|
+
time,
|
|
28
35
|
before: options.before,
|
|
29
36
|
after: options.after,
|
|
30
37
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseLimitOption = parseLimitOption;
|
|
3
4
|
exports.createQueryCommand = createQueryCommand;
|
|
4
5
|
const commander_1 = require("commander");
|
|
5
6
|
const client_1 = require("../../lts/client");
|
|
@@ -10,6 +11,17 @@ const common_1 = require("./common");
|
|
|
10
11
|
function parseIntOption(value) {
|
|
11
12
|
return parseInt(value, 10);
|
|
12
13
|
}
|
|
14
|
+
/** --all 读不到末尾时的止损上限 */
|
|
15
|
+
const ALL_CAP = 10000;
|
|
16
|
+
/** 超限的 limit 本地就拦下:服务端会回一个 LTS.1903,还要多一次往返 */
|
|
17
|
+
function parseLimitOption(value) {
|
|
18
|
+
const limit = parseIntOption(value);
|
|
19
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > pagination_1.MAX_PAGE_SIZE) {
|
|
20
|
+
// 用 commander 的 InvalidArgumentError:它会打印一行提示,而不是整个堆栈
|
|
21
|
+
throw new commander_1.InvalidArgumentError(`--limit must be between 1 and ${pagination_1.MAX_PAGE_SIZE}`);
|
|
22
|
+
}
|
|
23
|
+
return limit;
|
|
24
|
+
}
|
|
13
25
|
function createQueryCommand() {
|
|
14
26
|
const cmd = new commander_1.Command('query');
|
|
15
27
|
cmd.description('Query LTS logs')
|
|
@@ -28,10 +40,10 @@ function createQueryCommand() {
|
|
|
28
40
|
.option('--app <appName>', 'Filter by appName field, merged with -q via AND')
|
|
29
41
|
.option('--content <keyword>', 'Filter by log content (content:<keyword>), merged with -q via AND')
|
|
30
42
|
.option('--request-id <id>', 'Filter by REQUEST_ID field; output in chronological order')
|
|
31
|
-
.option('-l, --limit <limit>',
|
|
43
|
+
.option('-l, --limit <limit>', `Page size; with --all it also caps the total (max ${pagination_1.MAX_PAGE_SIZE})`, parseLimitOption, 100)
|
|
32
44
|
.option('-r, --reverse', 'Reverse order (newest first)')
|
|
33
45
|
.option('-f, --format <format>', 'Output format: json, table, pretty', 'pretty')
|
|
34
|
-
.option('--all',
|
|
46
|
+
.option('--all', `Follow the cursor to the end (up to ${ALL_CAP} logs unless -l is given)`)
|
|
35
47
|
.action(async (options) => {
|
|
36
48
|
const config = (0, common_1.loadCheckedConfig)();
|
|
37
49
|
const { groupId, streamId } = (0, common_1.resolveGroupStream)(options, config);
|
|
@@ -57,9 +69,11 @@ function createQueryCommand() {
|
|
|
57
69
|
console.log((0, output_1.formatOutput)(result, format));
|
|
58
70
|
}
|
|
59
71
|
else {
|
|
60
|
-
// --all
|
|
72
|
+
// --all:沿游标读到末尾。显式给了 -l 就以它为上限(帮助里就是这么写的),
|
|
73
|
+
// 否则用 ALL_CAP 防止失控。
|
|
74
|
+
const max = cmd.getOptionValueSource('limit') === 'cli' ? options.limit : ALL_CAP;
|
|
61
75
|
const result = await (0, pagination_1.fetchAllPages)((p) => client.queryLogs(p), params, {
|
|
62
|
-
max
|
|
76
|
+
max,
|
|
63
77
|
pageSize: options.limit,
|
|
64
78
|
});
|
|
65
79
|
(0, common_1.warnIfIncomplete)(result);
|
|
@@ -3,6 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.traceCandidateFields = traceCandidateFields;
|
|
7
|
+
exports.buildTraceExpression = buildTraceExpression;
|
|
6
8
|
exports.createTraceCommand = createTraceCommand;
|
|
7
9
|
const commander_1 = require("commander");
|
|
8
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
@@ -12,20 +14,39 @@ const output_1 = require("../../output");
|
|
|
12
14
|
const config_1 = require("../../config");
|
|
13
15
|
const common_1 = require("./common");
|
|
14
16
|
/**
|
|
15
|
-
*
|
|
17
|
+
* 默认字段的选择顺序:`--field` → `config.traceField` → 运行时发现。
|
|
16
18
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
19
|
+
* 以前第三步写死 `content`(全文检索)。实测不对:结构化日志的 ID 住在 labels 里
|
|
20
|
+
* (REQUEST_ID / trace_id …),而全文索引只覆盖正文,`content:"<uuid>"` 一条都
|
|
21
|
+
* 匹配不到——而且不报错,只是空结果。所以第三步改成读索引配置,把形如
|
|
22
|
+
* request/trace/span + id 的字段都 OR 上,再由 full-text 兜底。
|
|
20
23
|
*/
|
|
21
24
|
const DEFAULT_TRACE_FIELD = 'content';
|
|
25
|
+
/** 字段名能凭空进查询语句(不能含连字符,否则会被当成运算符) */
|
|
26
|
+
const SAFE_FIELD_NAME = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
27
|
+
const ID_FIELD_PATTERN = /(request|trace|span)[_-]?id$/i;
|
|
28
|
+
/** 从索引配置里挑出可能装链路 ID 的字段名(运行时发现,不维护字段表) */
|
|
29
|
+
function traceCandidateFields(index) {
|
|
30
|
+
const idFields = index.fields
|
|
31
|
+
.map((f) => f.name)
|
|
32
|
+
.filter((name) => SAFE_FIELD_NAME.test(name) && ID_FIELD_PATTERN.test(name));
|
|
33
|
+
// 全文索引用在两种情况下:有 ID 字段时做兜底,没有 ID 字段时是唯一手段
|
|
34
|
+
if (idFields.length === 0 || index.fullText.enabled) {
|
|
35
|
+
return [...idFields, DEFAULT_TRACE_FIELD];
|
|
36
|
+
}
|
|
37
|
+
return idFields;
|
|
38
|
+
}
|
|
39
|
+
/** 候选字段拼成 OR 表达式,值统一加双引号(含连字符的 UUID 不分词就匹配不到) */
|
|
40
|
+
function buildTraceExpression(fields, requestId) {
|
|
41
|
+
return fields.map((field) => `${field}:"${requestId}"`).join(' OR ');
|
|
42
|
+
}
|
|
22
43
|
function createTraceCommand() {
|
|
23
44
|
const cmd = new commander_1.Command('trace');
|
|
24
45
|
cmd.description('Trace a request by ID — fetch the full request chain in chronological order')
|
|
25
46
|
.argument('<requestId>', 'Value to trace (request ID, trace ID, or any search text)')
|
|
26
47
|
.option('-g, --group-id <groupId>', 'Log group ID (can also be set via config)')
|
|
27
48
|
.option('-s, --stream-id <streamId>', 'Log stream ID (can also be set via config)')
|
|
28
|
-
.option('--field <name>',
|
|
49
|
+
.option('--field <name>', 'Field to match. Defaults to config.traceField, then the ID fields found in the stream index')
|
|
29
50
|
.option('--last <duration>', 'Relative time range: 30m, 2h, 1d, 7d (default: 24h)')
|
|
30
51
|
.option('--start-time <startTime>', 'Start time (ISO 8601)')
|
|
31
52
|
.option('--end-time <endTime>', 'End time (ISO 8601)')
|
|
@@ -38,20 +59,23 @@ function createTraceCommand() {
|
|
|
38
59
|
const { groupId, streamId } = (0, common_1.resolveGroupStream)(options, config);
|
|
39
60
|
const timeParams = (0, common_1.resolveTimeParams)(options, '24h');
|
|
40
61
|
const format = (0, common_1.resolveFormat)(options.format);
|
|
41
|
-
const
|
|
42
|
-
// 值加双引号:含连字符的 UUID 不加引号会被分词导致匹配不到
|
|
43
|
-
const query = `${field}:"${requestId}"`;
|
|
62
|
+
const explicitField = options.field || (0, config_1.getConfig)('traceField');
|
|
44
63
|
const client = new client_1.LTSClient(config);
|
|
64
|
+
// 链路按时间正序,且不加应用过滤:一条链路本来就会横跨多个应用
|
|
45
65
|
const params = {
|
|
46
66
|
groupId,
|
|
47
67
|
streamId,
|
|
48
68
|
...timeParams,
|
|
49
|
-
|
|
50
|
-
reverse: false, // 链路按时间正序
|
|
69
|
+
reverse: false,
|
|
51
70
|
};
|
|
52
71
|
try {
|
|
72
|
+
const fields = explicitField
|
|
73
|
+
? [explicitField]
|
|
74
|
+
: await discoverTraceFields(client, groupId, streamId);
|
|
75
|
+
params.query = buildTraceExpression(fields, requestId);
|
|
53
76
|
const maxTotal = options.limit || 200;
|
|
54
|
-
//
|
|
77
|
+
// 每页 500 条:服务端单页最多 5000,但 500 的首屏延迟明显更好,
|
|
78
|
+
// 而一条链路本来就只有几十条,多跑几页也不痛不痒
|
|
55
79
|
const pageSize = Math.min(maxTotal, 500);
|
|
56
80
|
const result = await (0, pagination_1.fetchAllPages)((p) => client.queryLogs(p), params, {
|
|
57
81
|
max: maxTotal,
|
|
@@ -61,6 +85,10 @@ function createTraceCommand() {
|
|
|
61
85
|
if (result.truncated) {
|
|
62
86
|
console.error(`Warning: stopped at --limit ${maxTotal}; the chain may continue. Re-run with a larger --limit.`);
|
|
63
87
|
}
|
|
88
|
+
if (result.logs.length === 0) {
|
|
89
|
+
console.error(`No logs matched "${requestId}" in ${fields.join(', ')}. ` +
|
|
90
|
+
'Widen --last, or pick the exact field with --field <name>.');
|
|
91
|
+
}
|
|
64
92
|
let output = (0, output_1.formatOutput)({ logs: result.logs, total: result.total }, format);
|
|
65
93
|
// pretty 格式下高亮 requestId,便于肉眼定位
|
|
66
94
|
if (format === 'pretty' && result.logs.length > 0) {
|
|
@@ -74,3 +102,15 @@ function createTraceCommand() {
|
|
|
74
102
|
});
|
|
75
103
|
return cmd;
|
|
76
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* 读一次索引配置来决定搜哪些字段。读不到(权限/接口异常)时退回全文检索:
|
|
107
|
+
* 宁可退化成以前的行为,也不要让 trace 直接报错。
|
|
108
|
+
*/
|
|
109
|
+
async function discoverTraceFields(client, groupId, streamId) {
|
|
110
|
+
try {
|
|
111
|
+
return traceCandidateFields(await client.getStreamIndex(groupId, streamId));
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return [DEFAULT_TRACE_FIELD];
|
|
115
|
+
}
|
|
116
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -65,25 +65,25 @@ program
|
|
|
65
65
|
.name('lts-cli')
|
|
66
66
|
.description('Huawei Cloud LTS (Log Tank Service) CLI — query logs from the terminal')
|
|
67
67
|
.version(readVersion())
|
|
68
|
-
.addHelpText('after', `
|
|
69
|
-
Examples:
|
|
70
|
-
$ lts-cli init Setup credentials interactively
|
|
71
|
-
$ lts-cli init --from-url <url> Import region/groupId/streamId from a console URL
|
|
72
|
-
$ lts-cli config --list Show current configuration
|
|
73
|
-
$ lts-cli groups List log groups
|
|
74
|
-
$ lts-cli streams List log streams in a group
|
|
75
|
-
$ lts-cli fields Show the index config of a log stream
|
|
76
|
-
$ lts-cli query --last 2h -q 'level:error' --all
|
|
77
|
-
$ lts-cli trace <requestId> Fetch the full request chain
|
|
78
|
-
|
|
79
|
-
Environment variables:
|
|
80
|
-
LTS_AK Access Key
|
|
81
|
-
LTS_SK Secret Key
|
|
82
|
-
LTS_PROJECT_ID Project ID
|
|
83
|
-
LTS_REGION Region
|
|
84
|
-
LTS_ENDPOINT Service endpoint
|
|
85
|
-
LTS_GROUP_ID Default log group ID
|
|
86
|
-
LTS_STREAM_ID Default log stream ID
|
|
68
|
+
.addHelpText('after', `
|
|
69
|
+
Examples:
|
|
70
|
+
$ lts-cli init Setup credentials interactively
|
|
71
|
+
$ lts-cli init --from-url <url> Import region/groupId/streamId from a console URL
|
|
72
|
+
$ lts-cli config --list Show current configuration
|
|
73
|
+
$ lts-cli groups List log groups
|
|
74
|
+
$ lts-cli streams List log streams in a group
|
|
75
|
+
$ lts-cli fields Show the index config of a log stream
|
|
76
|
+
$ lts-cli query --last 2h -q 'level:error' --all
|
|
77
|
+
$ lts-cli trace <requestId> Fetch the full request chain
|
|
78
|
+
|
|
79
|
+
Environment variables:
|
|
80
|
+
LTS_AK Access Key
|
|
81
|
+
LTS_SK Secret Key
|
|
82
|
+
LTS_PROJECT_ID Project ID
|
|
83
|
+
LTS_REGION Region
|
|
84
|
+
LTS_ENDPOINT Service endpoint
|
|
85
|
+
LTS_GROUP_ID Default log group ID
|
|
86
|
+
LTS_STREAM_ID Default log stream ID
|
|
87
87
|
`);
|
|
88
88
|
program.addCommand((0, init_1.createInitCommand)());
|
|
89
89
|
program.addCommand((0, query_1.createQueryCommand)());
|
|
@@ -99,6 +99,7 @@ program.addCommand((0, config_1.createConfigCommand)());
|
|
|
99
99
|
*/
|
|
100
100
|
const QUERY_OPTS = '-g --group-id -s --stream-id --start-time --end-time --st --et --last -q --query --app --content --request-id -l --limit -r --reverse -f --format --all';
|
|
101
101
|
const TRACE_OPTS = '-g --group-id -s --stream-id --field --last --start-time --end-time --st --et -l --limit -f --format';
|
|
102
|
+
const CONTEXT_OPTS = '-g --group-id -s --stream-id --line-num --time --before --after -f --format';
|
|
102
103
|
const VALUE_OPTS = '-g --group-id -s --stream-id -f --format';
|
|
103
104
|
// Shell completion support
|
|
104
105
|
const completionCmd = new commander_1.Command('completion')
|
|
@@ -107,179 +108,200 @@ const completionCmd = new commander_1.Command('completion')
|
|
|
107
108
|
.action((shell) => {
|
|
108
109
|
switch (shell) {
|
|
109
110
|
case 'bash':
|
|
110
|
-
console.log(`# lts-cli bash completion — add to ~/.bashrc or ~/.bash_profile
|
|
111
|
-
_lts_cli_completion() {
|
|
112
|
-
local words cword
|
|
113
|
-
_get_comp_words_by_ref -n : words cword
|
|
114
|
-
local cur="\${words[cword]}"
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
111
|
+
console.log(`# lts-cli bash completion — add to ~/.bashrc or ~/.bash_profile
|
|
112
|
+
_lts_cli_completion() {
|
|
113
|
+
local words cword
|
|
114
|
+
_get_comp_words_by_ref -n : words cword
|
|
115
|
+
local cur="\${words[cword]}"
|
|
116
|
+
local prev="\${words[cword-1]}"
|
|
117
|
+
|
|
118
|
+
if (( cword == 1 )); then
|
|
119
|
+
COMPREPLY=( $(compgen -W "query trace context groups streams fields config init completion" -- "\$cur") )
|
|
120
|
+
return
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
local cmd="\${words[1]}"
|
|
124
|
+
case "\$cmd" in
|
|
125
|
+
query)
|
|
126
|
+
case "\$prev" in
|
|
127
|
+
-g|--group-id|-s|--stream-id|-q|--query|--app|--content|--request-id|--start-time|--end-time|--st|--et|--last|-l|--limit|-f|--format)
|
|
128
|
+
COMPREPLY=()
|
|
129
|
+
return
|
|
130
|
+
;;
|
|
131
|
+
*)
|
|
132
|
+
COMPREPLY=( $(compgen -W "${QUERY_OPTS}" -- "\$cur") )
|
|
133
|
+
return
|
|
134
|
+
;;
|
|
135
|
+
esac
|
|
136
|
+
;;
|
|
137
|
+
trace)
|
|
138
|
+
case "\$prev" in
|
|
139
|
+
-g|--group-id|-s|--stream-id|--field|--start-time|--end-time|--st|--et|--last|-l|--limit|-f|--format)
|
|
140
|
+
COMPREPLY=()
|
|
141
|
+
return
|
|
142
|
+
;;
|
|
143
|
+
*)
|
|
144
|
+
COMPREPLY=( $(compgen -W "${TRACE_OPTS}" -- "\$cur") )
|
|
145
|
+
return
|
|
146
|
+
;;
|
|
147
|
+
esac
|
|
148
|
+
;;
|
|
149
|
+
context)
|
|
150
|
+
case "\$prev" in
|
|
151
|
+
-g|--group-id|-s|--stream-id|--line-num|--time|--before|--after|-f|--format)
|
|
152
|
+
COMPREPLY=()
|
|
153
|
+
return
|
|
154
|
+
;;
|
|
155
|
+
*)
|
|
156
|
+
COMPREPLY=( $(compgen -W "${CONTEXT_OPTS}" -- "\$cur") )
|
|
157
|
+
return
|
|
158
|
+
;;
|
|
159
|
+
esac
|
|
160
|
+
;;
|
|
161
|
+
groups)
|
|
162
|
+
COMPREPLY=( $(compgen -W "-f --format" -- "\$cur") )
|
|
163
|
+
return
|
|
164
|
+
;;
|
|
165
|
+
streams)
|
|
166
|
+
COMPREPLY=( $(compgen -W "${VALUE_OPTS}" -- "\$cur") )
|
|
167
|
+
return
|
|
168
|
+
;;
|
|
169
|
+
fields)
|
|
170
|
+
COMPREPLY=( $(compgen -W "${VALUE_OPTS}" -- "\$cur") )
|
|
171
|
+
return
|
|
172
|
+
;;
|
|
173
|
+
config)
|
|
174
|
+
COMPREPLY=( $(compgen -W "--set --get --list --unset" -- "\$cur") )
|
|
175
|
+
return
|
|
176
|
+
;;
|
|
177
|
+
completion)
|
|
178
|
+
COMPREPLY=( $(compgen -W "bash zsh powershell" -- "\$cur") )
|
|
179
|
+
return
|
|
180
|
+
;;
|
|
181
|
+
*)
|
|
182
|
+
COMPREPLY=()
|
|
183
|
+
return
|
|
184
|
+
;;
|
|
185
|
+
esac
|
|
186
|
+
}
|
|
165
187
|
complete -F _lts_cli_completion lts-cli`);
|
|
166
188
|
break;
|
|
167
189
|
case 'zsh':
|
|
168
|
-
console.log(`# lts-cli zsh completion — add to ~/.zshrc
|
|
169
|
-
#compdef lts-cli
|
|
170
|
-
_lts_cli() {
|
|
171
|
-
local -a commands
|
|
172
|
-
commands=(
|
|
173
|
-
'query:Query LTS logs'
|
|
174
|
-
'trace:Trace a request by ID'
|
|
175
|
-
'context:View logs before/after a log entry'
|
|
176
|
-
'groups:List log groups'
|
|
177
|
-
'streams:List log streams'
|
|
178
|
-
'fields:Show the index config of a log stream'
|
|
179
|
-
'config:Manage configuration'
|
|
180
|
-
'init:Interactive setup wizard'
|
|
181
|
-
'completion:Generate shell completion script'
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
local context state state_descr line
|
|
185
|
-
typeset -A opt_args
|
|
186
|
-
|
|
187
|
-
_arguments -C \\
|
|
188
|
-
'1: :->command' \\
|
|
189
|
-
'*:: :->args'
|
|
190
|
-
|
|
191
|
-
case "\$state" in
|
|
192
|
-
command)
|
|
193
|
-
_describe 'command' commands
|
|
194
|
-
;;
|
|
195
|
-
args)
|
|
196
|
-
case "\$line[1]" in
|
|
197
|
-
query)
|
|
198
|
-
_arguments \\
|
|
199
|
-
'-g[Log group ID]:group ID:' \\
|
|
200
|
-
'-s[Log stream ID]:stream ID:' \\
|
|
201
|
-
'--start-time[Start time (ISO 8601)]:time:' \\
|
|
202
|
-
'--end-time[End time (ISO 8601)]:time:' \\
|
|
203
|
-
'--st[Alias for --start-time]:time:' \\
|
|
204
|
-
'--et[Alias for --end-time]:time:' \\
|
|
205
|
-
'--last[Relative range: 30m, 2h, 1d, 7d]:duration:' \\
|
|
206
|
-
'-q[Query expression]:query:' \\
|
|
207
|
-
'--app[Filter by appName]:appName:' \\
|
|
208
|
-
'--content[Filter by content]:keyword:' \\
|
|
209
|
-
'--request-id[Filter by REQUEST_ID]:id:' \\
|
|
210
|
-
'-l[Page size]:number:' \\
|
|
211
|
-
'-r[Reverse order]' \\
|
|
212
|
-
'-f[Output format]:format:(json table pretty)' \\
|
|
213
|
-
'--all[Follow the cursor to the end]'
|
|
214
|
-
;;
|
|
215
|
-
trace)
|
|
216
|
-
_arguments \\
|
|
217
|
-
'-g[Log group ID]:group ID:' \\
|
|
218
|
-
'-s[Log stream ID]:stream ID:' \\
|
|
219
|
-
'--field[Field to match]:field:' \\
|
|
220
|
-
'--last[Relative range: 30m, 2h, 1d, 7d]:duration:' \\
|
|
221
|
-
'--start-time[Start time (ISO 8601)]:time:' \\
|
|
222
|
-
'--end-time[End time (ISO 8601)]:time:' \\
|
|
223
|
-
'--st[Alias for --start-time]:time:' \\
|
|
224
|
-
'--et[Alias for --end-time]:time:' \\
|
|
225
|
-
'-l[Max logs]:number:' \\
|
|
226
|
-
'-f[Output format]:format:(json table pretty)'
|
|
227
|
-
;;
|
|
228
|
-
fields|groups|streams)
|
|
229
|
-
_arguments \\
|
|
230
|
-
'-g[Log group ID]:group ID:' \\
|
|
231
|
-
'-s[Log stream ID]:stream ID:' \\
|
|
232
|
-
'-f[Output format]:format:(json table pretty)'
|
|
233
|
-
;;
|
|
234
|
-
config)
|
|
235
|
-
_arguments \\
|
|
236
|
-
'--set[Set config value]:key=value:' \\
|
|
237
|
-
'--get[Get config value]:key:' \\
|
|
238
|
-
'--list[List all config]' \\
|
|
239
|
-
'--unset[Remove config value]:key:'
|
|
240
|
-
;;
|
|
241
|
-
completion)
|
|
242
|
-
_values 'shell' bash zsh powershell
|
|
243
|
-
;;
|
|
244
|
-
esac
|
|
245
|
-
;;
|
|
246
|
-
esac
|
|
247
|
-
}
|
|
190
|
+
console.log(`# lts-cli zsh completion — add to ~/.zshrc
|
|
191
|
+
#compdef lts-cli
|
|
192
|
+
_lts_cli() {
|
|
193
|
+
local -a commands
|
|
194
|
+
commands=(
|
|
195
|
+
'query:Query LTS logs'
|
|
196
|
+
'trace:Trace a request by ID'
|
|
197
|
+
'context:View logs before/after a log entry'
|
|
198
|
+
'groups:List log groups'
|
|
199
|
+
'streams:List log streams'
|
|
200
|
+
'fields:Show the index config of a log stream'
|
|
201
|
+
'config:Manage configuration'
|
|
202
|
+
'init:Interactive setup wizard'
|
|
203
|
+
'completion:Generate shell completion script'
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
local context state state_descr line
|
|
207
|
+
typeset -A opt_args
|
|
208
|
+
|
|
209
|
+
_arguments -C \\
|
|
210
|
+
'1: :->command' \\
|
|
211
|
+
'*:: :->args'
|
|
212
|
+
|
|
213
|
+
case "\$state" in
|
|
214
|
+
command)
|
|
215
|
+
_describe 'command' commands
|
|
216
|
+
;;
|
|
217
|
+
args)
|
|
218
|
+
case "\$line[1]" in
|
|
219
|
+
query)
|
|
220
|
+
_arguments \\
|
|
221
|
+
'-g[Log group ID]:group ID:' \\
|
|
222
|
+
'-s[Log stream ID]:stream ID:' \\
|
|
223
|
+
'--start-time[Start time (ISO 8601)]:time:' \\
|
|
224
|
+
'--end-time[End time (ISO 8601)]:time:' \\
|
|
225
|
+
'--st[Alias for --start-time]:time:' \\
|
|
226
|
+
'--et[Alias for --end-time]:time:' \\
|
|
227
|
+
'--last[Relative range: 30m, 2h, 1d, 7d]:duration:' \\
|
|
228
|
+
'-q[Query expression]:query:' \\
|
|
229
|
+
'--app[Filter by appName]:appName:' \\
|
|
230
|
+
'--content[Filter by content]:keyword:' \\
|
|
231
|
+
'--request-id[Filter by REQUEST_ID]:id:' \\
|
|
232
|
+
'-l[Page size]:number:' \\
|
|
233
|
+
'-r[Reverse order]' \\
|
|
234
|
+
'-f[Output format]:format:(json table pretty)' \\
|
|
235
|
+
'--all[Follow the cursor to the end]'
|
|
236
|
+
;;
|
|
237
|
+
trace)
|
|
238
|
+
_arguments \\
|
|
239
|
+
'-g[Log group ID]:group ID:' \\
|
|
240
|
+
'-s[Log stream ID]:stream ID:' \\
|
|
241
|
+
'--field[Field to match]:field:' \\
|
|
242
|
+
'--last[Relative range: 30m, 2h, 1d, 7d]:duration:' \\
|
|
243
|
+
'--start-time[Start time (ISO 8601)]:time:' \\
|
|
244
|
+
'--end-time[End time (ISO 8601)]:time:' \\
|
|
245
|
+
'--st[Alias for --start-time]:time:' \\
|
|
246
|
+
'--et[Alias for --end-time]:time:' \\
|
|
247
|
+
'-l[Max logs]:number:' \\
|
|
248
|
+
'-f[Output format]:format:(json table pretty)'
|
|
249
|
+
;;
|
|
250
|
+
fields|groups|streams)
|
|
251
|
+
_arguments \\
|
|
252
|
+
'-g[Log group ID]:group ID:' \\
|
|
253
|
+
'-s[Log stream ID]:stream ID:' \\
|
|
254
|
+
'-f[Output format]:format:(json table pretty)'
|
|
255
|
+
;;
|
|
256
|
+
config)
|
|
257
|
+
_arguments \\
|
|
258
|
+
'--set[Set config value]:key=value:' \\
|
|
259
|
+
'--get[Get config value]:key:' \\
|
|
260
|
+
'--list[List all config]' \\
|
|
261
|
+
'--unset[Remove config value]:key:'
|
|
262
|
+
;;
|
|
263
|
+
completion)
|
|
264
|
+
_values 'shell' bash zsh powershell
|
|
265
|
+
;;
|
|
266
|
+
esac
|
|
267
|
+
;;
|
|
268
|
+
esac
|
|
269
|
+
}
|
|
248
270
|
compdef _lts_cli lts-cli`);
|
|
249
271
|
break;
|
|
250
272
|
case 'powershell':
|
|
251
|
-
console.log(`# lts-cli PowerShell completion — add to $PROFILE
|
|
252
|
-
Register-ArgumentCompleter -CommandName lts-cli -ScriptBlock {
|
|
253
|
-
param(\$wordToComplete, \$commandAst, \$cursorPosition)
|
|
254
|
-
|
|
255
|
-
\$commands = @('query', 'trace', 'context', 'groups', 'streams', 'fields', 'config', 'init', 'completion')
|
|
256
|
-
|
|
257
|
-
\$args = \$commandAst.ToString() -split '\\s+'
|
|
258
|
-
if (\$args.Count -le 1) {
|
|
259
|
-
return \$commands | Where-Object { \$_ -like "\$wordToComplete*" }
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
switch (\$args[1]) {
|
|
263
|
-
'query' {
|
|
264
|
-
\$opts = @('-g', '-s', '--start-time', '--end-time', '--st', '--et', '--last', '-q', '--app', '--content', '--request-id', '-l', '-r', '-f', '--all')
|
|
265
|
-
return \$opts | Where-Object { \$_ -like "\$wordToComplete*" }
|
|
266
|
-
}
|
|
267
|
-
'trace' {
|
|
268
|
-
\$opts = @('-g', '-s', '--field', '--last', '--start-time', '--end-time', '--st', '--et', '-l', '-f')
|
|
269
|
-
return \$opts | Where-Object { \$_ -like "\$wordToComplete*" }
|
|
270
|
-
}
|
|
271
|
-
'fields' {
|
|
272
|
-
\$opts = @('-g', '-s', '-f')
|
|
273
|
-
return \$opts | Where-Object { \$_ -like "\$wordToComplete*" }
|
|
274
|
-
}
|
|
275
|
-
'config' {
|
|
276
|
-
\$opts = @('--set', '--get', '--list', '--unset')
|
|
277
|
-
return \$opts | Where-Object { \$_ -like "\$wordToComplete*" }
|
|
278
|
-
}
|
|
279
|
-
'completion' {
|
|
280
|
-
return @('bash', 'zsh', 'powershell') | Where-Object { \$_ -like "\$wordToComplete*" }
|
|
281
|
-
}
|
|
282
|
-
}
|
|
273
|
+
console.log(`# lts-cli PowerShell completion — add to $PROFILE
|
|
274
|
+
Register-ArgumentCompleter -CommandName lts-cli -ScriptBlock {
|
|
275
|
+
param(\$wordToComplete, \$commandAst, \$cursorPosition)
|
|
276
|
+
|
|
277
|
+
\$commands = @('query', 'trace', 'context', 'groups', 'streams', 'fields', 'config', 'init', 'completion')
|
|
278
|
+
|
|
279
|
+
\$args = \$commandAst.ToString() -split '\\s+'
|
|
280
|
+
if (\$args.Count -le 1) {
|
|
281
|
+
return \$commands | Where-Object { \$_ -like "\$wordToComplete*" }
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
switch (\$args[1]) {
|
|
285
|
+
'query' {
|
|
286
|
+
\$opts = @('-g', '-s', '--start-time', '--end-time', '--st', '--et', '--last', '-q', '--app', '--content', '--request-id', '-l', '-r', '-f', '--all')
|
|
287
|
+
return \$opts | Where-Object { \$_ -like "\$wordToComplete*" }
|
|
288
|
+
}
|
|
289
|
+
'trace' {
|
|
290
|
+
\$opts = @('-g', '-s', '--field', '--last', '--start-time', '--end-time', '--st', '--et', '-l', '-f')
|
|
291
|
+
return \$opts | Where-Object { \$_ -like "\$wordToComplete*" }
|
|
292
|
+
}
|
|
293
|
+
'fields' {
|
|
294
|
+
\$opts = @('-g', '-s', '-f')
|
|
295
|
+
return \$opts | Where-Object { \$_ -like "\$wordToComplete*" }
|
|
296
|
+
}
|
|
297
|
+
'config' {
|
|
298
|
+
\$opts = @('--set', '--get', '--list', '--unset')
|
|
299
|
+
return \$opts | Where-Object { \$_ -like "\$wordToComplete*" }
|
|
300
|
+
}
|
|
301
|
+
'completion' {
|
|
302
|
+
return @('bash', 'zsh', 'powershell') | Where-Object { \$_ -like "\$wordToComplete*" }
|
|
303
|
+
}
|
|
304
|
+
}
|
|
283
305
|
}`);
|
|
284
306
|
break;
|
|
285
307
|
default:
|
package/dist/lts/client.js
CHANGED
|
@@ -1,8 +1,43 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.LTSClient = void 0;
|
|
4
37
|
exports.resolveTimeRange = resolveTimeRange;
|
|
5
38
|
exports.parseQueryExpression = parseQueryExpression;
|
|
39
|
+
exports.anchorTimeFromLineNum = anchorTimeFromLineNum;
|
|
40
|
+
const path = __importStar(require("path"));
|
|
6
41
|
const LtsClient_1 = require("@huaweicloud/huaweicloud-sdk-lts/v2/LtsClient");
|
|
7
42
|
const ClientBuilder_1 = require("@huaweicloud/huaweicloud-sdk-core/ClientBuilder");
|
|
8
43
|
const BasicCredentials_1 = require("@huaweicloud/huaweicloud-sdk-core/auth/BasicCredentials");
|
|
@@ -15,6 +50,32 @@ const ListLogGroupsRequest_1 = require("@huaweicloud/huaweicloud-sdk-lts/v2/mode
|
|
|
15
50
|
const ListLogStreamsRequest_1 = require("@huaweicloud/huaweicloud-sdk-lts/v2/model/ListLogStreamsRequest");
|
|
16
51
|
const ListLogStreamIndexRequest_1 = require("@huaweicloud/huaweicloud-sdk-lts/v2/model/ListLogStreamIndexRequest");
|
|
17
52
|
const pagination_1 = require("./pagination");
|
|
53
|
+
/**
|
|
54
|
+
* 华为云 SDK 把 log4js 的 console appender 指向 stdout,任何 API 错误都会把整个
|
|
55
|
+
* axios 错误对象(含 Authorization 头)倒进 stdout——`-f json` 的输出因此变成
|
|
56
|
+
* 不可解析的混合体。这里把 SDK 日志改道 stderr 并默认关闭:CLI 自己会打印
|
|
57
|
+
* 一行可读的错误,6KB 的 axios dump 只会淹没它。要排查 SDK 层问题时设
|
|
58
|
+
* LTS_CLI_DEBUG=1。
|
|
59
|
+
*
|
|
60
|
+
* 解析到的必须是 SDK 自己那个 log4js 实例,否则 configure 改的是另一份模块状态。
|
|
61
|
+
* 拿不到就保持原状:日志难看,但不影响结果本身。
|
|
62
|
+
*/
|
|
63
|
+
function configureSdkLogger() {
|
|
64
|
+
try {
|
|
65
|
+
const corePkg = require.resolve('@huaweicloud/huaweicloud-sdk-core/package.json');
|
|
66
|
+
const log4js = require(require.resolve('log4js', { paths: [path.dirname(corePkg)] }));
|
|
67
|
+
log4js.configure({
|
|
68
|
+
appenders: { sdkStderr: { type: 'stderr' } },
|
|
69
|
+
categories: {
|
|
70
|
+
default: { appenders: ['sdkStderr'], level: process.env.LTS_CLI_DEBUG ? 'all' : 'off' },
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
/* 无法改道时不做任何事 */
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
configureSdkLogger();
|
|
18
79
|
function sleep(ms) {
|
|
19
80
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
20
81
|
}
|
|
@@ -71,6 +132,14 @@ function parseQueryExpression(expr) {
|
|
|
71
132
|
}
|
|
72
133
|
return { keywords: trimmed, isAnalysisQuery: false };
|
|
73
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* line_num 是 19 位纳秒时间戳,前 13 位就是毫秒(实测与日志时间严格对齐)。
|
|
137
|
+
* `context` 接口要求传毫秒时间戳,而 LTS 的日志记录本身不带时间字段,只能从
|
|
138
|
+
* line_num 推。格式不符时返回 undefined,让调用方要求显式 --time。
|
|
139
|
+
*/
|
|
140
|
+
function anchorTimeFromLineNum(lineNum) {
|
|
141
|
+
return /^\d{19}$/.test(lineNum) ? lineNum.slice(0, 13) : undefined;
|
|
142
|
+
}
|
|
74
143
|
/** 服务端默认在命中词上包裹 <HighLightTag>,输出前剥离 */
|
|
75
144
|
function stripHighlightTags(value) {
|
|
76
145
|
return value.replace(/<\/?HighLightTag>/g, '');
|
|
@@ -118,7 +187,7 @@ class LTSClient {
|
|
|
118
187
|
// 游标翻页:首屏不带游标,后续页把上一页末条的 line_num 当作续读起点
|
|
119
188
|
if (params.lineNum) {
|
|
120
189
|
queryParams.withLineNum(params.lineNum);
|
|
121
|
-
queryParams.withSearchType(params.searchType ??
|
|
190
|
+
queryParams.withSearchType(params.searchType ?? pagination_1.CONTINUE_SEARCH_TYPE);
|
|
122
191
|
}
|
|
123
192
|
const request = new ListLogsRequest_1.ListLogsRequest(params.groupId, params.streamId, 'application/json')
|
|
124
193
|
.withBody(queryParams);
|
package/dist/lts/pagination.js
CHANGED
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.MAX_PAGE_SIZE = exports.CONTINUE_SEARCH_TYPE = void 0;
|
|
4
4
|
exports.nextCursor = nextCursor;
|
|
5
|
+
exports.shiftLineNum = shiftLineNum;
|
|
5
6
|
exports.fetchAllPages = fetchAllPages;
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
/**
|
|
8
|
+
* LTS 的翻页语义:服务端没有 offset,只有「从某个 line_num 继续读」。
|
|
9
|
+
*
|
|
10
|
+
* 第一页不带游标;之后每页把上一页最后一条的 line_num 作为续读起点。
|
|
11
|
+
*
|
|
12
|
+
* 续读方向只有一种:`forwards`,它表示「沿当前排序方向继续」,与 `is_desc` 无关。
|
|
13
|
+
* 实测(2026-10,cn-south-1)降序查询配 `backwards` 时,第二页与第一页的 line_num
|
|
14
|
+
* 重叠 38%~100%:正序配 backwards 更直接,返回的就是第一页那一段。而且全程不报
|
|
15
|
+
* 错。所以这里不再按排序方向推方向——反了会静默产出重复数据,比报错难查得多。
|
|
16
|
+
* 对照:无论升降序,`forwards` 连翻 10 页、每页 100 条,边界重叠为 0。
|
|
17
|
+
*
|
|
18
|
+
* 游标是排他的:从 line_num=L 续读只会返回严格在 L 之后的记录。但 line_num 在
|
|
19
|
+
* 纳秒级并不唯一——同一纳秒的多条日志共用一个值,一组日志正好被页边界切开时,
|
|
20
|
+
* 后半组会被整组跳过(实测:单页查得到 18 条,--all 只拿到 17 条)。所以续读
|
|
21
|
+
* 游标取 L 的前一格(正序 L-1、降序 L+1),让下一页从这一组的组头开始读,再按
|
|
22
|
+
* 「line_num + 内容 + labels」跳过本页已经收走的那几条。line_num 不唯一,不能
|
|
23
|
+
* 只按它去重,否则会把同纳秒的真实日志一起丢掉。
|
|
24
|
+
*/
|
|
25
|
+
/** 续读游标时唯一正确的 search_type。 */
|
|
26
|
+
exports.CONTINUE_SEARCH_TYPE = 'forwards';
|
|
27
|
+
/** 服务端单页上限:limit=5001 直接回 LTS.1903 "limit must be between 1 and 5000"。 */
|
|
28
|
+
exports.MAX_PAGE_SIZE = 5000;
|
|
10
29
|
/** 游标取本页最后一条的 line_num;拿不到就返回 undefined(调用方据此停止)。 */
|
|
11
30
|
function nextCursor(logs) {
|
|
12
31
|
if (logs.length === 0)
|
|
@@ -14,39 +33,119 @@ function nextCursor(logs) {
|
|
|
14
33
|
const last = logs[logs.length - 1].lineNum;
|
|
15
34
|
return last ? last : undefined;
|
|
16
35
|
}
|
|
36
|
+
/** line_num 是纳秒整数;游标前后挪一格,才能把被页边界切开的同值组重新纳入。 */
|
|
37
|
+
function shiftLineNum(lineNum, delta) {
|
|
38
|
+
if (!/^\d+$/.test(lineNum))
|
|
39
|
+
return undefined;
|
|
40
|
+
return (BigInt(lineNum) + delta).toString();
|
|
41
|
+
}
|
|
42
|
+
/** line_num 不唯一,同纳秒同内容也可能是两条真实日志;回放判定要带上内容与 labels。 */
|
|
43
|
+
function entrySignature(entry) {
|
|
44
|
+
const labels = Object.keys(entry.labels)
|
|
45
|
+
.sort()
|
|
46
|
+
.map((key) => `${key}=${entry.labels[key]}`)
|
|
47
|
+
.join('\u0001');
|
|
48
|
+
return `${entry.content}\u0000${labels}`;
|
|
49
|
+
}
|
|
50
|
+
/** 统计某一 line_num 组内各签名的条数,下一页据此跳过回放。 */
|
|
51
|
+
function countSignatures(entries, lineNum) {
|
|
52
|
+
const counts = new Map();
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
if (entry.lineNum !== lineNum)
|
|
55
|
+
continue;
|
|
56
|
+
const signature = entrySignature(entry);
|
|
57
|
+
counts.set(signature, (counts.get(signature) ?? 0) + 1);
|
|
58
|
+
}
|
|
59
|
+
return counts;
|
|
60
|
+
}
|
|
61
|
+
function mergeCounts(into, extra) {
|
|
62
|
+
for (const [signature, count] of extra) {
|
|
63
|
+
into.set(signature, (into.get(signature) ?? 0) + count);
|
|
64
|
+
}
|
|
65
|
+
return into;
|
|
66
|
+
}
|
|
17
67
|
async function fetchAllPages(fetchPage, params, options = {}) {
|
|
18
68
|
const max = options.max ?? 10000;
|
|
19
|
-
|
|
20
|
-
const
|
|
69
|
+
// 每页条数不能超过服务端上限,否则整次 --all 直接报错
|
|
70
|
+
const basePageSize = Math.min(options.pageSize ?? params.limit ?? 100, exports.MAX_PAGE_SIZE);
|
|
71
|
+
// forwards 沿当前排序方向推进:正序越来越晚(-1),降序越来越早(+1)
|
|
72
|
+
const step = params.reverse === true ? 1n : -1n;
|
|
21
73
|
const collected = [];
|
|
22
74
|
let cursor;
|
|
75
|
+
// 上一页末条所在的 line_num 组;下一页从该组组头重读,组内已收走的条目按签名跳过
|
|
76
|
+
let boundaryLineNum;
|
|
77
|
+
let boundaryCounts = new Map();
|
|
23
78
|
let isQueryComplete;
|
|
24
79
|
let truncated = false;
|
|
25
80
|
while (collected.length < max) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
81
|
+
// 重读组头会带回已收走的同值组;多要这么多条,保证每页仍有 pageSize 条新数据
|
|
82
|
+
const boundarySize = [...boundaryCounts.values()].reduce((sum, count) => sum + count, 0);
|
|
83
|
+
let pageSize = Math.min(basePageSize + boundarySize, exports.MAX_PAGE_SIZE);
|
|
84
|
+
let pageLength = 0;
|
|
85
|
+
let fresh = [];
|
|
86
|
+
let reachedEnd = false;
|
|
87
|
+
// 同纳秒的日志多到一页装不下时,重读组头永远只能拿到前 pageSize 条;
|
|
88
|
+
// 放大一页才可能越过这一组。
|
|
89
|
+
while (true) {
|
|
90
|
+
const page = await fetchPage({
|
|
91
|
+
...params,
|
|
92
|
+
limit: pageSize,
|
|
93
|
+
lineNum: cursor,
|
|
94
|
+
searchType: cursor ? exports.CONTINUE_SEARCH_TYPE : undefined,
|
|
95
|
+
});
|
|
96
|
+
if (page.isQueryComplete === false) {
|
|
97
|
+
isQueryComplete = false;
|
|
98
|
+
}
|
|
99
|
+
else if (isQueryComplete === undefined && page.isQueryComplete === true) {
|
|
100
|
+
isQueryComplete = true;
|
|
101
|
+
}
|
|
102
|
+
pageLength = page.logs.length;
|
|
103
|
+
if (pageLength === 0) {
|
|
104
|
+
reachedEnd = true;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
const pending = new Map(boundaryCounts);
|
|
108
|
+
fresh = [];
|
|
109
|
+
for (const entry of page.logs) {
|
|
110
|
+
if (boundaryLineNum !== undefined && entry.lineNum === boundaryLineNum) {
|
|
111
|
+
const signature = entrySignature(entry);
|
|
112
|
+
const remaining = pending.get(signature) ?? 0;
|
|
113
|
+
if (remaining > 0) {
|
|
114
|
+
pending.set(signature, remaining - 1);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
fresh.push(entry);
|
|
119
|
+
}
|
|
120
|
+
if (fresh.length > 0)
|
|
121
|
+
break;
|
|
122
|
+
// 不满一页且全是回放 = 同值组之后再无日志,已经到底
|
|
123
|
+
if (pageLength < pageSize) {
|
|
124
|
+
reachedEnd = true;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
if (pageSize >= exports.MAX_PAGE_SIZE) {
|
|
128
|
+
truncated = true;
|
|
129
|
+
reachedEnd = true;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
pageSize = Math.min(pageSize * 2, exports.MAX_PAGE_SIZE);
|
|
34
133
|
}
|
|
35
|
-
|
|
36
|
-
isQueryComplete = true;
|
|
37
|
-
}
|
|
38
|
-
if (page.logs.length === 0)
|
|
39
|
-
break;
|
|
40
|
-
const next = nextCursor(page.logs);
|
|
41
|
-
// 服务端把同一段原样又给了一遍:整页丢弃并停止。
|
|
42
|
-
// 先判游标再入账,否则重放的一页会被当成新数据拼进结果。
|
|
43
|
-
if (cursor !== undefined && next === cursor)
|
|
134
|
+
if (reachedEnd)
|
|
44
135
|
break;
|
|
45
|
-
collected.push(...
|
|
46
|
-
|
|
47
|
-
|
|
136
|
+
collected.push(...fresh);
|
|
137
|
+
const lastLineNum = fresh[fresh.length - 1]?.lineNum;
|
|
138
|
+
// 不满一页 = 没有更多了;游标推不动则无法继续翻页
|
|
139
|
+
const next = lastLineNum ? shiftLineNum(lastLineNum, step) : undefined;
|
|
140
|
+
if (pageLength < pageSize || !next)
|
|
48
141
|
break;
|
|
49
142
|
cursor = next;
|
|
143
|
+
// 同值组可能横跨多页:计数必须累计,否则重读组头时会把前几页收走的条目再收一遍
|
|
144
|
+
boundaryCounts =
|
|
145
|
+
lastLineNum === boundaryLineNum
|
|
146
|
+
? mergeCounts(boundaryCounts, countSignatures(fresh, lastLineNum))
|
|
147
|
+
: countSignatures(fresh, lastLineNum);
|
|
148
|
+
boundaryLineNum = lastLineNum;
|
|
50
149
|
if (collected.length >= max) {
|
|
51
150
|
truncated = true;
|
|
52
151
|
}
|
package/package.json
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@cvtoolman/huawei-lts-cli",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "Command-line tool for querying Huawei Cloud Log Tank Service (LTS) logs",
|
|
5
|
-
"main": "dist/cli/index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"lts-cli": "dist/cli/index.js"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"dist/"
|
|
11
|
-
],
|
|
12
|
-
"scripts": {
|
|
13
|
-
"build": "tsc",
|
|
14
|
-
"dev": "tsx src/cli/index.ts",
|
|
15
|
-
"start": "node dist/cli/index.js",
|
|
16
|
-
"test": "node --import tsx --test \"test/**/*.test.ts\"",
|
|
17
|
-
"typecheck": "tsc --noEmit",
|
|
18
|
-
"clean": "node -e \"require('fs').rmSync('dist', {recursive: true, force: true})\"",
|
|
19
|
-
"prepublishOnly": "npm run clean && npm run build"
|
|
20
|
-
},
|
|
21
|
-
"keywords": [
|
|
22
|
-
"huawei-cloud",
|
|
23
|
-
"huawei",
|
|
24
|
-
"lts",
|
|
25
|
-
"log-tank-service",
|
|
26
|
-
"logs",
|
|
27
|
-
"cli",
|
|
28
|
-
"log-query",
|
|
29
|
-
"cloud-logging"
|
|
30
|
-
],
|
|
31
|
-
"author": "cvtoolman",
|
|
32
|
-
"license": "MIT",
|
|
33
|
-
"repository": {
|
|
34
|
-
"type": "git",
|
|
35
|
-
"url": "git+https://github.com/jcxy/huawei-lts-cli.git"
|
|
36
|
-
},
|
|
37
|
-
"bugs": {
|
|
38
|
-
"url": "https://github.com/jcxy/huawei-lts-cli/issues"
|
|
39
|
-
},
|
|
40
|
-
"homepage": "https://github.com/jcxy/huawei-lts-cli#readme",
|
|
41
|
-
"engines": {
|
|
42
|
-
"node": ">=18.0.0"
|
|
43
|
-
},
|
|
44
|
-
"dependencies": {
|
|
45
|
-
"@huaweicloud/huaweicloud-sdk-core": "^3.1.200",
|
|
46
|
-
"@huaweicloud/huaweicloud-sdk-lts": "^3.1.200",
|
|
47
|
-
"chalk": "^4.1.2",
|
|
48
|
-
"cli-table3": "^0.6.5",
|
|
49
|
-
"commander": "^15.0.0",
|
|
50
|
-
"uuid": "^9.0.0"
|
|
51
|
-
},
|
|
52
|
-
"devDependencies": {
|
|
53
|
-
"@types/node": "^26.0.0",
|
|
54
|
-
"tsx": "^4.22.4",
|
|
55
|
-
"typescript": "^6.0.3"
|
|
56
|
-
}
|
|
57
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@cvtoolman/huawei-lts-cli",
|
|
3
|
+
"version": "2.1.1",
|
|
4
|
+
"description": "Command-line tool for querying Huawei Cloud Log Tank Service (LTS) logs",
|
|
5
|
+
"main": "dist/cli/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"lts-cli": "dist/cli/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"dev": "tsx src/cli/index.ts",
|
|
15
|
+
"start": "node dist/cli/index.js",
|
|
16
|
+
"test": "node --import tsx --test \"test/**/*.test.ts\"",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"clean": "node -e \"require('fs').rmSync('dist', {recursive: true, force: true})\"",
|
|
19
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"huawei-cloud",
|
|
23
|
+
"huawei",
|
|
24
|
+
"lts",
|
|
25
|
+
"log-tank-service",
|
|
26
|
+
"logs",
|
|
27
|
+
"cli",
|
|
28
|
+
"log-query",
|
|
29
|
+
"cloud-logging"
|
|
30
|
+
],
|
|
31
|
+
"author": "cvtoolman",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/jcxy/huawei-lts-cli.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/jcxy/huawei-lts-cli/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/jcxy/huawei-lts-cli#readme",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18.0.0"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@huaweicloud/huaweicloud-sdk-core": "^3.1.200",
|
|
46
|
+
"@huaweicloud/huaweicloud-sdk-lts": "^3.1.200",
|
|
47
|
+
"chalk": "^4.1.2",
|
|
48
|
+
"cli-table3": "^0.6.5",
|
|
49
|
+
"commander": "^15.0.0",
|
|
50
|
+
"uuid": "^9.0.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "^26.0.0",
|
|
54
|
+
"tsx": "^4.22.4",
|
|
55
|
+
"typescript": "^6.0.3"
|
|
56
|
+
}
|
|
57
|
+
}
|
package/dist/cli/options.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.queryOptions = void 0;
|
|
4
|
-
exports.queryOptions = {
|
|
5
|
-
groupId: { short: '-g', long: '--group-id <groupId>', description: 'Log group ID', required: true },
|
|
6
|
-
streamId: { short: '-s', long: '--stream-id <streamId>', description: 'Log stream ID', required: true },
|
|
7
|
-
startTime: { short: '--start-time <startTime>', long: '--start-time <startTime>', description: 'Start time (ISO 8601)', required: true },
|
|
8
|
-
endTime: { short: '--end-time <endTime>', long: '--end-time <endTime>', description: 'End time (ISO 8601)', required: true },
|
|
9
|
-
keyword: { short: '-k', long: '--keyword <keyword>', description: 'Search keyword', required: false },
|
|
10
|
-
limit: { short: '-l', long: '--limit <limit>', description: 'Limit number of results', required: false },
|
|
11
|
-
offset: { short: '-o', long: '--offset <offset>', description: 'Offset for pagination', required: false },
|
|
12
|
-
reverse: { short: '-r', long: '--reverse', description: 'Reverse order', required: false },
|
|
13
|
-
format: { short: '-f', long: '--format <format>', description: 'Output format: json, table, pretty', required: false },
|
|
14
|
-
noPaginate: { short: '', long: '--no-paginate', description: 'Disable pagination', required: false },
|
|
15
|
-
};
|