From 1c26e806ffa3dcdd077d3f12abeaa9013c5df9a9 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 3 Apr 2026 11:30:04 +0100 Subject: [PATCH] refactor: simplify gateway startup logs --- src/cli/gateway-cli/run-loop.ts | 10 ++++++---- src/cli/gateway-cli/run.ts | 5 +++-- src/gateway/server-startup-log.test.ts | 23 ++++++++++++++++------- src/gateway/server-startup-log.ts | 15 +++++++++------ src/gateway/server.impl.ts | 8 ++++++-- 5 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/cli/gateway-cli/run-loop.ts b/src/cli/gateway-cli/run-loop.ts index d443c181c62..1bd041d6a0b 100644 --- a/src/cli/gateway-cli/run-loop.ts +++ b/src/cli/gateway-cli/run-loop.ts @@ -27,13 +27,14 @@ const gatewayLog = createSubsystemLogger("gateway"); type GatewayRunSignalAction = "stop" | "restart"; export async function runGatewayLoop(params: { - start: () => Promise>>; + start: (params?: { + startupStartedAt?: number; + }) => Promise>>; runtime: RuntimeEnv; lockPort?: number; }) { - gatewayLog.info("acquiring gateway lock..."); + let startupStartedAt = Date.now(); let lock = await acquireGatewayLock({ port: params.lockPort }); - gatewayLog.info("gateway lock acquired"); let server: Awaited> | null = null; let shuttingDown = false; let restartResolver: (() => void) | null = null; @@ -57,6 +58,7 @@ export async function runGatewayLoop(params: { }; const reacquireLockForInProcessRestart = async (): Promise => { try { + startupStartedAt = Date.now(); lock = await acquireGatewayLock({ port: params.lockPort }); return true; } catch (err) { @@ -231,7 +233,7 @@ export async function runGatewayLoop(params: { for (;;) { onIteration(); try { - server = await params.start(); + server = await params.start({ startupStartedAt }); isFirstStart = false; } catch (err) { // On initial startup, let the error propagate so the outer handler diff --git a/src/cli/gateway-cli/run.ts b/src/cli/gateway-cli/run.ts index 6b0960c7492..1b05242cd85 100644 --- a/src/cli/gateway-cli/run.ts +++ b/src/cli/gateway-cli/run.ts @@ -457,16 +457,17 @@ async function runGatewayCommand(opts: GatewayRunOpts) { } : undefined; - gatewayLog.info("starting gateway…"); + gatewayLog.info("starting..."); const startLoop = async () => await runGatewayLoop({ runtime: defaultRuntime, lockPort: port, - start: async () => + start: async ({ startupStartedAt } = {}) => await startGatewayServer(port, { bind, auth: authOverride, tailscale: tailscaleOverride, + startupStartedAt, }), }); diff --git a/src/gateway/server-startup-log.test.ts b/src/gateway/server-startup-log.test.ts index 60b820da2c7..02c7c2790d2 100644 --- a/src/gateway/server-startup-log.test.ts +++ b/src/gateway/server-startup-log.test.ts @@ -1,7 +1,11 @@ -import { describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { logGatewayStartup } from "./server-startup-log.js"; describe("gateway startup log", () => { + afterEach(() => { + vi.useRealTimers(); + }); + it("warns when dangerous config flags are enabled", () => { const info = vi.fn(); const warn = vi.fn(); @@ -15,6 +19,7 @@ describe("gateway startup log", () => { }, }, bindHost: "127.0.0.1", + pluginCount: 0, port: 18789, log: { info, warn }, isNixMode: false, @@ -35,6 +40,7 @@ describe("gateway startup log", () => { logGatewayStartup({ cfg: {}, bindHost: "127.0.0.1", + pluginCount: 0, port: 18789, log: { info, warn }, isNixMode: false, @@ -43,7 +49,10 @@ describe("gateway startup log", () => { expect(warn).not.toHaveBeenCalled(); }); - it("logs all listen endpoints on a single line", () => { + it("logs a compact ready line with plugin count and duration", () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-04-03T10:00:16.000Z")); + const info = vi.fn(); const warn = vi.fn(); @@ -51,16 +60,16 @@ describe("gateway startup log", () => { cfg: {}, bindHost: "127.0.0.1", bindHosts: ["127.0.0.1", "::1"], + pluginCount: 8, port: 18789, + startupStartedAt: Date.parse("2026-04-03T10:00:00.000Z"), log: { info, warn }, isNixMode: false, }); - const listenMessages = info.mock.calls + const readyMessages = info.mock.calls .map((call) => call[0]) - .filter((message) => message.startsWith("listening on ")); - expect(listenMessages).toEqual([ - `listening on ws://127.0.0.1:18789, ws://[::1]:18789 (PID ${process.pid})`, - ]); + .filter((message) => message.startsWith("ready (")); + expect(readyMessages).toEqual(["ready (8 plugins, 16.0s)"]); }); }); diff --git a/src/gateway/server-startup-log.ts b/src/gateway/server-startup-log.ts index 594ac23badb..8808e8b73a9 100644 --- a/src/gateway/server-startup-log.ts +++ b/src/gateway/server-startup-log.ts @@ -10,6 +10,8 @@ export function logGatewayStartup(params: { bindHost: string; bindHosts?: string[]; port: number; + pluginCount: number; + startupStartedAt?: number; tlsEnabled?: boolean; log: { info: (msg: string, meta?: Record) => void; warn: (msg: string) => void }; isNixMode: boolean; @@ -23,12 +25,13 @@ export function logGatewayStartup(params: { params.log.info(`agent model: ${modelRef}`, { consoleMessage: `agent model: ${chalk.whiteBright(modelRef)}`, }); - const scheme = params.tlsEnabled ? "wss" : "ws"; - const formatHost = (host: string) => (host.includes(":") ? `[${host}]` : host); - const hosts = - params.bindHosts && params.bindHosts.length > 0 ? params.bindHosts : [params.bindHost]; - const listenEndpoints = hosts.map((host) => `${scheme}://${formatHost(host)}:${params.port}`); - params.log.info(`listening on ${listenEndpoints.join(", ")} (PID ${process.pid})`); + const startupDurationMs = + typeof params.startupStartedAt === "number" ? Date.now() - params.startupStartedAt : null; + const startupDurationLabel = + startupDurationMs == null ? null : `${(startupDurationMs / 1000).toFixed(1)}s`; + params.log.info( + `ready (${params.pluginCount} ${params.pluginCount === 1 ? "plugin" : "plugins"}${startupDurationLabel ? `, ${startupDurationLabel}` : ""})`, + ); params.log.info(`log file: ${getResolvedLoggerSettings().file}`); if (params.isNixMode) { params.log.info("gateway: running in Nix mode (config managed externally)"); diff --git a/src/gateway/server.impl.ts b/src/gateway/server.impl.ts index 49f1364859b..cd8f01d244e 100644 --- a/src/gateway/server.impl.ts +++ b/src/gateway/server.impl.ts @@ -373,6 +373,10 @@ export type GatewayServerOptions = { runtime: import("../runtime.js").RuntimeEnv, prompter: import("../wizard/prompts.js").WizardPrompter, ) => Promise; + /** + * Optional startup timestamp used for concise readiness logging. + */ + startupStartedAt?: number; }; export async function startGatewayServer( @@ -606,7 +610,6 @@ export async function startGatewayServer( let pluginRegistry = emptyPluginRegistry; let baseGatewayMethods = baseMethods; if (!minimalTestGateway) { - log.info("loading plugins..."); ({ pluginRegistry, gatewayMethods: baseGatewayMethods } = loadGatewayStartupPlugins({ cfg: gatewayPluginConfigAtStart, workspaceDir: defaultWorkspaceDir, @@ -616,7 +619,6 @@ export async function startGatewayServer( pluginIds: startupPluginIds, preferSetupRuntimeForChannelPlugins: deferredConfiguredChannelPluginIds.length > 0, })); - log.info(`plugins loaded (${pluginRegistry.plugins.length} plugins)`); } else { setActivePluginRegistry(emptyPluginRegistry); } @@ -1340,8 +1342,10 @@ export async function startGatewayServer( bindHosts: httpBindHosts, port, tlsEnabled: gatewayTls.enabled, + pluginCount: pluginRegistry.plugins.length, log, isNixMode, + startupStartedAt: opts.startupStartedAt, }); stopGatewayUpdateCheck = minimalTestGateway ? () => {}