import { DynamicModule } from '@nestjs/common'; import { LoggerModule } from 'nestjs-pino'; /** 解析调用栈,返回打印日志的「文件:行号」(相对 src 的源码路径)。 */ function getCaller(): string | undefined { const stack = new Error().stack?.split('\n') ?? []; for (const line of stack) { const m = line.match(/(?:file:\/\/)?(\/[^\s:]+):(\d+):\d+\)?$/); if (!m) continue; const path = m[1]; // 跳过依赖与日志模块自身;只认本项目编译产物(dist)里的调用帧 if (path.includes('node_modules') || path.includes('/logging/')) continue; if (!path.includes('/dist/')) continue; const parts = path.split('/'); const distIdx = parts.indexOf('dist'); const rel = distIdx >= 0 ? parts.slice(distIdx + 1) : parts.slice(-2); const file = rel[rel.length - 1].replace(/\.js$/, '.ts'); const dir = rel.slice(0, -1).join('/'); return dir ? `${dir}/${file}:${m[2]}` : `${file}:${m[2]}`; } return undefined; } /** * 日志配置:pino 结构化日志。 * - stdout 默认开启:开发环境用 pino-pretty 可读格式(LOG_PRETTY 默认 true),生产设 false 输出 JSON * - 文件输出默认关闭,LOG_FILE_ENABLED=true 开启(文件始终为 JSON,便于采集) * - 每条日志附加 src(文件:行号),便于定位打印位置 * - 级别 LOG_LEVEL(默认 info)、路径 LOG_FILE_PATH(默认 logs/app.log) */ export function createLoggerModule(): DynamicModule { const level = process.env.LOG_LEVEL ?? 'info'; const pretty = process.env.LOG_PRETTY !== 'false'; const fileEnabled = process.env.LOG_FILE_ENABLED === 'true'; const filePath = process.env.LOG_FILE_PATH ?? 'logs/app.log'; const stdoutTarget = pretty ? { target: 'pino-pretty', options: { colorize: true, translateTime: 'SYS:yyyy-mm-dd HH:MM:ss.l', singleLine: true } } : { target: 'pino/file', options: { destination: 1 } }; const targets: any[] = [stdoutTarget]; if (fileEnabled) { targets.push({ target: 'pino-roll', options: { file: filePath, size: '10m', limit: { count: 10 } } }); } return LoggerModule.forRoot({ pinoHttp: { level, transport: { targets }, serializers: { req: (req: any) => ({ id: req.id, method: req.method, url: req.url, query: req.query, params: req.params, remoteAddress: req.remoteAddress, remotePort: req.remotePort, }), res: (res: any) => ({ statusCode: res.statusCode }), }, mixin() { const src = getCaller(); return src ? { src } : {}; }, }, }); }