fix: 收敛 observability CLI 默认输出
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import type { RenderedCliResult } from "../output";
|
||||
import { formatTable, shortenEnd, textValue } from "./manifest";
|
||||
import { configLabel } from "./types";
|
||||
|
||||
export function renderObservabilityPlan(result: Record<string, unknown>): RenderedCliResult {
|
||||
const config = record(result.config);
|
||||
const target = record(config.target);
|
||||
const images = record(config.images);
|
||||
const collector = record(config.collector);
|
||||
const traceBackend = record(config.traceBackend);
|
||||
const renderPlan = record(result.renderPlan);
|
||||
const otlp = record(renderPlan.otlp);
|
||||
const connections = records(config.serviceConnections);
|
||||
const objects = records(renderPlan.objects);
|
||||
const policy = records(result.policy);
|
||||
const configRefs = connections.flatMap((connection) => records(connection.configRefs));
|
||||
const presentConfigRefs = configRefs.filter((item) => item.present === true);
|
||||
const missingConfigRefs = configRefs.filter((item) => item.present !== true);
|
||||
const failedPolicy = policy.filter((item) => item.ok === false);
|
||||
const serviceNames = new Set(connections.map((item) => textValue(item.serviceName)).filter((item) => item !== "-"));
|
||||
const nodes = new Set(connections.map((item) => textValue(record(item.resolved).targetNode)).filter((item) => item !== "-"));
|
||||
const connectionRows = connections.map((item) => {
|
||||
const resolved = record(item.resolved);
|
||||
const refs = records(item.configRefs);
|
||||
const present = refs.filter((ref) => ref.present === true).length;
|
||||
return [
|
||||
shortenEnd(textValue(item.serviceName), 28),
|
||||
textValue(resolved.targetNode),
|
||||
textValue(resolved.lane),
|
||||
textValue(resolved.namespace),
|
||||
refs.length === 0 ? "inline" : `${present}/${refs.length}`,
|
||||
String(values(item.requiredSpans).length),
|
||||
];
|
||||
});
|
||||
const objectRows = objects.map((item) => [textValue(item.kind), textValue(item.name)]);
|
||||
const lines = [
|
||||
`platform-infra observability plan (${result.ok === false ? "not-ok" : "ok"})`,
|
||||
"",
|
||||
`target=${textValue(target.id)} route=${textValue(target.route)} namespace=${textValue(target.namespace)} role=${textValue(target.role)}`,
|
||||
`collector=${textValue(collector.serviceName)} replicas=${textValue(collector.replicas)} image=${textValue(images.collector)}`,
|
||||
`tempo=${textValue(traceBackend.serviceName)} replicas=${textValue(traceBackend.replicas)} retention=${textValue(record(traceBackend.storage).retention)} image=${textValue(images.traceBackend)}`,
|
||||
`serviceConnections=${connections.length} services=${serviceNames.size} nodes=${nodes.size} configRefs=${presentConfigRefs.length}/${configRefs.length} missing=${missingConfigRefs.length} objects=${objects.length}`,
|
||||
"",
|
||||
"Service connections:",
|
||||
formatTable(
|
||||
["SERVICE", "NODE", "LANE", "NAMESPACE", "CONFIG_REFS", "SPANS"],
|
||||
connectionRows.length > 0 ? connectionRows : [["-", "-", "-", "-", "-", "-"]],
|
||||
),
|
||||
"",
|
||||
"Render objects:",
|
||||
formatTable(["KIND", "NAME"], objectRows.length > 0 ? objectRows : [["-", "-"]]),
|
||||
];
|
||||
if (missingConfigRefs.length > 0) {
|
||||
lines.push(
|
||||
"",
|
||||
"Missing configRefs:",
|
||||
formatTable(
|
||||
["ID", "FILE", "PATH"],
|
||||
missingConfigRefs.map((item) => [
|
||||
shortenEnd(textValue(item.id), 48),
|
||||
shortenEnd(textValue(item.file), 48),
|
||||
shortenEnd(textValue(item.path), 64),
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (failedPolicy.length > 0) {
|
||||
lines.push(
|
||||
"",
|
||||
"Failed policy:",
|
||||
formatTable(["NAME", "DETAIL"], failedPolicy.map((item) => [textValue(item.name), shortenEnd(textValue(item.detail), 100)])),
|
||||
);
|
||||
}
|
||||
lines.push(
|
||||
"",
|
||||
"Endpoints:",
|
||||
` collector-grpc: ${textValue(otlp.collectorGrpcEndpoint)}`,
|
||||
` collector-http: ${textValue(otlp.collectorHttpEndpoint)}`,
|
||||
` tempo-grpc: ${textValue(otlp.backendGrpcEndpoint)}`,
|
||||
"",
|
||||
"Next:",
|
||||
` bun scripts/cli.ts platform-infra observability plan --target ${textValue(target.id)} --full`,
|
||||
` bun scripts/cli.ts platform-infra observability plan --target ${textValue(target.id)} --raw`,
|
||||
` bun scripts/cli.ts platform-infra observability status --target ${textValue(target.id)}`,
|
||||
"",
|
||||
"Disclosure:",
|
||||
" 默认视图只展示计划摘要;--full/--raw 显式披露完整 render plan。",
|
||||
);
|
||||
return {
|
||||
ok: result.ok !== false,
|
||||
command: "platform-infra observability plan",
|
||||
contentType: "text/plain",
|
||||
renderedText: lines.join("\n"),
|
||||
};
|
||||
}
|
||||
|
||||
export function renderObservabilityPlanFailure(error: unknown, targetId: string | null): RenderedCliResult {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const configRef = extractConfigRef(message);
|
||||
const sourcePath = configRef?.split("#")[0] ?? configLabel;
|
||||
const inspectPattern = configRef?.split("#")[1]?.split(/[.[]/u)[0] ?? "instrumentation";
|
||||
const target = targetId ?? "YAML-default";
|
||||
const targetArg = targetId === null ? "" : ` --target ${targetId}`;
|
||||
const lines = [
|
||||
"platform-infra observability plan (not-ok)",
|
||||
"",
|
||||
`target=${target}`,
|
||||
`config=${configLabel}`,
|
||||
`configRef=${configRef ?? "unresolved"}`,
|
||||
`reason=${shortenEnd(message.replace(/\s+/gu, " ").trim(), 500)}`,
|
||||
"",
|
||||
"Next:",
|
||||
` rg -n ${quoteArg(inspectPattern)} ${quoteArg(sourcePath)}`,
|
||||
` bun scripts/cli.ts platform-infra observability plan${targetArg} --full`,
|
||||
"",
|
||||
"Disclosure:",
|
||||
" 配置或 configRef 失败会直接显示来源与检查入口,不依赖 stdout dump。",
|
||||
];
|
||||
return {
|
||||
ok: false,
|
||||
command: "platform-infra observability plan",
|
||||
contentType: "text/plain",
|
||||
renderedText: lines.join("\n"),
|
||||
};
|
||||
}
|
||||
|
||||
function extractConfigRef(message: string): string | null {
|
||||
const match = message.match(/config\/[A-Za-z0-9_./-]+[.]yaml(?:#[A-Za-z0-9_.\-[\]]+)?/u);
|
||||
return match?.[0] ?? null;
|
||||
}
|
||||
|
||||
function quoteArg(value: string): string {
|
||||
return /^[A-Za-z0-9_./:[\]-]+$/u.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
|
||||
}
|
||||
|
||||
function record(value: unknown): Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
||||
}
|
||||
|
||||
function records(value: unknown): Record<string, unknown>[] {
|
||||
return Array.isArray(value) ? value.map(record) : [];
|
||||
}
|
||||
|
||||
function values(value: unknown): unknown[] {
|
||||
return Array.isArray(value) ? value : [];
|
||||
}
|
||||
Reference in New Issue
Block a user