feat: 引入运行时日志体系和存储配置,配置文件改为必填
- 新增 pino/pino-pretty/pino-roll 依赖,实现结构化日志(console pretty + file JSONL rolling) - 新增 Logger 接口及 PinoLoggerWrapper/ConsoleFallbackLogger/NoopLogger/MemoryLogger 实现 - 新增 src/pino-roll.d.ts 类型声明 - 新增 server.storage.dataDir 配置(默认 ./data,相对路径基于配置文件目录) - 新增 server.logging 配置(level/console/file/rotation,支持变量引用) - 配置文件从可选改为必填,parseRuntimeArgs 无参数时抛错 - bootstrap 创建 logger、确保 dataDir、shutdown flush、失败路径 fallback - startServer 接收 logger 并输出结构化监听日志 - ESLint 新增 no-restricted-syntax 禁止 src/server 直接 console.*(排除 logger.ts) - 更新 config.example.yaml、README.md、DEVELOPMENT.md 同步配置和日志文档 - 完善测试覆盖:logger、config、schema、bootstrap 共 150 个测试通过
This commit is contained in:
@@ -1,20 +1,24 @@
|
||||
import { mkdirSync } from "node:fs";
|
||||
|
||||
import type { RuntimeMode } from "../shared/api";
|
||||
import type { ServerConfig } from "./config";
|
||||
import type { ResolvedConfig, ResolvedLoggingConfig } from "./config/types";
|
||||
import type { Logger } from "./logger";
|
||||
import type { StartServerOptions } from "./server";
|
||||
|
||||
import { loadServerConfig } from "./config";
|
||||
import { createConsoleFallback, createRuntimeLogger } from "./logger";
|
||||
import { startServer } from "./server";
|
||||
|
||||
export interface BootstrapDependencies {
|
||||
loadConfig?: (configPath?: string) => Promise<ServerConfig>;
|
||||
logError?: (...data: unknown[]) => void;
|
||||
createLogger?: (config: ResolvedLoggingConfig, mode: string, version?: string) => Promise<Logger>;
|
||||
exit?: (code: number) => never;
|
||||
loadConfig?: (configPath: string) => Promise<ResolvedConfig>;
|
||||
onSignal?: (signal: "SIGINT" | "SIGTERM", handler: () => void) => void;
|
||||
startServer?: (options: StartServerOptions) => unknown;
|
||||
}
|
||||
|
||||
export interface BootstrapOptions {
|
||||
config?: ServerConfig;
|
||||
configPath?: string;
|
||||
configPath: string;
|
||||
mode: RuntimeMode;
|
||||
staticAssets?: StartServerOptions["staticAssets"];
|
||||
version?: string;
|
||||
@@ -22,26 +26,61 @@ export interface BootstrapOptions {
|
||||
|
||||
export async function bootstrap(options: BootstrapOptions, dependencies: BootstrapDependencies = {}): Promise<void> {
|
||||
const load = dependencies.loadConfig ?? loadServerConfig;
|
||||
const buildLogger = dependencies.createLogger ?? createRuntimeLogger;
|
||||
const serve = dependencies.startServer ?? startServer;
|
||||
const onSignal =
|
||||
dependencies.onSignal ??
|
||||
((signal: "SIGINT" | "SIGTERM", handler: () => void) => {
|
||||
process.on(signal, handler);
|
||||
});
|
||||
const logError = dependencies.logError ?? console.error;
|
||||
const exit = dependencies.exit ?? ((code: number) => process.exit(code));
|
||||
|
||||
const createFallback = (): Logger => createConsoleFallback();
|
||||
|
||||
let logger: Logger | undefined;
|
||||
|
||||
try {
|
||||
const config = options.config ?? (await load(options.configPath));
|
||||
const config = await load(options.configPath);
|
||||
|
||||
try {
|
||||
logger = await buildLogger(config.logging, options.mode, options.version);
|
||||
} catch (logInitError) {
|
||||
createFallback().fatal(
|
||||
`日志初始化失败: ${logInitError instanceof Error ? logInitError.message : String(logInitError)}`,
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
logger!.info(
|
||||
{ configDir: config.configDir, configPath: options.configPath, mode: options.mode, version: options.version },
|
||||
"配置加载成功",
|
||||
);
|
||||
|
||||
mkdirSync(config.dataDir, { recursive: true });
|
||||
logger!.info({ dataDir: config.dataDir }, "数据目录就绪");
|
||||
|
||||
const shutdown = () => {
|
||||
process.exit(0);
|
||||
logger?.info("收到退出信号,开始优雅关闭");
|
||||
logger?.flush();
|
||||
exit(0);
|
||||
};
|
||||
onSignal("SIGINT", shutdown);
|
||||
onSignal("SIGTERM", shutdown);
|
||||
|
||||
serve({ config, mode: options.mode, staticAssets: options.staticAssets, version: options.version });
|
||||
serve({
|
||||
config: { host: config.host, port: config.port },
|
||||
logger: logger!.child({ component: "server" }),
|
||||
mode: options.mode,
|
||||
staticAssets: options.staticAssets,
|
||||
version: options.version,
|
||||
});
|
||||
} catch (error) {
|
||||
logError("启动失败:", error instanceof Error ? error.message : error);
|
||||
process.exit(1);
|
||||
if (logger) {
|
||||
logger.fatal({ error: error instanceof Error ? error.message : String(error) }, "启动失败");
|
||||
logger.flush();
|
||||
} else {
|
||||
createFallback().fatal(`启动失败: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user