ec-nest/src/logging/logger.config.ts
liuyun c680fb9dc7 feat: log request body (masked, debug level) and drop headers from request logs
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-10 22:13:33 +08:00

69 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 } : {};
},
},
});
}