import { logging } from "../config"; import pino from "pino"; const targets: pino.TransportTargetOptions[] = [ { target: "pino/file", level: logging.level, options: { destination: 1 }, }, ]; if (logging.nodeEnv === "production") { targets.push({ target: "pino-roll", level: "info", options: { file: "/var/log/trade/data", frequency: "daily", mkdir: true, size: "50m", limit: { count: 30 }, }, }); } /** 从 Bun 的 Error.stack 中解析调用位置,返回 "file:line" */ function resolveCaller(): string | undefined { const stack = new Error().stack; if (!stack) return undefined; const lines = stack.split("\n"); // Bun stack format: // 0: "Error" // 1: " at resolveCaller (...)" // 2: " at mixin (...)" // 3-N: pino internal frames // find first frame NOT matching pino/node_modules/logger.ts for (let i = 1; i < lines.length; i++) { const line = lines[i]!; if ( line.includes("pino") || line.includes("node_modules") || line.includes("utils/logger.ts") || line.includes("resolveCaller") ) { continue; } const match = line.match(/at\s+(?:.*?\s+\()?(.+?):(\d+):\d+\)?$/); if (match) { const file = match[1]!; const ln = match[2]!; // 截取项目内相对路径 const idx = file.indexOf("/data/"); const short = idx >= 0 ? file.slice(idx + 6) : file; return `${short}:${ln}`; } } return undefined; } const baseLogger = pino({ level: logging.level, transport: logging.pretty ? { targets: [ { target: "pino-pretty", level: logging.level, options: { colorize: true, translateTime: "SYS:HH:MM:ss" }, }, ...targets.filter((t) => t.target !== "pino/file"), ], } : { targets }, base: { module: "trade-data" }, serializers: { err: pino.stdSerializers.err }, redact: { paths: ["api_key", "api_secret", "password", "apiKey", "apiSecret"], censor: "***", }, mixin() { const tid = (globalThis as Record).__traceId; const caller = resolveCaller(); return { ...(tid ? { traceId: tid } : {}), ...(caller ? { caller } : {}) }; }, formatters: { bindings(bindings) { return { ...bindings, pid: undefined, hostname: undefined }; }, }, }); export const logger = baseLogger; /** 设置当前异步上下文的 traceId,自动附加到后续所有日志 */ export function withTrace(traceId: string, fn: () => T): T { (globalThis as Record).__traceId = traceId; try { return fn(); } finally { (globalThis as Record).__traceId = undefined; } } export default logger;