fix(logger): preserve multiline browser logs
This commit is contained in:
+92
-9
@@ -4,14 +4,15 @@ import { AppEnvUtil } from "./app-env";
|
||||
|
||||
type LogArgs = unknown[];
|
||||
type PinoLogArgs = Parameters<PinoLogger["debug"]>;
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user