@cvtoolman/huawei-lts-cli 1.0.1 → 1.0.3

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.
@@ -3,81 +3,70 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createQueryCommand = createQueryCommand;
4
4
  const commander_1 = require("commander");
5
5
  const client_1 = require("../../lts/client");
6
- const config_1 = require("../../config");
7
6
  const output_1 = require("../../output");
7
+ const common_1 = require("./common");
8
+ // commander 的处理函数会收到 (value, previous),直接传 parseInt 会把 previous 当作进制
9
+ function parseIntOption(value) {
10
+ return parseInt(value, 10);
11
+ }
8
12
  function createQueryCommand() {
9
13
  const cmd = new commander_1.Command('query');
10
14
  cmd.description('Query LTS logs')
11
15
  .option('-g, --group-id <groupId>', 'Log group ID (can also be set via config)')
12
16
  .option('-s, --stream-id <streamId>', 'Log stream ID (can also be set via config)')
13
- .requiredOption('--start-time <startTime>', 'Start time (ISO 8601, e.g. 2024-01-01T00:00:00Z)')
14
- .requiredOption('--end-time <endTime>', 'End time (ISO 8601, e.g. 2024-01-02T00:00:00Z)')
17
+ .option('--start-time <startTime>', 'Start time (ISO 8601, e.g. 2024-01-01T00:00:00Z)')
18
+ .option('--end-time <endTime>', 'End time (ISO 8601, e.g. 2024-01-02T00:00:00Z)')
15
19
  .option('--st <startTime>', 'Alias for --start-time')
16
20
  .option('--et <endTime>', 'Alias for --end-time')
17
- .option('-k, --keyword <keyword>', 'Search keyword')
18
- .option('-l, --limit <limit>', 'Limit number of results', parseInt, 100)
19
- .option('-o, --offset <offset>', 'Offset for pagination', parseInt, 0)
21
+ .option('--last <duration>', 'Relative time range: 30m, 2h, 1d, 7d (mutually exclusive with --st/--et)')
22
+ .option('-k, --keyword <keyword>', 'Simple keyword search (legacy, use --query for advanced queries)')
23
+ .option('-q, --query <query>', 'Advanced query expression. Supports:\n' +
24
+ ' - Field queries: content:error AND appName:myapp\n' +
25
+ ' - Comparison: time>60 AND region:r\n' +
26
+ ' - SQL syntax: SELECT * FROM log WHERE content LIKE \'%error%\'\n' +
27
+ ' - Logical operators: AND, OR, NOT (must be surrounded by spaces)')
28
+ .option('--app <appName>', 'Filter by appName field (e.g. --app acerp-gateway), merged with -q via AND')
29
+ .option('--content <keyword>', 'Filter by log content (content:<keyword>), merged with -q via AND')
30
+ .option('--request-id <id>', 'Filter by REQUEST_ID field; output in chronological order')
31
+ .option('-l, --limit <limit>', 'Limit number of results', parseIntOption, 100)
32
+ .option('-o, --offset <offset>', 'Offset for pagination', parseIntOption, 0)
20
33
  .option('-r, --reverse', 'Reverse order (newest first)')
21
34
  .option('-f, --format <format>', 'Output format: json, table, pretty', 'pretty')
22
- .option('--no-paginate', 'Disable auto-pagination (single page only)')
35
+ .option('--all', 'Fetch all pages (up to 10000 logs; default is single page)')
36
+ .option('--no-paginate', '(deprecated) Single page is the default; kept for compatibility')
23
37
  .action(async (options) => {
24
- const config = (0, config_1.loadConfig)();
25
- // Validate required credentials
26
- if (!config.ak || !config.sk || !config.projectId) {
27
- console.error('Error: Missing required credentials. Please configure them first:');
28
- console.error('');
29
- console.error(' Quick setup (recommended):');
30
- console.error(' lts-cli init');
31
- console.error('');
32
- console.error(' Or set individually:');
33
- console.error(' lts-cli config --set ak=<your-access-key>');
34
- console.error(' lts-cli config --set sk=<your-secret-key>');
35
- console.error(' lts-cli config --set projectId=<your-project-id>');
36
- process.exit(1);
37
- }
38
- // groupId & streamId: CLI args take priority over config
39
- const groupId = options.groupId || config.groupId;
40
- const streamId = options.streamId || config.streamId;
41
- if (!groupId || !streamId) {
42
- console.error('Error: groupId and streamId are required. Provide them via:');
43
- console.error(' lts-cli config --set groupId=<your-group-id>');
44
- console.error(' lts-cli config --set streamId=<your-stream-id>');
45
- console.error(' or use -g <groupId> -s <streamId> flags');
46
- process.exit(1);
47
- }
48
- // --st / --et are aliases for --start-time / --end-time
49
- const startTime = options.startTime || options.st;
50
- const endTime = options.endTime || options.et;
51
- if (!startTime || !endTime) {
52
- console.error('Error: --start-time and --end-time are required.');
53
- process.exit(1);
54
- }
55
- const format = ['json', 'table', 'pretty'].includes(options.format)
56
- ? options.format
57
- : 'pretty';
38
+ const config = (0, common_1.loadCheckedConfig)();
39
+ const { groupId, streamId } = (0, common_1.resolveGroupStream)(options, config);
40
+ const timeParams = (0, common_1.resolveTimeParams)(options);
41
+ const query = (0, common_1.buildQueryExpression)(options);
42
+ const format = (0, common_1.resolveFormat)(options.format);
43
+ // --request-id 用于链路定位,按时间正序输出
44
+ const reverse = options.requestId ? false : options.reverse;
58
45
  const client = new client_1.LTSClient(config);
59
46
  const params = {
60
47
  groupId,
61
48
  streamId,
62
- startTime,
63
- endTime,
49
+ ...timeParams,
64
50
  keyword: options.keyword,
51
+ query,
65
52
  limit: options.limit,
66
53
  offset: options.offset,
67
- reverse: options.reverse,
54
+ reverse,
68
55
  };
69
56
  try {
70
- if (options.paginate) {
57
+ if (!options.all) {
58
+ // 默认单页查询,结果量由 --limit 控制
71
59
  const result = await client.queryLogs(params);
72
60
  console.log((0, output_1.formatOutput)(result, format));
73
61
  }
74
62
  else {
75
- // Auto-pagination: fetch all logs
63
+ // --all:自动翻页拉取全部日志,上限 10000 条防止失控
64
+ const MAX_LOGS = 10000;
76
65
  const allLogs = [];
77
66
  let offset = options.offset || 0;
78
67
  const limit = options.limit || 100;
79
68
  let hasMore = true;
80
- while (hasMore) {
69
+ while (hasMore && allLogs.length < MAX_LOGS) {
81
70
  const pageParams = {
82
71
  ...params,
83
72
  offset,
@@ -100,26 +89,7 @@ function createQueryCommand() {
100
89
  }
101
90
  }
102
91
  catch (error) {
103
- const msg = error.message || String(error);
104
- // Friendlier error messages for common issues
105
- if (msg.includes('Invalid URL')) {
106
- console.error('Error: Invalid endpoint URL. Make sure it starts with https://');
107
- console.error(' Example: https://lts.cn-south-1.myhuaweicloud.com');
108
- }
109
- else if (msg.includes('Authentication failed') || msg.includes('401') || msg.includes('403')) {
110
- console.error('Error: Authentication failed. Please check your AK/SK credentials:');
111
- console.error(' lts-cli config --list');
112
- }
113
- else if (msg.includes('ENOTFOUND') || msg.includes('ECONNREFUSED')) {
114
- console.error('Error: Cannot reach the LTS service. Please check your endpoint and network connection.');
115
- }
116
- else if (msg.includes('Request failed after retries')) {
117
- console.error('Error: The service is temporarily unavailable. Please try again later.');
118
- }
119
- else {
120
- console.error('Error querying logs:', msg);
121
- }
122
- process.exit(1);
92
+ (0, common_1.handleCommandError)(error);
123
93
  }
124
94
  });
125
95
  return cmd;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createGroupsCommand = createGroupsCommand;
7
+ exports.createStreamsCommand = createStreamsCommand;
8
+ const commander_1 = require("commander");
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const client_1 = require("../../lts/client");
11
+ const common_1 = require("./common");
12
+ function printGroups(groups, format) {
13
+ if (format === 'json') {
14
+ console.log(JSON.stringify(groups, null, 2));
15
+ return;
16
+ }
17
+ if (groups.length === 0) {
18
+ console.log(chalk_1.default.yellow('No log groups found.'));
19
+ return;
20
+ }
21
+ console.log(chalk_1.default.bold.green(`Found ${groups.length} log groups:`));
22
+ console.log('');
23
+ for (const g of groups) {
24
+ console.log(` ${chalk_1.default.cyan(g.name)} ${chalk_1.default.gray(g.id)}`);
25
+ }
26
+ console.log('');
27
+ console.log(chalk_1.default.gray('Set default: lts-cli config --set groupId=<id>'));
28
+ }
29
+ function printStreams(streams, format) {
30
+ if (format === 'json') {
31
+ console.log(JSON.stringify(streams, null, 2));
32
+ return;
33
+ }
34
+ if (streams.length === 0) {
35
+ console.log(chalk_1.default.yellow('No log streams found.'));
36
+ return;
37
+ }
38
+ console.log(chalk_1.default.bold.green(`Found ${streams.length} log streams:`));
39
+ console.log('');
40
+ for (const s of streams) {
41
+ const group = s.groupId ? chalk_1.default.gray(` group: ${s.groupId}`) : '';
42
+ console.log(` ${chalk_1.default.cyan(s.name)} ${chalk_1.default.gray(s.id)}${group}`);
43
+ }
44
+ console.log('');
45
+ console.log(chalk_1.default.gray('Set default: lts-cli config --set streamId=<id>'));
46
+ }
47
+ function createGroupsCommand() {
48
+ const cmd = new commander_1.Command('groups');
49
+ cmd.description('List all LTS log groups (name + ID)')
50
+ .option('-f, --format <format>', 'Output format: json, pretty', 'pretty')
51
+ .action(async (options) => {
52
+ const config = (0, common_1.loadCheckedConfig)();
53
+ const client = new client_1.LTSClient(config);
54
+ try {
55
+ const groups = await client.listGroups();
56
+ printGroups(groups, options.format);
57
+ }
58
+ catch (error) {
59
+ (0, common_1.handleCommandError)(error);
60
+ }
61
+ });
62
+ return cmd;
63
+ }
64
+ function createStreamsCommand() {
65
+ const cmd = new commander_1.Command('streams');
66
+ cmd.description('List LTS log streams, optionally filtered by log group name')
67
+ .option('-g, --group <name>', 'Filter by log group NAME (e.g. acerp)')
68
+ .option('-f, --format <format>', 'Output format: json, pretty', 'pretty')
69
+ .action(async (options) => {
70
+ const config = (0, common_1.loadCheckedConfig)();
71
+ const client = new client_1.LTSClient(config);
72
+ try {
73
+ const streams = await client.listStreams(options.group);
74
+ printStreams(streams, options.format);
75
+ }
76
+ catch (error) {
77
+ (0, common_1.handleCommandError)(error);
78
+ }
79
+ });
80
+ return cmd;
81
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createTraceCommand = createTraceCommand;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const client_1 = require("../../lts/client");
10
+ const output_1 = require("../../output");
11
+ const common_1 = require("./common");
12
+ function createTraceCommand() {
13
+ const cmd = new commander_1.Command('trace');
14
+ cmd.description('Trace a request by REQUEST_ID — fetch the full request chain in chronological order')
15
+ .argument('<requestId>', 'REQUEST_ID value to trace')
16
+ .option('-g, --group-id <groupId>', 'Log group ID (can also be set via config)')
17
+ .option('-s, --stream-id <streamId>', 'Log stream ID (can also be set via config)')
18
+ .option('--field <name>', 'Field to match: REQUEST_ID (default), trace_id, or content (full-text fallback)', 'REQUEST_ID')
19
+ .option('--last <duration>', 'Relative time range: 30m, 2h, 1d, 7d (default: 24h)')
20
+ .option('--start-time <startTime>', 'Start time (ISO 8601)')
21
+ .option('--end-time <endTime>', 'End time (ISO 8601)')
22
+ .option('--st <startTime>', 'Alias for --start-time')
23
+ .option('--et <endTime>', 'Alias for --end-time')
24
+ .option('-l, --limit <limit>', 'Max number of logs to fetch', (v) => parseInt(v, 10), 200)
25
+ .option('-f, --format <format>', 'Output format: json, table, pretty', 'pretty')
26
+ .action(async (requestId, options) => {
27
+ const config = (0, common_1.loadCheckedConfig)();
28
+ const { groupId, streamId } = (0, common_1.resolveGroupStream)(options, config);
29
+ const timeParams = (0, common_1.resolveTimeParams)(options, '24h');
30
+ const format = (0, common_1.resolveFormat)(options.format);
31
+ const field = options.field;
32
+ // 值加双引号:含连字符的 UUID 不加引号会被分词导致匹配不到
33
+ const query = `${field}:"${requestId}"`;
34
+ const client = new client_1.LTSClient(config);
35
+ const params = {
36
+ groupId,
37
+ streamId,
38
+ ...timeParams,
39
+ query,
40
+ reverse: false, // 链路按时间正序
41
+ };
42
+ try {
43
+ // Auto-pagination up to --limit
44
+ const pageSize = Math.min(options.limit || 200, 500);
45
+ const maxTotal = options.limit || 200;
46
+ const allLogs = [];
47
+ let offset = 0;
48
+ let hasMore = true;
49
+ while (hasMore && allLogs.length < maxTotal) {
50
+ const result = await client.queryLogs({ ...params, offset, limit: pageSize });
51
+ allLogs.push(...result.logs);
52
+ if (result.logs.length < pageSize) {
53
+ hasMore = false;
54
+ }
55
+ else {
56
+ offset += pageSize;
57
+ }
58
+ }
59
+ const result = { logs: allLogs.slice(0, maxTotal), total: allLogs.length };
60
+ let output = (0, output_1.formatOutput)(result, format);
61
+ // pretty 格式下高亮 requestId,便于肉眼定位
62
+ if (format === 'pretty' && result.logs.length > 0) {
63
+ output = output.split(requestId).join(chalk_1.default.yellow.bold(requestId));
64
+ }
65
+ console.log(output);
66
+ }
67
+ catch (error) {
68
+ (0, common_1.handleCommandError)(error);
69
+ }
70
+ });
71
+ return cmd;
72
+ }
package/dist/cli/index.js CHANGED
@@ -3,6 +3,9 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const commander_1 = require("commander");
5
5
  const query_1 = require("./commands/query");
6
+ const trace_1 = require("./commands/trace");
7
+ const context_1 = require("./commands/context");
8
+ const streams_1 = require("./commands/streams");
6
9
  const config_1 = require("./commands/config");
7
10
  const init_1 = require("./commands/init");
8
11
  const program = new commander_1.Command();
@@ -13,8 +16,12 @@ program
13
16
  .addHelpText('after', `
14
17
  Examples:
15
18
  $ lts-cli init Setup credentials interactively
19
+ $ lts-cli init --from-url <url> Import region/groupId/streamId from a console URL
16
20
  $ lts-cli config --list Show current configuration
17
- $ lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z
21
+ $ lts-cli query --last 2h --app acerp-gateway --content "error"
22
+ $ lts-cli trace <requestId> Fetch full request chain by REQUEST_ID
23
+ $ lts-cli groups List log groups
24
+ $ lts-cli streams List log streams
18
25
 
19
26
  Environment variables:
20
27
  LTS_AK Access Key
@@ -27,6 +34,10 @@ Environment variables:
27
34
  `);
28
35
  program.addCommand((0, init_1.createInitCommand)());
29
36
  program.addCommand((0, query_1.createQueryCommand)());
37
+ program.addCommand((0, trace_1.createTraceCommand)());
38
+ program.addCommand((0, context_1.createContextCommand)());
39
+ program.addCommand((0, streams_1.createGroupsCommand)());
40
+ program.addCommand((0, streams_1.createStreamsCommand)());
30
41
  program.addCommand((0, config_1.createConfigCommand)());
31
42
  // Shell completion support
32
43
  const completionCmd = new commander_1.Command('completion')
@@ -42,7 +53,7 @@ _lts_cli_completion() {
42
53
  local cur="\${words[cword]}"
43
54
 
44
55
  if (( cword == 1 )); then
45
- COMPREPLY=( $(compgen -W "query config init completion" -- "\$cur") )
56
+ COMPREPLY=( $(compgen -W "query trace context groups streams config init completion" -- "\$cur") )
46
57
  return
47
58
  fi
48
59
 
@@ -83,6 +94,10 @@ _lts_cli() {
83
94
  local -a commands
84
95
  commands=(
85
96
  'query:Query LTS logs'
97
+ 'trace:Trace request chain by REQUEST_ID'
98
+ 'context:View logs before/after a log entry'
99
+ 'groups:List log groups'
100
+ 'streams:List log streams'
86
101
  'config:Manage configuration'
87
102
  'init:Interactive setup wizard'
88
103
  'completion:Generate shell completion script'
@@ -137,7 +152,7 @@ compdef _lts_cli lts-cli`);
137
152
  Register-ArgumentCompleter -CommandName lts-cli -ScriptBlock {
138
153
  param(\$wordToComplete, \$commandAst, \$cursorPosition)
139
154
 
140
- \$commands = @('query', 'config', 'init', 'completion')
155
+ \$commands = @('query', 'trace', 'context', 'groups', 'streams', 'config', 'init', 'completion')
141
156
 
142
157
  \$args = \$commandAst.ToString() -split '\\s+'
143
158
  if (\$args.Count -le 1) {
@@ -1,15 +1,74 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LTSClient = void 0;
4
+ exports.resolveTimeRange = resolveTimeRange;
4
5
  const LtsClient_1 = require("@huaweicloud/huaweicloud-sdk-lts/v2/LtsClient");
5
6
  const ClientBuilder_1 = require("@huaweicloud/huaweicloud-sdk-core/ClientBuilder");
6
7
  const BasicCredentials_1 = require("@huaweicloud/huaweicloud-sdk-core/auth/BasicCredentials");
7
8
  const region_1 = require("@huaweicloud/huaweicloud-sdk-core/region/region");
8
9
  const ListLogsRequest_1 = require("@huaweicloud/huaweicloud-sdk-lts/v2/model/ListLogsRequest");
9
10
  const QueryLtsLogParams_1 = require("@huaweicloud/huaweicloud-sdk-lts/v2/model/QueryLtsLogParams");
11
+ const ListLogContextRequest_1 = require("@huaweicloud/huaweicloud-sdk-lts/v2/model/ListLogContextRequest");
12
+ const ListLogContextRequestBody_1 = require("@huaweicloud/huaweicloud-sdk-lts/v2/model/ListLogContextRequestBody");
13
+ const ListLogGroupsRequest_1 = require("@huaweicloud/huaweicloud-sdk-lts/v2/model/ListLogGroupsRequest");
14
+ const ListLogStreamsRequest_1 = require("@huaweicloud/huaweicloud-sdk-lts/v2/model/ListLogStreamsRequest");
10
15
  function sleep(ms) {
11
16
  return new Promise((resolve) => setTimeout(resolve, ms));
12
17
  }
18
+ const DURATION_REGEX = /^(\d+)([mhd])$/;
19
+ const DURATION_UNIT_MS = {
20
+ m: 60 * 1000,
21
+ h: 60 * 60 * 1000,
22
+ d: 24 * 60 * 60 * 1000,
23
+ };
24
+ /** 解析相对时间范围(如 30m/2h/1d),返回 [startMs, endMs] */
25
+ function resolveTimeRange(params) {
26
+ if (params.last) {
27
+ const match = DURATION_REGEX.exec(params.last.trim());
28
+ if (!match) {
29
+ throw new Error(`Invalid --last duration: "${params.last}". Expected format like 30m, 2h, 1d, 7d.`);
30
+ }
31
+ const amount = parseInt(match[1], 10);
32
+ const endMs = Date.now();
33
+ return { startMs: endMs - amount * DURATION_UNIT_MS[match[2]], endMs };
34
+ }
35
+ if (!params.startTime || !params.endTime) {
36
+ throw new Error('Time range required: provide --last (e.g. --last 2h) or both --start-time and --end-time.');
37
+ }
38
+ const startMs = new Date(params.startTime).getTime();
39
+ const endMs = new Date(params.endTime).getTime();
40
+ if (Number.isNaN(startMs) || Number.isNaN(endMs)) {
41
+ throw new Error(`Invalid time format. Use ISO 8601 (e.g. 2024-01-01T00:00:00Z).`);
42
+ }
43
+ return { startMs, endMs };
44
+ }
45
+ /**
46
+ * 解析查询表达式(对齐控制台管道语法):
47
+ * - "搜索语句 | SELECT ..." → keywords=搜索语句, query=SQL, is_analysis_query=true
48
+ * - 裸 "SELECT ..." → query=SQL, is_analysis_query=true
49
+ * - 其他(field:value 等) → keywords=搜索语句
50
+ */
51
+ function parseQueryExpression(expr) {
52
+ const trimmed = expr.trim();
53
+ if (trimmed.includes('|')) {
54
+ const idx = trimmed.indexOf('|');
55
+ const searchPart = trimmed.slice(0, idx).trim();
56
+ const sqlPart = trimmed.slice(idx + 1).trim();
57
+ return {
58
+ keywords: searchPart && searchPart !== '*' ? searchPart : undefined,
59
+ query: sqlPart || undefined,
60
+ isAnalysisQuery: sqlPart.length > 0,
61
+ };
62
+ }
63
+ if (/^select\s/i.test(trimmed)) {
64
+ return { query: trimmed, isAnalysisQuery: true };
65
+ }
66
+ return { keywords: trimmed, isAnalysisQuery: false };
67
+ }
68
+ /** 服务端默认在命中词上包裹 <HighLightTag>,输出前剥离 */
69
+ function stripHighlightTags(value) {
70
+ return value.replace(/<\/?HighLightTag>/g, '');
71
+ }
13
72
  class LTSClient {
14
73
  client;
15
74
  config;
@@ -30,11 +89,26 @@ class LTSClient {
30
89
  this.client = builder.build();
31
90
  }
32
91
  async queryLogs(params) {
92
+ const { startMs, endMs } = resolveTimeRange(params);
33
93
  const queryParams = new QueryLtsLogParams_1.QueryLtsLogParams()
34
- .withStartTime(new Date(params.startTime).getTime().toString())
35
- .withEndTime(new Date(params.endTime).getTime().toString())
36
- .withLimit(params.limit ?? 100);
37
- if (params.keyword) {
94
+ .withStartTime(startMs.toString())
95
+ .withEndTime(endMs.toString())
96
+ .withLimit(Number.isFinite(params.limit) ? params.limit : 100);
97
+ // 优先使用 query 参数(支持SQL和结构化查询)
98
+ if (params.query) {
99
+ const parsed = parseQueryExpression(params.query);
100
+ if (parsed.keywords) {
101
+ queryParams.withKeywords(parsed.keywords);
102
+ }
103
+ if (parsed.query) {
104
+ queryParams.withQuery(parsed.query);
105
+ }
106
+ if (parsed.isAnalysisQuery) {
107
+ queryParams.withIsAnalysisQuery(true);
108
+ }
109
+ }
110
+ else if (params.keyword) {
111
+ // 向后兼容:简单关键词搜索
38
112
  queryParams.withKeywords(params.keyword);
39
113
  }
40
114
  if (params.reverse !== undefined) {
@@ -43,16 +117,73 @@ class LTSClient {
43
117
  const request = new ListLogsRequest_1.ListLogsRequest(params.groupId, params.streamId, 'application/json')
44
118
  .withBody(queryParams);
45
119
  const response = await this.retryRequest(() => this.client.listLogs(request));
120
+ const raw = response;
121
+ // SQL 分析查询:结果在 analysisLogs 中(行为 JSON 对象)
122
+ if (Array.isArray(raw.analysisLogs)) {
123
+ const logs = raw.analysisLogs.map((row) => ({
124
+ logTime: '',
125
+ content: JSON.stringify(row),
126
+ labels: {},
127
+ }));
128
+ return { logs, total: logs.length };
129
+ }
130
+ const logs = (response.logs || []).map((log) => ({
131
+ logTime: log.line_num ?? log.lineNum ?? '',
132
+ content: stripHighlightTags(log.content ?? ''),
133
+ labels: Object.fromEntries(Object.entries(log.labels ?? {}).map(([k, v]) => [k, stripHighlightTags(v)])),
134
+ }));
135
+ return {
136
+ logs,
137
+ total: response.count ?? logs.length,
138
+ };
139
+ }
140
+ /** 查询某条日志的上下文(前后各 N 行),对应控制台的“查看上下文” */
141
+ async queryContext(params) {
142
+ const body = new ListLogContextRequestBody_1.ListLogContextRequestBody()
143
+ .withLineNum(params.lineNum)
144
+ .withTime(params.time)
145
+ .withBackwardsSize(params.before ?? 20)
146
+ .withForwardsSize(params.after ?? 20);
147
+ const request = new ListLogContextRequest_1.ListLogContextRequest(params.groupId, params.streamId, 'application/json')
148
+ .withBody(body);
149
+ const response = await this.retryRequest(() => this.client.listLogContext(request));
46
150
  const logs = (response.logs || []).map((log) => ({
47
- logTime: log.lineNum ?? '',
151
+ logTime: log.line_num ?? log.lineNum ?? '',
48
152
  content: log.content ?? '',
49
153
  labels: log.labels ?? {},
50
154
  }));
51
155
  return {
52
156
  logs,
53
- total: response.count ?? logs.length,
157
+ // SDK 实际返回纯对象,字段为 snake_case
158
+ total: response.total_count ?? response.totalCount ?? logs.length,
54
159
  };
55
160
  }
161
+ /** 列出全部日志组 */
162
+ async listGroups() {
163
+ const request = new ListLogGroupsRequest_1.ListLogGroupsRequest();
164
+ const response = await this.retryRequest(() => this.client.listLogGroups(request));
165
+ // SDK 实际返回纯对象,字段为 snake_case(log_groups)
166
+ const groups = (response.log_groups ?? []);
167
+ return groups.map((g) => ({
168
+ id: g.log_group_id ?? '',
169
+ name: g.log_group_name ?? '',
170
+ }));
171
+ }
172
+ /** 列出日志流,可按日志组名称过滤(API 使用组名而非组 ID) */
173
+ async listStreams(groupName) {
174
+ const request = new ListLogStreamsRequest_1.ListLogStreamsRequest('application/json');
175
+ if (groupName) {
176
+ request.withLogGroupName(groupName);
177
+ }
178
+ const response = await this.retryRequest(() => this.client.listLogStreams(request));
179
+ // SDK 实际返回纯对象,字段为 snake_case(log_streams)
180
+ const streams = (response.log_streams ?? []);
181
+ return streams.map((s) => ({
182
+ id: s.log_stream_id ?? '',
183
+ name: s.log_stream_name ?? '',
184
+ groupId: s.log_group_id,
185
+ }));
186
+ }
56
187
  async retryRequest(fn, maxRetries = 3) {
57
188
  let lastError;
58
189
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
package/package.json CHANGED
@@ -1,55 +1,55 @@
1
- {
2
- "name": "@cvtoolman/huawei-lts-cli",
3
- "version": "1.0.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
- "clean": "node -e \"require('fs').rmSync('dist', {recursive: true, force: true})\"",
17
- "prepublishOnly": "npm run clean && npm run build"
18
- },
19
- "keywords": [
20
- "huawei-cloud",
21
- "huawei",
22
- "lts",
23
- "log-tank-service",
24
- "logs",
25
- "cli",
26
- "log-query",
27
- "cloud-logging"
28
- ],
29
- "author": "cvtoolman",
30
- "license": "MIT",
31
- "repository": {
32
- "type": "git",
33
- "url": "git+https://github.com/jcxy/huawei-lts-cli.git"
34
- },
35
- "bugs": {
36
- "url": "https://github.com/jcxy/huawei-lts-cli/issues"
37
- },
38
- "homepage": "https://github.com/jcxy/huawei-lts-cli#readme",
39
- "engines": {
40
- "node": ">=16.0.0"
41
- },
42
- "dependencies": {
43
- "@huaweicloud/huaweicloud-sdk-core": "latest",
44
- "@huaweicloud/huaweicloud-sdk-lts": "latest",
45
- "chalk": "^4.1.2",
46
- "cli-table3": "latest",
47
- "commander": "latest",
48
- "uuid": "^9.0.0"
49
- },
50
- "devDependencies": {
51
- "@types/node": "latest",
52
- "tsx": "latest",
53
- "typescript": "latest"
54
- }
55
- }
1
+ {
2
+ "name": "@cvtoolman/huawei-lts-cli",
3
+ "version": "1.0.3",
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
+ "clean": "node -e \"require('fs').rmSync('dist', {recursive: true, force: true})\"",
17
+ "prepublishOnly": "npm run clean && npm run build"
18
+ },
19
+ "keywords": [
20
+ "huawei-cloud",
21
+ "huawei",
22
+ "lts",
23
+ "log-tank-service",
24
+ "logs",
25
+ "cli",
26
+ "log-query",
27
+ "cloud-logging"
28
+ ],
29
+ "author": "cvtoolman",
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/jcxy/huawei-lts-cli.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/jcxy/huawei-lts-cli/issues"
37
+ },
38
+ "homepage": "https://github.com/jcxy/huawei-lts-cli#readme",
39
+ "engines": {
40
+ "node": ">=16.0.0"
41
+ },
42
+ "dependencies": {
43
+ "@huaweicloud/huaweicloud-sdk-core": "latest",
44
+ "@huaweicloud/huaweicloud-sdk-lts": "latest",
45
+ "chalk": "^4.1.2",
46
+ "cli-table3": "latest",
47
+ "commander": "latest",
48
+ "uuid": "^9.0.0"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "latest",
52
+ "tsx": "latest",
53
+ "typescript": "latest"
54
+ }
55
+ }