From bcbf5bfc4459fa09f75d19c6526c3f3512a45c10 Mon Sep 17 00:00:00 2001 From: redshift <213178690+1ftredsh@users.noreply.github.com> Date: Fri, 8 May 2026 19:20:00 +0800 Subject: [PATCH] feat(daemon): wire SdkLogger through daemon with per-request reqId prefixing - daemon/index.ts: add makeSdkLogger() factory, wire daemonSdkLogger into createBunSqliteDriver, createProviderRegistryFromStore, ModelManager, ProviderManager - daemon/http/index.ts: add makeSdkLogger() factory, generate per-request reqId via randomBytes(4), pass reqLogger = sdkLogger.child("req:${reqId}") to routeRequests so every log line from a request is prefixed [req:XXXXXXXX] - start-daemon.ts: remove >> logfile 2>&1 redirect and getTodayLogFile(); all file logging now goes through the daemon's writeLog() with daily rotation Result: Every log line from a request is prefixed [req:XXXXXXXX], all entries have [ISO] [INFO/ERROR/DEBUG] timestamps, no duplicates, and the log file rotates correctly at midnight. --- src/daemon/http/index.ts | 20 ++++++++++++++++++-- src/daemon/index.ts | 22 ++++++++++++++++++---- src/start-daemon.ts | 17 +---------------- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/daemon/http/index.ts b/src/daemon/http/index.ts index b42acc6..d053356 100644 --- a/src/daemon/http/index.ts +++ b/src/daemon/http/index.ts @@ -6,7 +6,7 @@ import { InsufficientBalanceError, ProviderManager, } from "@routstr/sdk"; -import type { UsageTrackingDriver } from "@routstr/sdk"; +import type { UsageTrackingDriver, SdkLogger } from "@routstr/sdk"; import { logger } from "../../utils/logger"; import { CocodHttpError, @@ -264,6 +264,19 @@ async function buildWalletDetails(deps: DaemonDeps): Promise<{ }; } +function makeSdkLogger(prefix?: string): SdkLogger { + const tag = prefix ? `[${prefix}]` : undefined; + const fmt = (...args: unknown[]) => (tag ? [tag, ...args] : args); + return { + log: (...args: unknown[]) => logger.log(...fmt(...args)), + warn: (...args: unknown[]) => logger.log(...fmt(...args)), + error: (...args: unknown[]) => logger.error(...fmt(...args)), + debug: (...args: unknown[]) => logger.debug(...fmt(...args)), + child: (p: string) => makeSdkLogger(prefix ? `${prefix}:${p}` : p), + }; +} +const sdkLogger: SdkLogger = makeSdkLogger(); + export function createDaemonRequestHandler(deps: { provider: string | null; server: { close(cb?: () => void): void }; @@ -1030,7 +1043,9 @@ export function createDaemonRequestHandler(deps: { try { await deps.ensureProvidersBootstrapped(); - logger.log("Routing request with path: ", url.pathname); + const reqId = randomBytes(4).toString("hex"); + const reqLogger = sdkLogger.child(`req:${reqId}`); + logger.log(`[req:${reqId}] Routing request with path: `, url.pathname); const response = await routeRequests({ modelId, @@ -1048,6 +1063,7 @@ export function createDaemonRequestHandler(deps: { usageTrackingDriver: deps.usageTrackingDriver, sdkStore: deps.store, providerManager: deps.providerManager, + logger: reqLogger, }); // Bridge the Web `Response` to the Node `ServerResponse` with no diff --git a/src/daemon/index.ts b/src/daemon/index.ts index 0810551..eb134e7 100644 --- a/src/daemon/index.ts +++ b/src/daemon/index.ts @@ -8,8 +8,22 @@ import { createStorageAdapterFromStore, createSdkStore, } from "@routstr/sdk"; +import type { SdkLogger } from "@routstr/sdk"; import { DB_PATH, SOCKET_PATH, PID_FILE } from "../utils/config"; import { logger } from "../utils/logger"; + +function makeSdkLogger(prefix?: string): SdkLogger { + const tag = prefix ? `[${prefix}]` : undefined; + const fmt = (...args: unknown[]) => (tag ? [tag, ...args] : args); + return { + log: (...args: unknown[]) => logger.log(...fmt(...args)), + warn: (...args: unknown[]) => logger.log(...fmt(...args)), + error: (...args: unknown[]) => logger.error(...fmt(...args)), + debug: (...args: unknown[]) => logger.debug(...fmt(...args)), + child: (p: string) => makeSdkLogger(prefix ? `${prefix}:${p}` : p), + }; +} +const daemonSdkLogger: SdkLogger = makeSdkLogger(); import { parseArgs } from "./args"; import { ensureDirs, loadDaemonConfig, saveDaemonConfig } from "./config-store"; import { @@ -35,7 +49,7 @@ async function main(): Promise { const updatedConfig = { ...config, port, provider }; saveDaemonConfig(updatedConfig); - const sqliteDriver = await createBunSqliteDriver(DB_PATH); + const sqliteDriver = await createBunSqliteDriver(DB_PATH, { logger: daemonSdkLogger }); const { store, hydrate } = createSdkStore({ driver: sqliteDriver }); await hydrate; const { Database } = await import("bun:sqlite"); @@ -46,11 +60,11 @@ async function main(): Promise { }); const discoveryAdapter = createDiscoveryAdapterFromStore(store); - const providerRegistry = createProviderRegistryFromStore(store); + const providerRegistry = createProviderRegistryFromStore(store, daemonSdkLogger); const storageAdapter = createStorageAdapterFromStore(store); - const modelManager = new ModelManager(discoveryAdapter); + const modelManager = new ModelManager(discoveryAdapter, { logger: daemonSdkLogger }); // Create shared ProviderManager for consistent failure tracking across all requests - const providerManager = new ProviderManager(providerRegistry, store); + const providerManager = new ProviderManager(providerRegistry, store, daemonSdkLogger); const { ensureProvidersBootstrapped, getRoutstr21Models, getModelProviders } = createModelService(modelManager, store); diff --git a/src/start-daemon.ts b/src/start-daemon.ts index 4c51b00..ac50ade 100644 --- a/src/start-daemon.ts +++ b/src/start-daemon.ts @@ -1,18 +1,9 @@ import { logger } from "./utils/logger"; -import { existsSync } from "fs"; import { CONFIG_DIR, LOGS_DIR } from "./utils/config"; import { withCrossProcessLock } from "./utils/process-lock"; const DAEMON_STARTUP_LOCK_PATH = `${CONFIG_DIR}/routstrd-startup.lock`; -function getTodayLogFile(): string { - const now = new Date(); - const year = now.getFullYear(); - const month = String(now.getMonth() + 1).padStart(2, "0"); - const day = String(now.getDate()).padStart(2, "0"); - return `${LOGS_DIR}/${year}-${month}-${day}.log`; -} - async function isDaemonHealthy(port: string): Promise { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 2000); @@ -48,14 +39,8 @@ async function startDaemonUnlocked( args.push("--provider", options.provider); } - // Ensure logs directory exists (logger handles date-based files) - if (!existsSync(LOGS_DIR)) { - await Bun.$`mkdir -p ${LOGS_DIR}`; - } - const daemonScript = new URL("./daemon/index.js", import.meta.url).pathname; - const todayLogFile = getTodayLogFile(); - const shellCmd = `bun run "${daemonScript}" ${args.map((a) => `'${a}'`).join(" ")} >> "${todayLogFile}" 2>&1`; + const shellCmd = `bun run "${daemonScript}" ${args.map((a) => `'${a}'`).join(" ")}`; const proc = Bun.spawn(["sh", "-c", shellCmd], { stdout: "inherit",