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.
This commit is contained in:
redshift
2026-05-08 19:20:00 +08:00
parent c6290eb356
commit bcbf5bfc44
3 changed files with 37 additions and 22 deletions

View File

@@ -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

View File

@@ -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<void> {
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<void> {
});
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);

View File

@@ -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<boolean> {
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",