Files
whale-town-end/src/core/utils/logger/logger.config.ts
moyin c6ca204fae feat:增强日志系统功能
- 新增高级日志配置工厂类,支持环境差异化配置
- 新增日志管理服务,提供定时清理和健康监控
- 支持生产环境多文件分类输出(app.log、error.log、access.log)
- 支持开发环境美化输出和文件备份
- 添加自动日志清理和统计功能
2025-12-13 16:44:18 +08:00

278 lines
7.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.
/**
* 日志配置模块
*
* 功能描述:
* - 提供详细的日志配置选项
* - 支持日志文件轮转和管理
* - 根据环境自动调整日志策略
* - 提供日志文件清理和归档功能
*
* @author 开发团队
* @version 1.0.0
* @since 2024-12-13
*/
import { ConfigService } from '@nestjs/config';
import * as path from 'path';
import * as fs from 'fs';
/**
* 日志配置工厂类
*
* 职责:
* - 根据环境变量生成 Pino 日志配置
* - 管理日志文件的创建和轮转
* - 提供不同环境的日志策略
*/
export class LoggerConfigFactory {
/**
* 创建 Pino 日志配置
*
* 功能描述:
* 根据环境变量和配置生成完整的 Pino 日志配置对象
*
* 业务逻辑:
* 1. 读取环境变量配置
* 2. 确保日志目录存在
* 3. 根据环境选择不同的输出策略
* 4. 配置日志轮转和清理策略
*
* @param configService 配置服务实例
* @returns Pino 日志配置对象
*/
static createLoggerConfig(configService: ConfigService) {
const isProduction = configService.get('NODE_ENV') === 'production';
const logDir = configService.get('LOG_DIR', './logs');
const logLevel = configService.get('LOG_LEVEL', isProduction ? 'info' : 'debug');
const appName = configService.get('APP_NAME', 'pixel-game-server');
// 确保日志目录存在
this.ensureLogDirectory(logDir);
return {
pinoHttp: {
level: logLevel,
// 根据环境配置不同的输出策略
transport: this.createTransportConfig(isProduction, logDir, logLevel),
// 自定义序列化器
serializers: this.createSerializers(),
// 基础字段
base: {
pid: process.pid,
hostname: require('os').hostname(),
app: appName,
version: process.env.npm_package_version || '1.0.0',
},
// HTTP 请求日志配置
autoLogging: true,
// 自定义日志级别判断
customLogLevel: this.customLogLevel,
// 自定义请求ID生成
genReqId: (req: any) => req.headers['x-request-id'] || this.generateRequestId(),
// 自定义成功响应消息
customSuccessMessage: (req: any, res: any) => {
return `${req.method} ${req.url} completed in ${res.responseTime}ms`;
},
// 自定义错误响应消息
customErrorMessage: (req: any, res: any, err: any) => {
return `${req.method} ${req.url} failed: ${err.message}`;
},
},
};
}
/**
* 创建传输配置
*
* @param isProduction 是否为生产环境
* @param logDir 日志目录
* @param logLevel 日志级别
* @returns 传输配置对象
* @private
*/
private static createTransportConfig(isProduction: boolean, logDir: string, logLevel: string) {
if (isProduction) {
// 生产环境:多目标输出,包含日志轮转
return {
targets: [
{
// 应用日志(所有级别)
target: 'pino/file',
options: {
destination: path.join(logDir, 'app.log'),
mkdir: true,
},
level: 'info',
},
{
// 错误日志(仅错误和致命错误)
target: 'pino/file',
options: {
destination: path.join(logDir, 'error.log'),
mkdir: true,
},
level: 'error',
},
{
// 访问日志HTTP 请求)
target: 'pino/file',
options: {
destination: path.join(logDir, 'access.log'),
mkdir: true,
},
level: 'info',
},
{
// 控制台输出(用于容器日志收集)
target: 'pino/file',
options: {
destination: 1, // stdout
},
level: 'warn',
},
],
};
} else {
// 开发环境:美化输出 + 文件备份
return {
targets: [
{
// 控制台美化输出
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'SYS:yyyy-mm-dd HH:MM:ss',
ignore: 'pid,hostname',
messageFormat: '{app} [{level}] {msg}',
customPrettifiers: {
time: (timestamp: any) => `🕐 ${timestamp}`,
level: (logLevel: any) => {
const levelEmojis: Record<number, string> = {
10: '🔍', // trace
20: '🐛', // debug
30: '📝', // info
40: '⚠️', // warn
50: '❌', // error
60: '💀', // fatal
};
return `${levelEmojis[logLevel] || '📝'} ${logLevel}`;
},
},
},
level: logLevel,
},
{
// 开发环境文件输出
target: 'pino/file',
options: {
destination: path.join(logDir, 'dev.log'),
mkdir: true,
},
level: 'debug',
},
],
};
}
}
/**
* 创建序列化器配置
*
* @returns 序列化器配置对象
* @private
*/
private static createSerializers() {
return {
req: (req: any) => ({
id: req.id,
method: req.method,
url: req.url,
path: req.route?.path,
parameters: req.params,
query: req.query,
headers: {
host: req.headers.host,
'user-agent': req.headers['user-agent'],
'content-type': req.headers['content-type'],
'content-length': req.headers['content-length'],
authorization: req.headers.authorization ? '[REDACTED]' : undefined,
},
ip: req.ip,
ips: req.ips,
hostname: req.hostname,
}),
res: (res: any) => ({
statusCode: res.statusCode,
statusMessage: res.statusMessage,
headers: {
'content-type': res.getHeader('content-type'),
'content-length': res.getHeader('content-length'),
},
responseTime: res.responseTime,
}),
err: (err: any) => ({
type: err.constructor.name,
message: err.message,
stack: err.stack,
code: err.code,
statusCode: err.statusCode,
}),
};
}
/**
* 自定义日志级别判断
*
* @param req HTTP 请求对象
* @param res HTTP 响应对象
* @param err 错误对象
* @returns 日志级别
* @private
*/
private static customLogLevel(req: any, res: any, err: any) {
if (res.statusCode >= 400 && res.statusCode < 500) {
return 'warn';
} else if (res.statusCode >= 500 || err) {
return 'error';
} else if (res.statusCode >= 300 && res.statusCode < 400) {
return 'info';
}
return 'info';
}
/**
* 生成请求ID
*
* @returns 唯一的请求ID
* @private
*/
private static generateRequestId(): string {
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
/**
* 确保日志目录存在
*
* @param logDir 日志目录路径
* @private
*/
private static ensureLogDirectory(logDir: string): void {
try {
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
console.log(`📁 Created log directory: ${logDir}`);
}
} catch (error) {
console.error(`❌ Failed to create log directory: ${logDir}`, error);
throw error;
}
}
}