63 lines
2.1 KiB
TypeScript
63 lines
2.1 KiB
TypeScript
import type { LoggerService } from '@nestjs/common';
|
|
|
|
type LogLevel = 'error' | 'warn' | 'log' | 'debug';
|
|
const severity: Record<LogLevel, number> = {
|
|
error: 0,
|
|
warn: 1,
|
|
log: 2,
|
|
debug: 3,
|
|
};
|
|
// 按字段名递归脱敏,日志中不得出现会话、密码、密钥或授权头原文。
|
|
const sensitiveKeys = /authorization|cookie|password|secret|token|api[_-]?key/i;
|
|
|
|
export class AppLogger implements LoggerService {
|
|
public constructor(private readonly minimumLevel: LogLevel) {}
|
|
|
|
public log(message: unknown, context?: unknown): void {
|
|
this.write('log', message, context);
|
|
}
|
|
|
|
public error(message: unknown, stack?: string, context?: unknown): void {
|
|
this.write('error', message, { stack, context });
|
|
}
|
|
|
|
public warn(message: unknown, context?: unknown): void {
|
|
this.write('warn', message, context);
|
|
}
|
|
|
|
public debug(message: unknown, context?: unknown): void {
|
|
this.write('debug', message, context);
|
|
}
|
|
|
|
public verbose(message: unknown, context?: unknown): void {
|
|
this.debug(message, context);
|
|
}
|
|
|
|
private write(level: LogLevel, message: unknown, context?: unknown): void {
|
|
if (severity[level] > severity[this.minimumLevel]) return;
|
|
const entry = {
|
|
timestamp: new Date().toISOString(),
|
|
level,
|
|
message: typeof message === 'string' ? message : sanitize(message),
|
|
context: sanitize(context),
|
|
};
|
|
// 每行独立 JSON,方便日志采集系统按字段索引 request_id、状态码等上下文。
|
|
const output = JSON.stringify(entry);
|
|
if (level === 'error') console.error(output);
|
|
else console.log(output);
|
|
}
|
|
}
|
|
|
|
function sanitize(value: unknown): unknown {
|
|
if (Array.isArray(value)) return value.map(sanitize);
|
|
if (value && typeof value === 'object') {
|
|
return Object.fromEntries(
|
|
Object.entries(value).map(([key, child]) => [
|
|
key,
|
|
sensitiveKeys.test(key) ? '[REDACTED]' : sanitize(child),
|
|
]),
|
|
);
|
|
}
|
|
return value;
|
|
}
|