rl-rockcli 0.0.7 → 0.0.8
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/index.js +15 -5
- package/package.json +2 -2
- package/commands/log/core/constants.js +0 -237
- package/commands/log/core/display.js +0 -370
- package/commands/log/core/search.js +0 -330
- package/commands/log/core/tail.js +0 -216
- package/commands/log/core/utils.js +0 -424
- package/commands/log.js +0 -298
- package/commands/sandbox/core/log-bridge.js +0 -119
- package/commands/sandbox/core/replay/analyzer.js +0 -311
- package/commands/sandbox/core/replay/batch-orchestrator.js +0 -536
- package/commands/sandbox/core/replay/batch-task.js +0 -369
- package/commands/sandbox/core/replay/concurrent-display.js +0 -70
- package/commands/sandbox/core/replay/concurrent-orchestrator.js +0 -170
- package/commands/sandbox/core/replay/data-source.js +0 -86
- package/commands/sandbox/core/replay/display.js +0 -231
- package/commands/sandbox/core/replay/executor.js +0 -634
- package/commands/sandbox/core/replay/history-fetcher.js +0 -124
- package/commands/sandbox/core/replay/index.js +0 -338
- package/commands/sandbox/core/replay/loghouse-data-source.js +0 -177
- package/commands/sandbox/core/replay/pid-mapping.js +0 -26
- package/commands/sandbox/core/replay/request.js +0 -109
- package/commands/sandbox/core/replay/worker.js +0 -166
- package/commands/sandbox/core/session.js +0 -346
- package/commands/sandbox/log-bridge.js +0 -2
- package/commands/sandbox/ray.js +0 -2
- package/commands/sandbox/replay/analyzer.js +0 -311
- package/commands/sandbox/replay/batch-orchestrator.js +0 -536
- package/commands/sandbox/replay/batch-task.js +0 -369
- package/commands/sandbox/replay/concurrent-display.js +0 -70
- package/commands/sandbox/replay/concurrent-orchestrator.js +0 -170
- package/commands/sandbox/replay/display.js +0 -231
- package/commands/sandbox/replay/executor.js +0 -634
- package/commands/sandbox/replay/history-fetcher.js +0 -118
- package/commands/sandbox/replay/index.js +0 -338
- package/commands/sandbox/replay/pid-mapping.js +0 -26
- package/commands/sandbox/replay/request.js +0 -109
- package/commands/sandbox/replay/worker.js +0 -166
- package/commands/sandbox/replay.js +0 -2
- package/commands/sandbox/session.js +0 -2
- package/commands/sandbox-original.js +0 -1393
- package/commands/sandbox.js +0 -499
- package/help/help.json +0 -1071
- package/help/middleware.js +0 -71
- package/help/renderer.js +0 -800
- package/lib/plugin-context.js +0 -40
- package/sdks/sandbox/core/client.js +0 -845
- package/sdks/sandbox/core/config.js +0 -70
- package/sdks/sandbox/core/types.js +0 -74
- package/sdks/sandbox/httpLogger.js +0 -251
- package/sdks/sandbox/index.js +0 -9
- package/utils/asciiArt.js +0 -138
- package/utils/bun-compat.js +0 -59
- package/utils/ciPipelines.js +0 -138
- package/utils/cli.js +0 -17
- package/utils/command-router.js +0 -79
- package/utils/configManager.js +0 -503
- package/utils/dependency-resolver.js +0 -135
- package/utils/eagleeye_traceid.js +0 -151
- package/utils/envDetector.js +0 -78
- package/utils/execution_logger.js +0 -415
- package/utils/featureManager.js +0 -68
- package/utils/firstTimeTip.js +0 -44
- package/utils/hook-manager.js +0 -125
- package/utils/http-logger.js +0 -264
- package/utils/i18n.js +0 -139
- package/utils/image-progress.js +0 -159
- package/utils/logger.js +0 -154
- package/utils/plugin-loader.js +0 -124
- package/utils/plugin-manager.js +0 -348
- package/utils/ray_cli_wrapper.js +0 -746
- package/utils/sandbox-client.js +0 -419
- package/utils/terminal.js +0 -32
- package/utils/tips.js +0 -106
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { ReplayDataSource } = require('./data-source');
|
|
4
|
-
const { LoghouseDataSource } = require('./loghouse-data-source');
|
|
5
|
-
const logger = require('../../../../utils/logger');
|
|
6
|
-
|
|
7
|
-
// Default data source for internal environment
|
|
8
|
-
const defaultDataSource = new LoghouseDataSource();
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 历史获取器
|
|
12
|
-
* 封装数据源调用逻辑,支持批量并发获取
|
|
13
|
-
*/
|
|
14
|
-
class HistoryFetcher {
|
|
15
|
-
/**
|
|
16
|
-
* @param {ReplayDataSource} [dataSource] - 数据源实例(可选,默认使用 LoghouseDataSource)
|
|
17
|
-
*/
|
|
18
|
-
constructor(dataSource = null) {
|
|
19
|
-
this.dataSource = dataSource || defaultDataSource;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* 获取单个沙箱的历史
|
|
24
|
-
* @param {string} sandboxId - 沙箱 ID
|
|
25
|
-
* @param {Object} options - 选项
|
|
26
|
-
* @param {string} options.cluster - 集群名称
|
|
27
|
-
* @param {Array<string>} options.uris - URI 过滤器
|
|
28
|
-
* @param {boolean} options.debug - 是否开启调试
|
|
29
|
-
* @returns {Promise<Object>} - 历史数据
|
|
30
|
-
*/
|
|
31
|
-
async fetch(sandboxId, options = {}) {
|
|
32
|
-
const result = await this.dataSource.fetch(sandboxId, options);
|
|
33
|
-
return result;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* 批量获取历史(并发控制)
|
|
38
|
-
* @param {SandboxTask[]} tasks - 任务列表
|
|
39
|
-
* @param {number} concurrency - 并发数
|
|
40
|
-
* @param {Object} options - 选项
|
|
41
|
-
* @param {boolean} options.quiet - 静默模式
|
|
42
|
-
* @param {Function} options.onProgress - 进度回调
|
|
43
|
-
* @returns {Promise<void>}
|
|
44
|
-
*/
|
|
45
|
-
async fetchBatch(tasks, concurrency, options = {}) {
|
|
46
|
-
const { quiet, onProgress } = options;
|
|
47
|
-
|
|
48
|
-
// 过滤出需要获取历史的任务(没有预加载 requests 的)
|
|
49
|
-
const tasksToFetch = tasks.filter(task => !task.hasRequests());
|
|
50
|
-
|
|
51
|
-
if (tasksToFetch.length === 0) {
|
|
52
|
-
if (!quiet) {
|
|
53
|
-
logger.info('All sandboxes have pre-loaded requests, skipping history fetch.');
|
|
54
|
-
}
|
|
55
|
-
// 标记所有任务为 ready
|
|
56
|
-
tasks.forEach(task => {
|
|
57
|
-
if (task.hasRequests()) {
|
|
58
|
-
task.status = 'ready';
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (!quiet) {
|
|
65
|
-
logger.info(`Fetching history for ${tasksToFetch.length} sandboxes (concurrency: ${concurrency})...`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// 创建任务队列
|
|
69
|
-
const queue = [...tasksToFetch];
|
|
70
|
-
let completed = 0;
|
|
71
|
-
|
|
72
|
-
// Worker 函数
|
|
73
|
-
const worker = async () => {
|
|
74
|
-
while (queue.length > 0) {
|
|
75
|
-
const task = queue.shift();
|
|
76
|
-
if (!task) break;
|
|
77
|
-
|
|
78
|
-
task.startHistoryFetch();
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
const historyData = await this.fetch(task.sandboxId, {
|
|
82
|
-
cluster: task.cluster,
|
|
83
|
-
uris: task.uris
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
task.completeHistoryFetch(historyData);
|
|
87
|
-
} catch (error) {
|
|
88
|
-
task.failHistoryFetch(error);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
completed++;
|
|
92
|
-
onProgress?.(completed, tasksToFetch.length, task);
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
// 启动并发 workers
|
|
97
|
-
const workers = [];
|
|
98
|
-
const actualConcurrency = Math.min(concurrency, tasksToFetch.length);
|
|
99
|
-
|
|
100
|
-
for (let i = 0; i < actualConcurrency; i++) {
|
|
101
|
-
workers.push(worker());
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// 等待所有 workers 完成
|
|
105
|
-
await Promise.allSettled(workers);
|
|
106
|
-
|
|
107
|
-
// 标记预加载的任务为 ready
|
|
108
|
-
tasks.forEach(task => {
|
|
109
|
-
if (task.hasRequests() && task.status === 'pending') {
|
|
110
|
-
task.status = 'ready';
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// 汇总结果
|
|
115
|
-
const succeeded = tasks.filter(t => t.status === 'ready').length;
|
|
116
|
-
const failed = tasks.filter(t => t.status === 'failed').length;
|
|
117
|
-
|
|
118
|
-
if (!quiet) {
|
|
119
|
-
logger.info(`History fetch completed: ${succeeded} succeeded, ${failed} failed.`);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
module.exports = { HistoryFetcher };
|
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
const fs = require('fs-extra');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const logger = require('../../../../utils/logger');
|
|
4
|
-
const { RequestAnalyzer } = require('./analyzer');
|
|
5
|
-
const { ReplayExecutor } = require('./executor');
|
|
6
|
-
const {
|
|
7
|
-
displayAnalysisSummary,
|
|
8
|
-
displayResultsSummary,
|
|
9
|
-
displayDryRunSummary
|
|
10
|
-
} = require('./display');
|
|
11
|
-
const { ConcurrentReplayOrchestrator } = require('./concurrent-orchestrator');
|
|
12
|
-
const { displayConcurrentResultsSummary } = require('./concurrent-display');
|
|
13
|
-
const { BatchReplayOrchestrator } = require('./batch-orchestrator');
|
|
14
|
-
const { gracefulExit } = require('../../../../utils/execution_logger');
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* 从 stdin 读取数据
|
|
18
|
-
* @returns {Promise<string>}
|
|
19
|
-
*/
|
|
20
|
-
function readStdin() {
|
|
21
|
-
return new Promise((resolve, reject) => {
|
|
22
|
-
let data = '';
|
|
23
|
-
process.stdin.setEncoding('utf8');
|
|
24
|
-
|
|
25
|
-
process.stdin.on('data', (chunk) => {
|
|
26
|
-
data += chunk;
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
process.stdin.on('end', () => {
|
|
30
|
-
resolve(data);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
process.stdin.on('error', (error) => {
|
|
34
|
-
reject(new Error(`Error reading from stdin: ${error.message}`));
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 解析请求数据
|
|
41
|
-
* @param {string} inputData - JSON 字符串
|
|
42
|
-
* @returns {Object[]} 请求列表
|
|
43
|
-
*/
|
|
44
|
-
function parseRequests(inputData) {
|
|
45
|
-
let data;
|
|
46
|
-
try {
|
|
47
|
-
data = JSON.parse(inputData);
|
|
48
|
-
} catch (parseError) {
|
|
49
|
-
throw new Error(`Failed to parse JSON from stdin: ${parseError.message}`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (!data.requests || !Array.isArray(data.requests)) {
|
|
53
|
-
throw new Error('Invalid input format: "requests" array is required');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return data.requests;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* 处理 replay 命令
|
|
61
|
-
* @param {Object} argv - 命令行参数
|
|
62
|
-
* @param {Object} client - SandboxClient 实例
|
|
63
|
-
*/
|
|
64
|
-
async function handleSmartReplay(argv, client) {
|
|
65
|
-
// 检查是否为批量模式
|
|
66
|
-
if (argv.fromFile) {
|
|
67
|
-
return handleBatchReplay(argv, client);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// 检查是否为目录模式
|
|
71
|
-
if (argv.fromDir) {
|
|
72
|
-
return handleDirectoryReplay(argv, client);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// 1. 读取并解析输入
|
|
76
|
-
const inputData = await readStdin();
|
|
77
|
-
const requests = parseRequests(inputData);
|
|
78
|
-
|
|
79
|
-
if (requests.length === 0) {
|
|
80
|
-
logger.info('No requests to replay');
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// 2. 分析请求
|
|
85
|
-
const analyzer = new RequestAnalyzer({
|
|
86
|
-
mode: argv.mode || 'smart',
|
|
87
|
-
filters: argv.filter || [],
|
|
88
|
-
executeStop: argv.stop || false
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const plan = analyzer.analyze(requests);
|
|
92
|
-
|
|
93
|
-
// 3. 显示分析摘要
|
|
94
|
-
displayAnalysisSummary(plan, {
|
|
95
|
-
mode: argv.mode || 'smart',
|
|
96
|
-
quiet: argv.quiet || false
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// 4. 预演模式
|
|
100
|
-
if (argv.dryRun) {
|
|
101
|
-
displayDryRunSummary(plan);
|
|
102
|
-
logger.info('Dry run completed - no requests were executed');
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// 5. 检查是否有请求需要执行
|
|
107
|
-
if (plan.executeCount === 0) {
|
|
108
|
-
logger.info('No requests to execute after filtering');
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// 6. 检查并发模式
|
|
113
|
-
const concurrency = argv.concurrency || 1;
|
|
114
|
-
|
|
115
|
-
if (concurrency > 1) {
|
|
116
|
-
// === 并发回放路径 ===
|
|
117
|
-
const orchestrator = new ConcurrentReplayOrchestrator({
|
|
118
|
-
concurrency: concurrency,
|
|
119
|
-
config: client.config,
|
|
120
|
-
quiet: argv.quiet || false,
|
|
121
|
-
logFile: argv.logFile || 'replay.log',
|
|
122
|
-
failureStrategy: argv.failureStrategy || 'continue'
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
const result = await orchestrator.execute(plan, {
|
|
126
|
-
timing: argv.timing || 'none',
|
|
127
|
-
interval: argv.interval || 5,
|
|
128
|
-
quiet: argv.quiet || false,
|
|
129
|
-
logFile: argv.logFile || 'replay.log'
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// 显示并发结果摘要
|
|
133
|
-
displayConcurrentResultsSummary(result, {
|
|
134
|
-
quiet: argv.quiet || false
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// 设置退出码
|
|
138
|
-
if (!result.allSucceeded || result.aggregated.failed > 0) {
|
|
139
|
-
process.exitCode = 1;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
} else {
|
|
143
|
-
// === 原有单沙箱回放路径 ===
|
|
144
|
-
const executor = new ReplayExecutor(client, {
|
|
145
|
-
timing: argv.timing || 'none',
|
|
146
|
-
interval: argv.interval || 5,
|
|
147
|
-
quiet: argv.quiet || false,
|
|
148
|
-
logFile: argv.logFile || 'replay.log',
|
|
149
|
-
verboseLog: argv.verboseLog || false,
|
|
150
|
-
plan: plan
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
const results = await executor.execute(plan);
|
|
154
|
-
|
|
155
|
-
// 显示结果摘要
|
|
156
|
-
displayResultsSummary(results, [], {
|
|
157
|
-
quiet: argv.quiet || false,
|
|
158
|
-
logFile: argv.logFile || 'replay.log',
|
|
159
|
-
sandboxId: executor.currentSandboxId
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// 如果有失败,设置退出码
|
|
163
|
-
if (results.failed > 0) {
|
|
164
|
-
process.exitCode = 1;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* 处理批量回放命令
|
|
171
|
-
* @param {Object} argv - 命令行参数
|
|
172
|
-
* @param {Object} client - SandboxClient 实例
|
|
173
|
-
*/
|
|
174
|
-
async function handleBatchReplay(argv, client) {
|
|
175
|
-
const orchestrator = new BatchReplayOrchestrator({
|
|
176
|
-
concurrency: argv.concurrency || 1,
|
|
177
|
-
baseConfig: client.config,
|
|
178
|
-
mode: argv.mode || 'smart',
|
|
179
|
-
timing: argv.timing || 'none',
|
|
180
|
-
interval: argv.interval || 5,
|
|
181
|
-
filters: argv.filter || [],
|
|
182
|
-
autoFetchHistory: argv.autoFetchHistory !== false,
|
|
183
|
-
continueOnError: argv.continueOnSandboxError !== false,
|
|
184
|
-
quiet: argv.quiet || false,
|
|
185
|
-
dryRun: argv.dryRun || false,
|
|
186
|
-
executeStop: argv.stop || false,
|
|
187
|
-
reportFile: argv.reportFile || 'batch-replay-report.json',
|
|
188
|
-
logFileBase: argv.logFile || 'replay.log',
|
|
189
|
-
verboseLog: argv.verboseLog || false
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
const result = await orchestrator.execute(argv.fromFile);
|
|
193
|
-
|
|
194
|
-
// 设置退出码
|
|
195
|
-
if (result.summary.failed > 0) {
|
|
196
|
-
process.exitCode = 1;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return result;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* 处理目录模式批量回放
|
|
204
|
-
* @param {Object} argv - 命令行参数
|
|
205
|
-
* @param {Object} client - SandboxClient 实例
|
|
206
|
-
*/
|
|
207
|
-
async function handleDirectoryReplay(argv, client) {
|
|
208
|
-
const sourceDir = argv.fromDir;
|
|
209
|
-
|
|
210
|
-
// 1. 验证目录存在
|
|
211
|
-
if (!fs.existsSync(sourceDir)) {
|
|
212
|
-
logger.error(`Directory not found: ${sourceDir}`);
|
|
213
|
-
gracefulExit(1);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// 2. 读取 manifest.json(如果存在)
|
|
217
|
-
const manifestPath = path.join(sourceDir, 'manifest.json');
|
|
218
|
-
let manifest = null;
|
|
219
|
-
let historyFiles = [];
|
|
220
|
-
|
|
221
|
-
if (fs.existsSync(manifestPath)) {
|
|
222
|
-
try {
|
|
223
|
-
manifest = fs.readJsonSync(manifestPath);
|
|
224
|
-
} catch (error) {
|
|
225
|
-
logger.warn(`Failed to read manifest.json: ${error.message}`);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// 3. 扫描目录中的 JSON 文件
|
|
230
|
-
const files = fs.readdirSync(sourceDir);
|
|
231
|
-
historyFiles = files.filter(f =>
|
|
232
|
-
f.endsWith('.json') &&
|
|
233
|
-
f !== 'manifest.json' &&
|
|
234
|
-
!f.startsWith('.')
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
if (historyFiles.length === 0) {
|
|
238
|
-
logger.error(`No history files found in ${sourceDir}`);
|
|
239
|
-
gracefulExit(1);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// 4. 构建输入数据(模拟 --from-file 格式)
|
|
243
|
-
const sandboxes = [];
|
|
244
|
-
const loadErrors = [];
|
|
245
|
-
|
|
246
|
-
for (const file of historyFiles) {
|
|
247
|
-
const filePath = path.join(sourceDir, file);
|
|
248
|
-
try {
|
|
249
|
-
const data = fs.readJsonSync(filePath);
|
|
250
|
-
|
|
251
|
-
if (!data.sandboxId) {
|
|
252
|
-
loadErrors.push({ file, error: 'Missing sandboxId' });
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (!data.requests || !Array.isArray(data.requests)) {
|
|
257
|
-
loadErrors.push({ file, error: 'Missing or invalid requests array' });
|
|
258
|
-
continue;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
sandboxes.push(data);
|
|
262
|
-
} catch (error) {
|
|
263
|
-
loadErrors.push({ file, error: error.message });
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (!argv.quiet) {
|
|
268
|
-
console.log('\n' + '═'.repeat(70));
|
|
269
|
-
console.log('📂 Directory Replay Mode');
|
|
270
|
-
console.log('═'.repeat(70));
|
|
271
|
-
console.log(` Source Dir: ${sourceDir}`);
|
|
272
|
-
console.log(` Files Found: ${historyFiles.length}`);
|
|
273
|
-
console.log(` Valid: ${sandboxes.length}`);
|
|
274
|
-
if (loadErrors.length > 0) {
|
|
275
|
-
console.log(` Errors: ${loadErrors.length}`);
|
|
276
|
-
loadErrors.slice(0, 5).forEach(e => {
|
|
277
|
-
console.log(` - ${e.file}: ${e.error}`);
|
|
278
|
-
});
|
|
279
|
-
if (loadErrors.length > 5) {
|
|
280
|
-
console.log(` ... and ${loadErrors.length - 5} more`);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
console.log('═'.repeat(70) + '\n');
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (sandboxes.length === 0) {
|
|
287
|
-
logger.error('No valid sandbox histories found');
|
|
288
|
-
gracefulExit(1);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// 5. 构建虚拟输入文件并调用批量回放
|
|
292
|
-
const inputData = {
|
|
293
|
-
metadata: {
|
|
294
|
-
...(manifest?.metadata || {}),
|
|
295
|
-
sourceDir: sourceDir,
|
|
296
|
-
loadedAt: new Date().toISOString()
|
|
297
|
-
},
|
|
298
|
-
defaults: manifest?.defaults || {},
|
|
299
|
-
sandboxes: sandboxes
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
// 创建临时文件
|
|
303
|
-
const tempFile = path.join(sourceDir, '.replay-input-temp.json');
|
|
304
|
-
try {
|
|
305
|
-
await fs.writeJson(tempFile, inputData, { spaces: 2 });
|
|
306
|
-
|
|
307
|
-
// 使用批量回放处理(强制禁用自动获取历史)
|
|
308
|
-
const modifiedArgv = {
|
|
309
|
-
...argv,
|
|
310
|
-
fromFile: tempFile,
|
|
311
|
-
autoFetchHistory: false // 已经有预加载的 requests
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
const result = await handleBatchReplay(modifiedArgv, client);
|
|
315
|
-
|
|
316
|
-
// 清理临时文件
|
|
317
|
-
await fs.remove(tempFile);
|
|
318
|
-
|
|
319
|
-
return result;
|
|
320
|
-
} catch (error) {
|
|
321
|
-
// 确保清理临时文件
|
|
322
|
-
if (fs.existsSync(tempFile)) {
|
|
323
|
-
await fs.remove(tempFile);
|
|
324
|
-
}
|
|
325
|
-
throw error;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
module.exports = {
|
|
330
|
-
handleSmartReplay,
|
|
331
|
-
handleBatchReplay,
|
|
332
|
-
handleDirectoryReplay,
|
|
333
|
-
readStdin,
|
|
334
|
-
parseRequests,
|
|
335
|
-
RequestAnalyzer,
|
|
336
|
-
ReplayExecutor,
|
|
337
|
-
BatchReplayOrchestrator
|
|
338
|
-
};
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { ReplayDataSource } = require('./data-source');
|
|
4
|
-
const { createClient } = require('../../../../lib/log_provider');
|
|
5
|
-
const { clusterTableMapping } = require('../../../log/core/utils');
|
|
6
|
-
const logger = require('../../../../utils/logger');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
|
|
10
|
-
// Load internal log config
|
|
11
|
-
const internalLogConfigPath = path.join(__dirname, '../../../config/internal_log_config.json');
|
|
12
|
-
let internalLogConfig = { internalDomains: [], logFiles: {} };
|
|
13
|
-
if (fs.existsSync(internalLogConfigPath)) {
|
|
14
|
-
try {
|
|
15
|
-
internalLogConfig = JSON.parse(fs.readFileSync(internalLogConfigPath, 'utf8'));
|
|
16
|
-
} catch (error) {
|
|
17
|
-
logger.warn(`Failed to load internal log config: ${error.message}`);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Time ranges to search (in minutes)
|
|
22
|
-
const TIME_RANGES = [30, 60, 120, 240, 480];
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* LoghouseDataSource - Internal implementation of ReplayDataSource
|
|
26
|
-
* Fetches sandbox history data from Loghouse (internal log storage)
|
|
27
|
-
*/
|
|
28
|
-
class LoghouseDataSource extends ReplayDataSource {
|
|
29
|
-
/**
|
|
30
|
-
* Fetch history data for a sandbox from Loghouse
|
|
31
|
-
* @param {string} sandboxId - Sandbox ID
|
|
32
|
-
* @param {Object} options - Fetch options
|
|
33
|
-
* @param {string} [options.cluster] - Cluster name (optional, will search all clusters if not specified)
|
|
34
|
-
* @param {Array<string>} [options.uris] - URI filters
|
|
35
|
-
* @param {boolean} [options.debug] - Enable debug mode
|
|
36
|
-
* @returns {Promise<HistoryData>} History data
|
|
37
|
-
*/
|
|
38
|
-
async fetch(sandboxId, options = {}) {
|
|
39
|
-
const { cluster, uris, debug } = options;
|
|
40
|
-
|
|
41
|
-
// Determine clusters to query
|
|
42
|
-
let clustersToQuery = cluster
|
|
43
|
-
? [cluster]
|
|
44
|
-
: Object.keys(clusterTableMapping.clusters);
|
|
45
|
-
|
|
46
|
-
const now = Date.now();
|
|
47
|
-
const requests = [];
|
|
48
|
-
let foundCluster = null;
|
|
49
|
-
|
|
50
|
-
// Query logs helper function
|
|
51
|
-
const queryLogs = async (queryOptions) => {
|
|
52
|
-
const { tableName, startTime, endTime, filter, limit } = queryOptions;
|
|
53
|
-
const mysqlClient = createClient();
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
await mysqlClient.connect();
|
|
57
|
-
const logs = await mysqlClient.query({
|
|
58
|
-
table: tableName,
|
|
59
|
-
startTime,
|
|
60
|
-
endTime,
|
|
61
|
-
filter: filter || null,
|
|
62
|
-
limit: limit || 1000,
|
|
63
|
-
offset: 0,
|
|
64
|
-
count: false,
|
|
65
|
-
orderBy: '@timestamp',
|
|
66
|
-
orderDirection: 'ASC'
|
|
67
|
-
});
|
|
68
|
-
return logs || [];
|
|
69
|
-
} catch (error) {
|
|
70
|
-
if (debug) {
|
|
71
|
-
logger.error(`Query error: ${error.message}`);
|
|
72
|
-
}
|
|
73
|
-
return [];
|
|
74
|
-
} finally {
|
|
75
|
-
await mysqlClient.close();
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// Search for sandbox execution logs across clusters and time ranges
|
|
80
|
-
for (const minutes of TIME_RANGES) {
|
|
81
|
-
const startTime = now - minutes * 60 * 1000;
|
|
82
|
-
const endTime = now;
|
|
83
|
-
|
|
84
|
-
for (const clusterName of clustersToQuery) {
|
|
85
|
-
const clusterConfig = clusterTableMapping.clusters[clusterName];
|
|
86
|
-
if (!clusterConfig || !clusterConfig.nginx) continue;
|
|
87
|
-
|
|
88
|
-
const tableName = clusterConfig.nginx;
|
|
89
|
-
const nginxLogPath = internalLogConfig.logFiles?.nginxLog || '/home/admin/logs/nginx.log';
|
|
90
|
-
|
|
91
|
-
// Build filter for sandbox execution logs
|
|
92
|
-
let filter = `@filename:${nginxLogPath} and ${sandboxId}`;
|
|
93
|
-
|
|
94
|
-
// Add URI filters if provided
|
|
95
|
-
if (uris && uris.length > 0) {
|
|
96
|
-
const uriFilters = uris.map(uri => `request_uri:*${uri}*`).join(' or ');
|
|
97
|
-
filter = `${filter} and (${uriFilters})`;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const logs = await queryLogs({
|
|
101
|
-
tableName,
|
|
102
|
-
startTime,
|
|
103
|
-
endTime,
|
|
104
|
-
filter,
|
|
105
|
-
limit: 5000
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
if (logs && logs.length > 0) {
|
|
109
|
-
foundCluster = clusterName;
|
|
110
|
-
|
|
111
|
-
// Transform logs to request format
|
|
112
|
-
for (const log of logs) {
|
|
113
|
-
const request = this._transformLogToRequest(log);
|
|
114
|
-
if (request) {
|
|
115
|
-
requests.push(request);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// If we found data, no need to search further back
|
|
122
|
-
if (requests.length > 0) {
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
sandboxId,
|
|
129
|
-
requests,
|
|
130
|
-
metadata: {
|
|
131
|
-
cluster: foundCluster,
|
|
132
|
-
count: requests.length,
|
|
133
|
-
fetchedAt: new Date().toISOString()
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Transform a log entry to a request object
|
|
140
|
-
* @param {Object} log - Log entry from Loghouse
|
|
141
|
-
* @returns {Object|null} Request object or null if invalid
|
|
142
|
-
*/
|
|
143
|
-
_transformLogToRequest(log) {
|
|
144
|
-
if (!log) return null;
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
const request = {
|
|
148
|
-
method: log.request_method || 'GET',
|
|
149
|
-
uri: log.request_uri || '',
|
|
150
|
-
timestamp: log['@timestamp'] || new Date().toISOString()
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
// Parse request body if present
|
|
154
|
-
if (log.request_body) {
|
|
155
|
-
try {
|
|
156
|
-
request.requestBody = JSON.parse(log.request_body);
|
|
157
|
-
} catch {
|
|
158
|
-
request.requestBody = log.request_body;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Include relevant headers
|
|
163
|
-
if (log.request_headers) {
|
|
164
|
-
request.headers = log.request_headers;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return request;
|
|
168
|
-
} catch (error) {
|
|
169
|
-
logger.debug(`Failed to transform log: ${error.message}`);
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
module.exports = {
|
|
176
|
-
LoghouseDataSource,
|
|
177
|
-
};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 从 nohup 输出中提取 PID
|
|
3
|
-
* SDK 使用 PIDSTART{pid}PIDEND 格式
|
|
4
|
-
* @param {string} output - 命令输出
|
|
5
|
-
* @returns {string|null} 提取的 PID,如果未找到则返回 null
|
|
6
|
-
*/
|
|
7
|
-
function extractPidFromOutput(output) {
|
|
8
|
-
if (!output) return null;
|
|
9
|
-
|
|
10
|
-
const match = output.match(/PIDSTART(\d+)PIDEND/);
|
|
11
|
-
if (match) {
|
|
12
|
-
return match[1];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// 备用方案:尝试匹配 [1] 12345 格式
|
|
16
|
-
const bgMatch = output.match(/\[\d+\]\s+(\d+)/);
|
|
17
|
-
if (bgMatch) {
|
|
18
|
-
return bgMatch[1];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
module.exports = {
|
|
25
|
-
extractPidFromOutput
|
|
26
|
-
};
|