chore: add runtime observability ops
This commit is contained in:
Executable
+98
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { Agent, fetch as undiciFetch } from 'undici';
|
||||
|
||||
function loadEnvFile(filePath) {
|
||||
if (!fs.existsSync(filePath)) return;
|
||||
for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||
const idx = trimmed.indexOf('=');
|
||||
if (idx < 0) continue;
|
||||
const key = trimmed.slice(0, idx).trim();
|
||||
const value = trimmed.slice(idx + 1).trim();
|
||||
if (!process.env[key]) process.env[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
loadEnvFile(path.join(process.cwd(), '.env'));
|
||||
|
||||
const insecureDispatcher = new Agent({ connect: { rejectUnauthorized: false } });
|
||||
const publicBase = (process.env.H5_PUBLIC_BASE_URL || 'https://mm.tkmind.cn').replace(/\/$/, '');
|
||||
const targets = (process.env.TKMIND_API_TARGETS || process.env.TKMIND_API_TARGET || '')
|
||||
.split(',')
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
async function fetchText(url, init = {}) {
|
||||
const res = await undiciFetch(url, {
|
||||
...init,
|
||||
dispatcher: url.startsWith('https://127.0.0.1') ? insecureDispatcher : undefined,
|
||||
});
|
||||
const text = await res.text();
|
||||
return { res, text };
|
||||
}
|
||||
|
||||
async function checkJson(pathname) {
|
||||
const { res, text } = await fetchText(`${publicBase}${pathname}`);
|
||||
let json = null;
|
||||
try {
|
||||
json = JSON.parse(text);
|
||||
} catch {
|
||||
json = null;
|
||||
}
|
||||
return { ok: res.ok, status: res.status, json, text: json ? undefined : text.slice(0, 160) };
|
||||
}
|
||||
|
||||
async function checkSseHeaders(pathname) {
|
||||
const { res } = await fetchText(`${publicBase}${pathname}`, {
|
||||
headers: { Accept: 'text/event-stream' },
|
||||
});
|
||||
return {
|
||||
ok: res.status === 401 || res.ok,
|
||||
status: res.status,
|
||||
xAccelBuffering: res.headers.get('x-accel-buffering'),
|
||||
contentType: res.headers.get('content-type'),
|
||||
cacheControl: res.headers.get('cache-control'),
|
||||
};
|
||||
}
|
||||
|
||||
async function checkTarget(target) {
|
||||
const { res, text } = await fetchText(new URL('/status', target).toString());
|
||||
return { target, ok: res.ok, status: res.status, text: text.slice(0, 80) };
|
||||
}
|
||||
|
||||
const result = {
|
||||
ok: true,
|
||||
publicBase,
|
||||
checkedAt: new Date().toISOString(),
|
||||
status: await checkJson('/api/status').catch((err) => ({ ok: false, error: err.message })),
|
||||
runtime: await checkJson('/api/runtime/status').catch((err) => ({ ok: false, error: err.message })),
|
||||
sse: {
|
||||
sessions: await checkSseHeaders('/api/sessions/check-stream-runtime/events').catch((err) => ({
|
||||
ok: false,
|
||||
error: err.message,
|
||||
})),
|
||||
agentRuns: await checkSseHeaders('/api/agent/runs/check-stream-runtime/events').catch((err) => ({
|
||||
ok: false,
|
||||
error: err.message,
|
||||
})),
|
||||
},
|
||||
targets: await Promise.all(targets.map((target) => checkTarget(target).catch((err) => ({
|
||||
target,
|
||||
ok: false,
|
||||
error: err.message,
|
||||
})))),
|
||||
};
|
||||
|
||||
result.ok = Boolean(
|
||||
result.status.ok &&
|
||||
result.runtime.ok &&
|
||||
result.sse.sessions.ok &&
|
||||
result.sse.agentRuns.ok &&
|
||||
result.targets.every((target) => target.ok),
|
||||
);
|
||||
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
process.exit(result.ok ? 0 : 1);
|
||||
Reference in New Issue
Block a user