147 lines
6.2 KiB
TypeScript
147 lines
6.2 KiB
TypeScript
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 : [];
|
|
}
|