diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 4589f736..5ef7df70 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -4,14 +4,15 @@ import { AppEnvUtil } from "./app-env"; type LogArgs = unknown[]; type PinoLogArgs = Parameters; +type LogLevel = "debug" | "info" | "warn" | "error" | "fatal"; /** * 全局 pino root logger(单例,进程级复用)。 * * 配置驱动(基于 `app-env.ts` 的 `AppEnvUtil`): * - production: silent(不输出日志) - * - test: warn 级别(减少 vitest 噪音) - * - development: debug 级别 + ISO 时间戳(人眼友好) + * - test/dev: debug 级别 + ISO 时间戳 + * - browser: 保留原始 message 参数,避免多行日志被转义成 `\n` */ const rootLogger: PinoLogger = pino(createLoggerOptions()); @@ -20,10 +21,18 @@ function createLoggerOptions(): LoggerOptions { return { level: "silent" }; } if (AppEnvUtil.isTest()) { - return { level: "debug", timestamp: pino.stdTimeFunctions.isoTime }; + return { + level: "debug", + timestamp: pino.stdTimeFunctions.isoTime, + browser: { asObjectBindingsOnly: true }, + }; } // development - return { level: "debug", timestamp: pino.stdTimeFunctions.isoTime }; + return { + level: "debug", + timestamp: pino.stdTimeFunctions.isoTime, + browser: { asObjectBindingsOnly: true }, + }; } /** @@ -43,25 +52,27 @@ function createLoggerOptions(): LoggerOptions { */ export class Logger { private readonly logger: PinoLogger; + private readonly component: string; constructor(component: string) { + this.component = component; this.logger = rootLogger.child({ component }); } debug(...args: LogArgs): void { - this.logger.debug(...Logger.normalizeLogArgs(args)); + this.write("debug", args); } info(...args: LogArgs): void { - this.logger.info(...Logger.normalizeLogArgs(args)); + this.write("info", args); } warn(...args: LogArgs): void { - this.logger.warn(...Logger.normalizeLogArgs(args)); + this.write("warn", args); } error(...args: LogArgs): void { - this.logger.error(...Logger.normalizeLogArgs(args)); + this.write("error", args); } fatal(...args: LogArgs): void { - this.logger.fatal(...Logger.normalizeLogArgs(args)); + this.write("fatal", args); } /** @@ -121,6 +132,78 @@ export class Logger { return args as PinoLogArgs; } + private write(level: LogLevel, args: LogArgs): void { + if (Logger.shouldUseBrowserConsole()) { + Logger.writeBrowserConsole(level, this.component, args); + return; + } + + switch (level) { + case "debug": + this.logger.debug(...Logger.normalizeLogArgs(args)); + return; + case "info": + this.logger.info(...Logger.normalizeLogArgs(args)); + return; + case "warn": + this.logger.warn(...Logger.normalizeLogArgs(args)); + return; + case "error": + this.logger.error(...Logger.normalizeLogArgs(args)); + return; + case "fatal": + this.logger.fatal(...Logger.normalizeLogArgs(args)); + return; + } + } + + private static shouldUseBrowserConsole(): boolean { + return typeof window !== "undefined" && AppEnvUtil.canOutputLogs(); + } + + private static writeBrowserConsole( + level: LogLevel, + component: string, + args: LogArgs, + ): void { + const { message, data } = Logger.toBrowserConsolePayload(args); + const prefix = `${new Date().toISOString()} ${level.toUpperCase()} [${component}]`; + const method = level === "fatal" ? "error" : level; + + if (data.length === 0) { + console[method](`${prefix} ${message}`); + return; + } + + console[method](`${prefix} ${message}`, ...data); + } + + private static toBrowserConsolePayload(args: LogArgs): { + message: string; + data: unknown[]; + } { + const [first, second, ...rest] = args; + + if (typeof first === "string") { + return { + message: first, + data: second === undefined ? rest : [second, ...rest], + }; + } + + if (typeof second === "string") { + return { + message: second, + data: [first, ...rest], + }; + } + + return { + message: "", + data: args, + }; + } + /** 透传 raw pino logger(高级用法:自定义 binding / child / serializers) */ get raw(): PinoLogger { return this.logger;