rl-rockcli 0.0.13 → 0.0.14

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.
@@ -1,330 +0,0 @@
1
- // 条件引用:内网专有模块(开源版不存在)
2
- let log_provider, access_control, internal_context;
3
-
4
- try {
5
- log_provider = require('../../../lib/log_provider');
6
- } catch (e) {
7
- // 开源版:log_provider 不存在,使用空实现
8
- log_provider = null;
9
- }
10
-
11
- try {
12
- access_control = require('../../../lib/access_control');
13
- } catch (e) {
14
- // 开源版:无权限控制,使用空实现
15
- access_control = {
16
- checkLogSearchPermission: async () => ({ allowed: true }),
17
- getApiKeyFromSettings: () => null,
18
- };
19
- }
20
-
21
- try {
22
- internal_context = require('../internal/context');
23
- } catch (e) {
24
- // 开源版:无上下文查询,使用空实现
25
- internal_context = {
26
- fetchContext: async () => [],
27
- mergeAndDeduplicateLogs: (logs) => logs,
28
- };
29
- }
30
-
31
- // 通用依赖
32
- const logger = require('../../../utils/logger');
33
- const { parseTime, buildQueryString, getQueriesToRun } = require('./utils');
34
- const { displayLogsWithContext } = require('./display');
35
- const { gracefulExit } = require('../../../utils/execution_logger');
36
-
37
- // 解构赋值
38
- const { createClient: defaultCreateClient } = log_provider || {};
39
- const { checkLogSearchPermission, getApiKeyFromSettings } = access_control;
40
- const { fetchContext, mergeAndDeduplicateLogs } = internal_context;
41
-
42
- // 用于测试的依赖注入
43
- let clientFactory = null;
44
-
45
- /**
46
- * 设置自定义客户端工厂(用于测试)
47
- * @param {Function} factory - 客户端工厂函数
48
- */
49
- function setClientFactory(factory) {
50
- clientFactory = factory;
51
- }
52
-
53
- /**
54
- * 重置客户端工厂为默认值
55
- */
56
- function resetClientFactory() {
57
- clientFactory = null;
58
- }
59
-
60
- /**
61
- * 处理日志搜索命令
62
- * @param {Object} argv - 命令行参数
63
- */
64
- async function handleLogSearch(argv) {
65
- const createClient = clientFactory || defaultCreateClient;
66
- const { keyword, field, query, sandboxId, logFile, startTime, endTime, minutes, limit, offset, raw, cluster, debug, count, logFormat, multilines, columns, truncate, highlight, afterContext, beforeContext, context, groupBy, highlightSandboxId = true } = argv;
67
-
68
- // ===== 权限检查 =====
69
- const apiKey = getApiKeyFromSettings();
70
- const permissionResult = checkLogSearchPermission({
71
- apiKey,
72
- sandboxId,
73
- logFile,
74
- });
75
-
76
- if (!permissionResult.allowed) {
77
- logger.error(`❌ ${permissionResult.error}`);
78
- gracefulExit(1);
79
- }
80
-
81
- const userRole = permissionResult.role;
82
- // ===== 权限检查结束 =====
83
-
84
- const afterLines = context !== undefined ? context : (afterContext || 0);
85
- const beforeLines = context !== undefined ? context : (beforeContext || 0);
86
- const needContext = afterLines > 0 || beforeLines > 0;
87
-
88
- // 解析 groupBy 参数为数组
89
- let groupByFields = null;
90
- if (groupBy && groupBy.length > 0) {
91
- groupByFields = Array.isArray(groupBy) ? groupBy : groupBy.split(',').map(s => s.trim());
92
- }
93
-
94
- try {
95
- let actualStartTime = null;
96
- let actualEndTime = null;
97
-
98
- // 解析时间参数
99
- if (startTime || endTime) {
100
- if (startTime) {
101
- actualStartTime = parseTime(startTime);
102
- }
103
- if (endTime) {
104
- actualEndTime = parseTime(endTime);
105
- } else {
106
- actualEndTime = Date.now();
107
- }
108
- } else if (minutes) {
109
- actualStartTime = Date.now() - minutes * 60 * 1000;
110
- actualEndTime = Date.now();
111
- }
112
-
113
- if (!actualStartTime) {
114
- logger.error('❌ Error: Start time is required (use --start-time or --minutes)');
115
- gracefulExit(1);
116
- }
117
-
118
- // 规范化时间戳(末尾3位为0)
119
- actualStartTime = Math.floor(actualStartTime / 1000) * 1000;
120
- actualEndTime = Math.floor(actualEndTime / 1000) * 1000;
121
-
122
- // 构建查询字符串(传递用户角色用于权限控制)
123
- const queryString = buildQueryString({ keyword, field, query, sandboxId, logFile, role: userRole });
124
-
125
- // 获取要查询的集群和表
126
- const queriesToRun = getQueriesToRun(cluster);
127
-
128
- // 生成 trace ID 用于调试关联
129
- let traceCounter = 0;
130
- const generateTraceId = () => {
131
- traceCounter++;
132
- return String(traceCounter).padStart(2, '0');
133
- };
134
-
135
- // 并行查询所有集群和日志类型
136
- const allLogs = [];
137
-
138
- await Promise.all(queriesToRun.map(async ({ clusterName, logType, tableName }) => {
139
- const mysqlClient = createClient();
140
- const traceId = generateTraceId();
141
-
142
- try {
143
- await mysqlClient.connect();
144
-
145
- const queryOptions = {
146
- table: tableName,
147
- startTime: actualStartTime,
148
- endTime: actualEndTime,
149
- filter: queryString || null,
150
- limit: limit,
151
- offset: offset,
152
- count: count,
153
- orderBy: '@timestamp',
154
- orderDirection: 'ASC'
155
- };
156
-
157
- if (debug) {
158
- logger.debug(`🔍 Debug [${traceId}] Cluster: ${clusterName}, LogType: ${logType}, Table: ${tableName}`);
159
- logger.debug(`🔍 Debug [${traceId}] Filter: ${queryString || '(none)'}`);
160
-
161
- const conditions = [`IN_RANGE(\`@timestamp\`, ${actualStartTime}, ${actualEndTime})`];
162
- if (queryString) {
163
- const escapedQuery = queryString.replace(/'/g, "''");
164
- conditions.push(`query_string('${escapedQuery}')`);
165
- }
166
- const whereClause = conditions.join(' AND ');
167
- let sql;
168
- if (count) {
169
- sql = `SELECT count(*) as total FROM \`${tableName}\` WHERE ${whereClause}`;
170
- } else {
171
- sql = `SELECT *, \`@packId\`, \`@logGroupOrder\` FROM \`${tableName}\` WHERE ${whereClause} ORDER BY \`@timestamp\`, \`@packId\`, \`@logGroupOrder\` LIMIT ${limit} OFFSET ${offset}`;
172
- }
173
- }
174
-
175
- const logs = await mysqlClient.query(queryOptions);
176
-
177
- if (debug) {
178
- logger.debug(`📊 Debug [${traceId}] Returned rows: ${logs ? logs.length : 0}`);
179
- if (logs && logs.length > 0) {
180
- const firstLog = logs[0];
181
- const hasPackId = firstLog.hasOwnProperty('@packId');
182
- const hasLogGroupOrder = firstLog.hasOwnProperty('@logGroupOrder');
183
- logger.debug(`🔍 Debug - First row has @packId: ${hasPackId} (value: ${firstLog['@packId']})`);
184
- logger.debug(`🔍 Debug - First row has @logGroupOrder: ${hasLogGroupOrder} (value: ${firstLog['@logGroupOrder']})`);
185
- }
186
- logger.debug('');
187
- }
188
-
189
- if (logs && logs.length > 0) {
190
- logs.forEach(log => {
191
- log._cluster = clusterName;
192
- log._tableName = tableName;
193
- });
194
- allLogs.push(...logs);
195
- }
196
- } catch (error) {
197
- logger.error(`❌ Error querying cluster ${clusterName}: ${error.message}`);
198
- } finally {
199
- await mysqlClient.close();
200
- }
201
- }));
202
-
203
- // 按时间戳和 packId 排序
204
- allLogs.sort((a, b) => {
205
- const timeA = parseInt(a['@timestamp']) || 0;
206
- const timeB = parseInt(b['@timestamp']) || 0;
207
- if (timeA !== timeB) return timeA - timeB;
208
-
209
- const packIdA = parseInt(a['@packId']) || 0;
210
- const packIdB = parseInt(b['@packId']) || 0;
211
- if (packIdA !== packIdB) return packIdA - packIdB;
212
-
213
- const orderA = parseInt(a['@logGroupOrder']) || 0;
214
- const orderB = parseInt(b['@logGroupOrder']) || 0;
215
- return orderA - orderB;
216
- });
217
-
218
- // 如果需要上下文查询
219
- let finalLogs = allLogs;
220
- if (needContext && allLogs.length > 0 && !count) {
221
- if (debug) {
222
- logger.debug(`🔍 Debug - Context query enabled: before=${beforeLines}, after=${afterLines}`);
223
- logger.debug(`🔍 Debug - Matched logs count: ${allLogs.length}`);
224
- }
225
-
226
- allLogs.forEach(log => {
227
- log._isMatch = true;
228
- });
229
-
230
- const contextPromises = allLogs.map(log => {
231
- const logTableName = log._tableName;
232
- if (!logTableName) {
233
- if (debug) {
234
- logger.warn(`⚠️ Debug - Log missing _tableName, skipping context query`);
235
- }
236
- return Promise.resolve({ before: [], after: [] });
237
- }
238
- return fetchContext({
239
- log,
240
- tableName: logTableName,
241
- startTime: actualStartTime,
242
- endTime: actualEndTime,
243
- beforeLines,
244
- afterLines,
245
- debug
246
- });
247
- });
248
-
249
- const contextResults = await Promise.all(contextPromises);
250
-
251
- const allContextLogs = [];
252
- contextResults.forEach(result => {
253
- if (result.before && result.before.length > 0) {
254
- allContextLogs.push(...result.before);
255
- }
256
- if (result.after && result.after.length > 0) {
257
- allContextLogs.push(...result.after);
258
- }
259
- });
260
-
261
- if (debug) {
262
- logger.debug(`🔍 Debug - Context logs fetched: ${allContextLogs.length}`);
263
- }
264
-
265
- finalLogs = mergeAndDeduplicateLogs(allLogs, allContextLogs);
266
-
267
- if (debug) {
268
- logger.debug(`🔍 Debug - Final logs after merge: ${finalLogs.length}`);
269
- logger.debug('');
270
- }
271
- }
272
-
273
- // 显示结果
274
- if (count) {
275
- const totalCount = allLogs.reduce((sum, log) => sum + (parseInt(log.total) || 0), 0);
276
- console.log(totalCount);
277
- } else {
278
- // 提取所有需要高亮的关键词
279
- let highlightKeywords = [];
280
-
281
- // 添加 keyword 参数
282
- if (keyword) {
283
- highlightKeywords = highlightKeywords.concat(Array.isArray(keyword) ? keyword : [keyword]);
284
- }
285
-
286
- // 添加 field 参数中的值(等值匹配和比较运算符)
287
- if (field && field.length > 0) {
288
- field.forEach(f => {
289
- const greaterMatch = f.match(/^(\w+)>(.*)$/);
290
- const greaterEqualMatch = f.match(/^(\w+)>=(.*)$/);
291
- const lessMatch = f.match(/^(\w+)<(.*)$/);
292
- const lessEqualMatch = f.match(/^(\w+)<=(.*)$/);
293
- const notEqualMatch = f.match(/^(\w+)!=(.*)$/);
294
- const equalMatch = f.match(/^([^=<>!]+)=(.*)$/);
295
-
296
- if (greaterMatch || greaterEqualMatch || lessMatch || lessEqualMatch) {
297
- const match = greaterMatch || greaterEqualMatch || lessMatch || lessEqualMatch;
298
- const [, fieldName] = match;
299
- highlightKeywords.push(fieldName);
300
- } else if (notEqualMatch) {
301
- const [, fieldName, fieldValue] = notEqualMatch;
302
- highlightKeywords.push(fieldValue);
303
- } else if (equalMatch) {
304
- const [, fieldName, fieldValue] = equalMatch;
305
- highlightKeywords.push(fieldValue);
306
- }
307
- });
308
- }
309
-
310
- // 添加 sandboxId
311
- if (sandboxId && highlightSandboxId !== false) {
312
- highlightKeywords.push(sandboxId);
313
- }
314
-
315
- displayLogsWithContext(finalLogs, raw, logFormat, multilines, columns, truncate, highlightKeywords, highlight, needContext, groupByFields);
316
- }
317
- } catch (error) {
318
- logger.error(`❌ Search failed: ${error.message}`);
319
- if (error.stack) {
320
- logger.error('Stack trace:', error.stack);
321
- }
322
- gracefulExit(1);
323
- }
324
- }
325
-
326
- module.exports = {
327
- handleLogSearch,
328
- setClientFactory,
329
- resetClientFactory,
330
- };
@@ -1,216 +0,0 @@
1
- // 条件引用:内网专有模块(开源版不存在)
2
- let log_provider, access_control;
3
-
4
- try {
5
- log_provider = require('../../../lib/log_provider');
6
- } catch (e) {
7
- // 开源版:log_provider 不存在,使用空实现
8
- log_provider = null;
9
- }
10
-
11
- try {
12
- access_control = require('../../../lib/access_control');
13
- } catch (e) {
14
- // 开源版:无权限控制,使用空实现
15
- access_control = {
16
- checkLogSearchPermission: () => ({ allowed: true }),
17
- getApiKeyFromSettings: () => null,
18
- };
19
- }
20
-
21
- // 通用依赖
22
- const logger = require('../../../utils/logger');
23
- const { buildQueryString, getQueriesToRun } = require('./utils');
24
- const { displayLogs } = require('./display');
25
- const { gracefulExit } = require('../../../utils/execution_logger');
26
-
27
- // 解构赋值
28
- const { createClient: defaultCreateClient } = log_provider || {};
29
- const { checkLogSearchPermission, getApiKeyFromSettings } = access_control;
30
-
31
- let clientFactory = null;
32
-
33
- function setClientFactory(factory) {
34
- clientFactory = factory;
35
- }
36
-
37
- function resetClientFactory() {
38
- clientFactory = null;
39
- }
40
-
41
- /**
42
- * 处理日志实时监控命令
43
- * @param {Object} argv - 命令行参数
44
- */
45
- async function handleLogTail(argv) {
46
- const { keyword, field, query, sandboxId, logFile, limit, raw, cluster, debug, interval, n, logFormat, multilines, columns, truncate, highlight, groupByFile } = argv;
47
-
48
- const apiKey = getApiKeyFromSettings();
49
- const permissionResult = checkLogSearchPermission({
50
- apiKey,
51
- sandboxId,
52
- logFile,
53
- });
54
-
55
- if (!permissionResult.allowed) {
56
- logger.error(`❌ ${permissionResult.error}`);
57
- gracefulExit(1);
58
- }
59
-
60
- const userRole = permissionResult.role;
61
-
62
- let lastTimestamp = Date.now();
63
- let targetCluster = cluster;
64
- let isFirstQuery = true;
65
-
66
- const queryString = buildQueryString({ keyword, field, query, sandboxId, logFile, role: userRole });
67
-
68
- if (debug) {
69
- logger.info(`🔄 Starting log tail (interval: ${interval}s)... (Press Ctrl+C to stop)`);
70
- logger.info('');
71
- }
72
-
73
- const tailInterval = setInterval(async () => {
74
- try {
75
- const now = Date.now();
76
- let startTime, endTime;
77
-
78
- if (isFirstQuery) {
79
- startTime = Math.floor((now - 60 * 60 * 1000) / 1000) * 1000;
80
- endTime = Math.floor(now / 1000) * 1000;
81
- } else {
82
- startTime = Math.floor(lastTimestamp / 1000) * 1000 + 1000;
83
- endTime = Math.floor(now / 1000) * 1000;
84
- }
85
-
86
- let clustersToQuery = [];
87
-
88
- if (targetCluster) {
89
- clustersToQuery = [{ name: targetCluster }];
90
- } else {
91
- clustersToQuery = Object.keys(require('./utils').clusterTableMapping.clusters).map(name => ({ name }));
92
- }
93
-
94
- const allLogs = [];
95
-
96
- let traceCounter = 0;
97
- const generateTraceId = () => {
98
- traceCounter++;
99
- return String(traceCounter).padStart(2, '0');
100
- };
101
-
102
- await Promise.all(clustersToQuery.map(async ({ name: clusterName }) => {
103
- const { clusterTableMapping } = require('./utils');
104
- const clusterConfig = clusterTableMapping.clusters[clusterName];
105
- if (!clusterConfig) return;
106
-
107
- let tableName;
108
- if (clusterConfig.log) {
109
- tableName = clusterConfig.log;
110
- } else if (clusterName === 'nt-c') {
111
- // 条件引用:内网专有模块(开源版不存在)
112
- try {
113
- const { client: unifiedClient } = require('../../../utils/unified_log_client');
114
- tableName = unifiedClient.defaultTableName;
115
- } catch (e) {
116
- // 开源版:无统一日志客户端
117
- return;
118
- }
119
- } else {
120
- return;
121
- }
122
-
123
- const createClient = clientFactory || defaultCreateClient;
124
- const mysqlClient = createClient();
125
- const traceId = generateTraceId();
126
-
127
- try {
128
- await mysqlClient.connect();
129
-
130
- const queryOptions = {
131
- table: tableName,
132
- startTime,
133
- endTime,
134
- filter: queryString || null,
135
- limit: isFirstQuery ? n : limit,
136
- offset: 0,
137
- count: false,
138
- orderBy: '@timestamp',
139
- orderDirection: isFirstQuery ? 'DESC' : 'ASC'
140
- };
141
-
142
- if (debug) {
143
- logger.debug(`🔍 Debug [${traceId}] Cluster: ${clusterName}, Table: ${tableName}, Order: ${queryOptions.orderDirection}`);
144
- }
145
-
146
- const logs = await mysqlClient.query(queryOptions);
147
-
148
- if (debug) {
149
- logger.debug(`📊 Debug [${traceId}] Returned rows: ${logs ? logs.length : 0}`);
150
- }
151
-
152
- if (logs && logs.length > 0) {
153
- logs.forEach(log => {
154
- log._cluster = clusterName;
155
- });
156
- allLogs.push(...logs);
157
-
158
- if (!cluster && !targetCluster) {
159
- targetCluster = clusterName;
160
- if (debug) {
161
- logger.info(`📍 Found logs in cluster: ${clusterName}, switching to single-cluster mode`);
162
- logger.info('');
163
- }
164
- }
165
- }
166
- } catch (error) {
167
- if (debug) {
168
- logger.error(`❌ Error querying cluster ${clusterName}: ${error.message}`);
169
- }
170
- } finally {
171
- await mysqlClient.close();
172
- }
173
- }));
174
-
175
- allLogs.sort((a, b) => {
176
- const timeA = parseInt(a['@timestamp']) || 0;
177
- const timeB = parseInt(b['@timestamp']) || 0;
178
- if (timeA !== timeB) return timeA - timeB;
179
-
180
- const packIdA = parseInt(a['@packId']) || 0;
181
- const packIdB = parseInt(b['@packId']) || 0;
182
- return packIdA - packIdB;
183
- });
184
-
185
- if (allLogs.length > 0) {
186
- const groupByFields = null;
187
- const tailRaw = true;
188
- displayLogs(allLogs, tailRaw, logFormat, multilines, columns, truncate, keyword, highlight, groupByFields);
189
-
190
- const latestLog = allLogs[allLogs.length - 1];
191
- lastTimestamp = parseInt(latestLog['@timestamp']) || now;
192
- }
193
-
194
- if (isFirstQuery) {
195
- isFirstQuery = false;
196
- }
197
- } catch (error) {
198
- logger.error(`❌ Tail error: ${error.message}`);
199
- }
200
- }, interval * 1000);
201
-
202
- process.on('SIGINT', () => {
203
- if (debug) {
204
- logger.debug('');
205
- console.error('🛑 Stopping log tail...');
206
- }
207
- clearInterval(tailInterval);
208
- gracefulExit(0);
209
- });
210
- }
211
-
212
- module.exports = {
213
- handleLogTail,
214
- setClientFactory,
215
- resetClientFactory,
216
- };