mcp-log-query-server 3.3.0 → 3.4.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/index.js +23 -5
- package/package.json +1 -1
- package/ssh-client.js +35 -69
package/index.js
CHANGED
|
@@ -670,25 +670,43 @@ async function handleToolCall(name, args) {
|
|
|
670
670
|
|
|
671
671
|
console.error(`[MCP] 追踪日志: traceId=${traceId}, namespace=${targetNamespace || 'default'}, 服务数=${servicesToSearch.length}`);
|
|
672
672
|
|
|
673
|
+
const TRACE_TOTAL_TIMEOUT = 50000; // 总耗时上限 50s
|
|
674
|
+
const TRACE_PER_SERVICE = 10000; // 单服务超时 10s
|
|
675
|
+
const traceStart = Date.now();
|
|
673
676
|
const results = [];
|
|
677
|
+
let searched = 0;
|
|
678
|
+
let skipped = 0;
|
|
679
|
+
|
|
674
680
|
for (const serviceName of servicesToSearch) {
|
|
681
|
+
// 总耗时检查
|
|
682
|
+
if (Date.now() - traceStart > TRACE_TOTAL_TIMEOUT) {
|
|
683
|
+
skipped = servicesToSearch.length - searched;
|
|
684
|
+
console.error(`[MCP] trace_log 总耗时超过 ${TRACE_TOTAL_TIMEOUT}ms,跳过剩余 ${skipped} 个服务`);
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
|
|
675
688
|
const service = findService(serviceName, targetNamespace);
|
|
676
|
-
if (!service) continue;
|
|
689
|
+
if (!service) { searched++; continue; }
|
|
677
690
|
|
|
678
691
|
try {
|
|
679
692
|
const command = `grep -i -C ${contextLines} "${traceId}"`;
|
|
680
|
-
const result = await queryLog(service, command);
|
|
693
|
+
const result = await queryLog(service, command, { timeout: TRACE_PER_SERVICE });
|
|
681
694
|
|
|
682
695
|
if (result && result.trim() && !result.includes('未找到')) {
|
|
683
696
|
results.push({ service: serviceName, namespace: service.namespace, logs: result });
|
|
684
697
|
}
|
|
685
698
|
} catch (err) {
|
|
686
|
-
|
|
699
|
+
// 快速跳过失败/超时的服务
|
|
700
|
+
console.error(`[MCP] ${serviceName} 跳过: ${err.message.substring(0, 80)}`);
|
|
687
701
|
}
|
|
702
|
+
searched++;
|
|
688
703
|
}
|
|
689
704
|
|
|
705
|
+
const elapsed = Date.now() - traceStart;
|
|
706
|
+
const timeNote = skipped > 0 ? `\n**注意**: 已搜索 ${searched}/${servicesToSearch.length} 个服务(耗时 ${elapsed}ms,跳过 ${skipped} 个)` : '';
|
|
707
|
+
|
|
690
708
|
if (results.length === 0) {
|
|
691
|
-
return { content: [{ type: 'text', text: `## TraceId 追踪结果\n\n**traceId**: ${traceId}\n**namespace**: ${targetNamespace || '默认'}\n\n❌
|
|
709
|
+
return { content: [{ type: 'text', text: `## TraceId 追踪结果\n\n**traceId**: ${traceId}\n**namespace**: ${targetNamespace || '默认'}\n\n❌ 未在已搜索的 ${searched} 个服务中找到匹配的日志${timeNote}` }] };
|
|
692
710
|
}
|
|
693
711
|
|
|
694
712
|
const output = results.map(r => `### ${r.service} (${r.namespace})\n\`\`\`\n${r.logs}\n\`\`\``).join('\n\n');
|
|
@@ -696,7 +714,7 @@ async function handleToolCall(name, args) {
|
|
|
696
714
|
return {
|
|
697
715
|
content: [{
|
|
698
716
|
type: 'text',
|
|
699
|
-
text: `## TraceId 追踪结果\n\n**traceId**: ${traceId}\n**namespace**: ${targetNamespace || '默认'}\n**匹配服务数**: ${results.length}\n\n${output}`
|
|
717
|
+
text: `## TraceId 追踪结果\n\n**traceId**: ${traceId}\n**namespace**: ${targetNamespace || '默认'}\n**匹配服务数**: ${results.length}${timeNote}\n\n${output}`
|
|
700
718
|
}]
|
|
701
719
|
};
|
|
702
720
|
}
|
package/package.json
CHANGED
package/ssh-client.js
CHANGED
|
@@ -34,32 +34,35 @@ export async function queryLog(service, command, options = {}) {
|
|
|
34
34
|
let kubectlOutput = '';
|
|
35
35
|
let collectingOutput = false;
|
|
36
36
|
|
|
37
|
-
// 设置超时
|
|
37
|
+
// 设置超时 - 使用 destroy() 强制关闭
|
|
38
|
+
let settled = false;
|
|
38
39
|
timeoutId = setTimeout(() => {
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
if (!settled) {
|
|
41
|
+
settled = true;
|
|
42
|
+
try { conn.destroy(); } catch {};
|
|
43
|
+
reject(new Error(`命令执行超时 (${timeout}ms)`));
|
|
44
|
+
}
|
|
41
45
|
}, timeout);
|
|
42
46
|
|
|
43
47
|
conn.on('ready', () => {
|
|
44
|
-
console.error('[SSH] 已连接到堡垒机');
|
|
45
|
-
|
|
46
48
|
conn.shell({ term: 'xterm', rows: 24, cols: 500 }, (err, stream) => {
|
|
47
49
|
if (err) {
|
|
48
50
|
clearTimeout(timeoutId);
|
|
49
|
-
reject(err);
|
|
51
|
+
if (!settled) { settled = true; reject(err); }
|
|
50
52
|
return;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
stream.on('close', () => {
|
|
54
56
|
clearTimeout(timeoutId);
|
|
55
|
-
conn.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
try { conn.destroy(); } catch {}
|
|
58
|
+
if (!settled) {
|
|
59
|
+
settled = true;
|
|
60
|
+
resolve(cleanTerminalOutput(kubectlOutput || buffer));
|
|
61
|
+
}
|
|
60
62
|
});
|
|
61
63
|
|
|
62
64
|
stream.on('data', (data) => {
|
|
65
|
+
if (settled) return; // 已超时,忽略后续数据
|
|
63
66
|
const text = data.toString();
|
|
64
67
|
buffer += text;
|
|
65
68
|
|
|
@@ -67,64 +70,43 @@ export async function queryLog(service, command, options = {}) {
|
|
|
67
70
|
kubectlOutput += text;
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
// JumpServer 状态机
|
|
71
|
-
|
|
72
|
-
// 阶段1: 等待 Opt> 提示符
|
|
73
|
+
// JumpServer 状态机
|
|
73
74
|
if (stage === 'init' && buffer.includes('Opt>')) {
|
|
74
75
|
stage = 'opt';
|
|
75
|
-
console.error('[SSH] 输入目标服务器 IP');
|
|
76
76
|
stream.write(K8S_SERVER.host + '\r');
|
|
77
77
|
}
|
|
78
|
-
// 阶段2: 等待 [Host]> 提示符(搜索结果列表后)
|
|
79
78
|
else if (stage === 'opt' && buffer.includes('[Host]>')) {
|
|
80
79
|
stage = 'host';
|
|
81
|
-
console.error('[SSH] 选择服务器 ID: ' + K8S_SERVER.selectOption);
|
|
82
80
|
stream.write(K8S_SERVER.selectOption + '\r');
|
|
83
81
|
}
|
|
84
|
-
// 阶段3: 等待进入服务器 shell(检测 ~]$ 或 ~]# 提示符)
|
|
85
82
|
else if (stage === 'host' && (buffer.includes('~]$') || buffer.includes('~]#'))) {
|
|
86
83
|
stage = 'server';
|
|
87
|
-
console.error('[SSH] 已进入服务器,执行 kubectl 命令');
|
|
88
|
-
|
|
89
|
-
// 构建并执行 kubectl 命令
|
|
90
84
|
const kubectlCmd = buildKubectlCommand(service, command);
|
|
91
|
-
console.error(`[SSH] 命令: ${kubectlCmd.substring(0, 80)}...`);
|
|
92
|
-
|
|
93
|
-
// 重置 buffer,开始收集 kubectl 输出
|
|
94
85
|
kubectlOutput = '';
|
|
95
86
|
collectingOutput = true;
|
|
96
|
-
|
|
97
87
|
stream.write(kubectlCmd + '\r');
|
|
98
88
|
stage = 'kubectl';
|
|
99
89
|
}
|
|
100
|
-
// 阶段4: kubectl 执行完成
|
|
101
90
|
else if (stage === 'kubectl' && collectingOutput && kubectlOutput.length > 50) {
|
|
102
|
-
// 检测命令执行完成(返回到 shell 提示符)
|
|
103
91
|
if (kubectlOutput.includes('~]$') || kubectlOutput.includes('~]#')) {
|
|
104
92
|
stage = 'done';
|
|
105
93
|
collectingOutput = false;
|
|
106
|
-
console.error('[SSH] 命令执行完成,退出');
|
|
107
|
-
|
|
108
|
-
// 立即退出
|
|
109
94
|
stream.write('exit\r');
|
|
110
95
|
stream.write('exit\r');
|
|
111
|
-
setTimeout(() => stream.end(),
|
|
96
|
+
setTimeout(() => { try { stream.end(); } catch {} }, 300);
|
|
112
97
|
}
|
|
113
98
|
}
|
|
114
99
|
});
|
|
115
100
|
|
|
116
|
-
stream.stderr.on('data', (
|
|
117
|
-
console.error('[SSH stderr]', data.toString());
|
|
118
|
-
});
|
|
101
|
+
stream.stderr.on('data', () => {});
|
|
119
102
|
});
|
|
120
103
|
});
|
|
121
104
|
|
|
122
105
|
conn.on('error', (err) => {
|
|
123
106
|
clearTimeout(timeoutId);
|
|
124
|
-
reject(new Error(`SSH 连接错误: ${err.message}`));
|
|
107
|
+
if (!settled) { settled = true; reject(new Error(`SSH 连接错误: ${err.message}`)); }
|
|
125
108
|
});
|
|
126
109
|
|
|
127
|
-
// 连接堡垒机
|
|
128
110
|
conn.connect({
|
|
129
111
|
host: JUMP_HOST.host,
|
|
130
112
|
port: JUMP_HOST.port,
|
|
@@ -227,32 +209,35 @@ export async function executeKubectl(kubectlCommand, options = {}) {
|
|
|
227
209
|
let kubectlOutput = '';
|
|
228
210
|
let collectingOutput = false;
|
|
229
211
|
|
|
230
|
-
// 设置超时
|
|
212
|
+
// 设置超时 - 使用 destroy() 强制关闭
|
|
213
|
+
let settled = false;
|
|
231
214
|
timeoutId = setTimeout(() => {
|
|
232
|
-
|
|
233
|
-
|
|
215
|
+
if (!settled) {
|
|
216
|
+
settled = true;
|
|
217
|
+
try { conn.destroy(); } catch {}
|
|
218
|
+
reject(new Error(`命令执行超时 (${timeout}ms)`));
|
|
219
|
+
}
|
|
234
220
|
}, timeout);
|
|
235
221
|
|
|
236
222
|
conn.on('ready', () => {
|
|
237
|
-
console.error('[SSH] 已连接到堡垒机');
|
|
238
|
-
|
|
239
223
|
conn.shell({ term: 'xterm', rows: 24, cols: 500 }, (err, stream) => {
|
|
240
224
|
if (err) {
|
|
241
225
|
clearTimeout(timeoutId);
|
|
242
|
-
reject(err);
|
|
226
|
+
if (!settled) { settled = true; reject(err); }
|
|
243
227
|
return;
|
|
244
228
|
}
|
|
245
229
|
|
|
246
230
|
stream.on('close', () => {
|
|
247
231
|
clearTimeout(timeoutId);
|
|
248
|
-
conn.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
232
|
+
try { conn.destroy(); } catch {}
|
|
233
|
+
if (!settled) {
|
|
234
|
+
settled = true;
|
|
235
|
+
resolve(cleanTerminalOutput(kubectlOutput || buffer));
|
|
236
|
+
}
|
|
253
237
|
});
|
|
254
238
|
|
|
255
239
|
stream.on('data', (data) => {
|
|
240
|
+
if (settled) return;
|
|
256
241
|
const text = data.toString();
|
|
257
242
|
buffer += text;
|
|
258
243
|
|
|
@@ -261,60 +246,41 @@ export async function executeKubectl(kubectlCommand, options = {}) {
|
|
|
261
246
|
}
|
|
262
247
|
|
|
263
248
|
// JumpServer 状态机
|
|
264
|
-
|
|
265
|
-
// 阶段1: 等待 Opt> 提示符
|
|
266
249
|
if (stage === 'init' && buffer.includes('Opt>')) {
|
|
267
250
|
stage = 'opt';
|
|
268
|
-
console.error('[SSH] 输入目标服务器 IP');
|
|
269
251
|
stream.write(K8S_SERVER.host + '\r');
|
|
270
252
|
}
|
|
271
|
-
// 阶段2: 等待 [Host]> 提示符
|
|
272
253
|
else if (stage === 'opt' && buffer.includes('[Host]>')) {
|
|
273
254
|
stage = 'host';
|
|
274
|
-
console.error('[SSH] 选择服务器 ID: ' + K8S_SERVER.selectOption);
|
|
275
255
|
stream.write(K8S_SERVER.selectOption + '\r');
|
|
276
256
|
}
|
|
277
|
-
// 阶段3: 等待进入服务器 shell
|
|
278
257
|
else if (stage === 'host' && (buffer.includes('~]$') || buffer.includes('~]#'))) {
|
|
279
258
|
stage = 'server';
|
|
280
|
-
console.error('[SSH] 已进入服务器,执行 kubectl 命令');
|
|
281
|
-
console.error(`[SSH] 命令: ${kubectlCommand.substring(0, 100)}...`);
|
|
282
|
-
|
|
283
|
-
// 重置 buffer,开始收集 kubectl 输出
|
|
284
259
|
kubectlOutput = '';
|
|
285
260
|
collectingOutput = true;
|
|
286
|
-
|
|
287
261
|
stream.write(kubectlCommand + '\r');
|
|
288
262
|
stage = 'kubectl';
|
|
289
263
|
}
|
|
290
|
-
// 阶段4: kubectl 执行完成
|
|
291
264
|
else if (stage === 'kubectl' && collectingOutput && kubectlOutput.length > 50) {
|
|
292
|
-
// 检测命令执行完成(返回到 shell 提示符)
|
|
293
265
|
if (kubectlOutput.includes('~]$') || kubectlOutput.includes('~]#')) {
|
|
294
266
|
stage = 'done';
|
|
295
267
|
collectingOutput = false;
|
|
296
|
-
console.error('[SSH] 命令执行完成,退出');
|
|
297
|
-
|
|
298
|
-
// 立即退出
|
|
299
268
|
stream.write('exit\r');
|
|
300
269
|
stream.write('exit\r');
|
|
301
|
-
setTimeout(() => stream.end(),
|
|
270
|
+
setTimeout(() => { try { stream.end(); } catch {} }, 300);
|
|
302
271
|
}
|
|
303
272
|
}
|
|
304
273
|
});
|
|
305
274
|
|
|
306
|
-
stream.stderr.on('data', (
|
|
307
|
-
console.error('[SSH stderr]', data.toString());
|
|
308
|
-
});
|
|
275
|
+
stream.stderr.on('data', () => {});
|
|
309
276
|
});
|
|
310
277
|
});
|
|
311
278
|
|
|
312
279
|
conn.on('error', (err) => {
|
|
313
280
|
clearTimeout(timeoutId);
|
|
314
|
-
reject(new Error(`SSH 连接错误: ${err.message}`));
|
|
281
|
+
if (!settled) { settled = true; reject(new Error(`SSH 连接错误: ${err.message}`)); }
|
|
315
282
|
});
|
|
316
283
|
|
|
317
|
-
// 连接堡垒机
|
|
318
284
|
conn.connect({
|
|
319
285
|
host: JUMP_HOST.host,
|
|
320
286
|
port: JUMP_HOST.port,
|