Add Prometheus metrics, JSON logs and provider health checks

- GET /metrics (bearer server.metrics_token): build info, users, sessions,
  pending requests, per-model request/token/cost counters, provider_up
- CODECRAFT_LOG_FORMAT=json for one JSON object per log line
- Providers are probed every 5 minutes via their model list; status shown
  in the provider settings with a manual re-check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-09 00:57:02 +01:00
1 parent 10d4b64289
commit b5de531d2e
12 files changed
+244 -6

No files matched your search

+28 -2
View File
@@ -371,7 +371,9 @@ function cleanModel(m: ModelRow) {
return out;
}
function ProviderEditor({ name, p, reload }: { name: string; p: Json; reload: () => void }) {
type ProviderStatus = { up: boolean; error?: string; latency_ms: number; checked_at: number };
function ProviderEditor({ name, p, reload, status }: { name: string; p: Json; reload: () => void; status?: ProviderStatus }) {
const t = useT();
const [form, setForm] = useState({ ...p, api_key: p.api_key === MASK ? "" : p.api_key, admin_key: "" });
const [priceNote, setPriceNote] = useState<string>();
@@ -436,6 +438,15 @@ function ProviderEditor({ name, p, reload }: { name: string; p: Json; reload: ()
{name}
<span className="chip">{form.kind}</span>
<span className="chip">{t("settings.modelCount", { n: models.length || discovered.length })}</span>
{status && (
<span
className={"chip " + (status.up ? "ok" : "warn")}
title={(status.error ?? "") + " · " + new Date(status.checked_at).toLocaleTimeString()}
>
<Icon name={status.up ? "check" : "warning"} size={12} />
{status.up ? t("settings.providerUp", { ms: status.latency_ms }) : t("settings.providerDown")}
</span>
)}
<span className="spacer" />
<button className="small" onClick={() => setOpen(!open)}>
{open ? t("common.collapse") : t("common.edit")}
@@ -584,6 +595,18 @@ function Providers({ cfg, reload }: { cfg: ConfigResponse; reload: () => void })
const [preset, setPreset] = useState("anthropic");
const { run, view } = useSaver(reload);
const providers = cfg.config.providers ?? {};
const [status, setStatus] = useState<Record<string, ProviderStatus>>({});
const [checking, setChecking] = useState(false);
useEffect(() => {
get<Record<string, ProviderStatus>>("/providers/status").then(setStatus).catch(() => {});
}, []);
const recheck = () => {
setChecking(true);
post<Record<string, ProviderStatus>>("/providers/status")
.then(setStatus)
.catch(() => {})
.finally(() => setChecking(false));
};
const add = () => {
const n = name.trim() || preset;
@@ -599,9 +622,12 @@ function Providers({ cfg, reload }: { cfg: ConfigResponse; reload: () => void })
<>
<div className="row" style={{ marginBottom: 12 }}>
<button onClick={() => post("/providers/reload").then(reload)}>{t("settings.reloadProviders")}</button>
<button onClick={recheck} disabled={checking}>
{checking ? t("settings.providerChecking") : t("settings.providerCheck")}
</button>
</div>
{Object.entries(providers).map(([n, p]) => (
<ProviderEditor key={n + JSON.stringify(p)} name={n} p={p} reload={reload} />
<ProviderEditor key={n + JSON.stringify(p)} name={n} p={p} reload={reload} status={status[n]} />
))}
<div className="panel">
<h3>{t("settings.addProvider")}</h3>
+4
View File
@@ -569,6 +569,10 @@ export const de: Messages = {
"settings.priceCacheWrite": "Cache schreiben",
"settings.addProvider": "Provider hinzufügen",
"settings.providerName": "Name (optional)",
"settings.providerUp": "erreichbar · {ms} ms",
"settings.providerDown": "nicht erreichbar",
"settings.providerCheck": "Provider prüfen",
"settings.providerChecking": "Prüfe…",
"settings.reloadProviders": "Provider & Modelle neu laden",
"settings.deleteProviderConfirm": "Provider „{name}“ löschen?",
"settings.invalidName": "Namen dürfen nur Buchstaben, Ziffern, - und _ enthalten.",
+4
View File
@@ -568,6 +568,10 @@ export const en = {
"settings.priceCacheWrite": "Cache write",
"settings.addProvider": "Add provider",
"settings.providerName": "Name (optional)",
"settings.providerUp": "reachable · {ms} ms",
"settings.providerDown": "unreachable",
"settings.providerCheck": "Check providers",
"settings.providerChecking": "Checking…",
"settings.reloadProviders": "Reload providers & models",
"settings.deleteProviderConfirm": "Delete provider \"{name}\"?",
"settings.invalidName": "Names may only contain letters, digits, - and _.",