| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 |
- import { createWriteStream, existsSync, mkdirSync } from 'fs'
- import { join } from 'path'
- export class ProductionLogger {
- constructor(config = {}) {
- this.currentFileSize = 0
- this.config = {
- level: 'info',
- enableConsole: true,
- enableFile: true,
- logDir: './logs',
- maxFileSize: 100,
- maxFiles: 10,
- enableRotation: true,
- enableAudit: true,
- ...config,
- }
- this.initializeLogDirectory()
- this.initializeLogStreams()
- }
- /**
- * 获取单例实例
- */
- static getInstance(config) {
- if (!ProductionLogger.instance) {
- ProductionLogger.instance = new ProductionLogger(config)
- }
- return ProductionLogger.instance
- }
- /**
- * 初始化日志目录
- */
- initializeLogDirectory() {
- if (this.config.enableFile && this.config.logDir) {
- if (!existsSync(this.config.logDir)) {
- mkdirSync(this.config.logDir, { recursive: true })
- }
- }
- }
- /**
- * 初始化日志流
- */
- initializeLogStreams() {
- if (this.config.enableFile && this.config.logDir) {
- const timestamp = new Date().toISOString().split('T')[0]
- // 主日志文件
- this.currentLogFile = join(this.config.logDir, `trading-system-${timestamp}.log`)
- this.logStream = createWriteStream(this.currentLogFile, { flags: 'a' })
- // 审计日志文件
- if (this.config.enableAudit) {
- const auditFile = join(this.config.logDir, `audit-${timestamp}.log`)
- this.auditStream = createWriteStream(auditFile, { flags: 'a' })
- }
- }
- }
- /**
- * 记录 Debug 级别日志
- */
- debug(message, metadata, module = 'SYSTEM') {
- this.log('debug', module, message, metadata)
- }
- /**
- * 记录 Info 级别日志
- */
- info(message, metadata, module = 'SYSTEM') {
- this.log('info', module, message, metadata)
- }
- /**
- * 记录 Warning 级别日志
- */
- warn(message, metadata, module = 'SYSTEM') {
- this.log('warn', module, message, metadata)
- }
- /**
- * 记录 Error 级别日志
- */
- error(message, error, metadata, module = 'SYSTEM') {
- const errorMetadata = {
- ...metadata,
- ...(error && {
- error: {
- name: error.name,
- message: error.message,
- stack: error.stack,
- },
- }),
- }
- this.log('error', module, message, errorMetadata)
- }
- /**
- * 记录 Critical 级别日志
- */
- critical(message, error, metadata, module = 'SYSTEM') {
- const errorMetadata = {
- ...metadata,
- ...(error && {
- error: {
- name: error.name,
- message: error.message,
- stack: error.stack,
- },
- }),
- }
- this.log('critical', module, message, errorMetadata)
- }
- /**
- * 记录交易相关日志
- */
- trade(action, details) {
- this.log('info', 'TRADING', `交易操作: ${action}`, {
- ...details,
- category: 'trading',
- })
- }
- /**
- * 记录对冲相关日志
- */
- hedge(action, details) {
- this.log('info', 'HEDGING', `对冲操作: ${action}`, {
- ...details,
- category: 'hedging',
- })
- }
- /**
- * 记录账户相关日志
- */
- account(action, details) {
- this.log('info', 'ACCOUNT', `账户操作: ${action}`, {
- ...details,
- category: 'account',
- })
- }
- /**
- * 记录 WebSocket 相关日志
- */
- websocket(action, details) {
- this.log('info', 'WEBSOCKET', `WebSocket事件: ${action}`, {
- ...details,
- category: 'websocket',
- })
- }
- /**
- * 记录性能相关日志
- */
- performance(metric, value, details) {
- this.log('info', 'PERFORMANCE', `性能指标: ${metric}`, {
- metric,
- value,
- unit: 'ms',
- ...details,
- category: 'performance',
- })
- }
- /**
- * 记录审计日志
- */
- audit(action, details) {
- const auditEntry = {
- timestamp: new Date().toISOString(),
- action,
- ...details,
- category: 'audit',
- }
- // 同时写入主日志和审计日志
- this.log('info', 'AUDIT', `审计操作: ${action}`, auditEntry)
- if (this.auditStream) {
- this.auditStream.write(JSON.stringify(auditEntry) + '\n')
- }
- }
- /**
- * 核心日志记录方法
- */
- log(level, module, message, metadata) {
- // 检查日志级别
- if (!this.shouldLog(level)) {
- return
- }
- const logEntry = {
- timestamp: new Date().toISOString(),
- level,
- module,
- message,
- metadata,
- }
- // 控制台输出
- if (this.config.enableConsole) {
- this.outputToConsole(logEntry)
- }
- // 文件输出
- if (this.config.enableFile && this.logStream) {
- this.outputToFile(logEntry)
- }
- // 检查文件轮转
- if (this.config.enableRotation && this.shouldRotate()) {
- this.rotateLogFile()
- }
- }
- /**
- * 检查是否应该记录该级别的日志
- */
- shouldLog(level) {
- const levels = ['debug', 'info', 'warn', 'error', 'critical']
- const currentLevelIndex = levels.indexOf(this.config.level)
- const messageLevelIndex = levels.indexOf(level)
- return messageLevelIndex >= currentLevelIndex
- }
- /**
- * 输出到控制台
- */
- outputToConsole(entry) {
- const coloredLevel = this.getColoredLevel(entry.level)
- const timestamp = entry.timestamp.split('T')[1].split('.')[0] // 只显示时间部分
- let output = `${timestamp} ${coloredLevel} [${entry.module}] ${entry.message}`
- if (entry.metadata) {
- const metadataStr = JSON.stringify(entry.metadata, null, 2)
- .split('\n')
- .map(line => ` ${line}`)
- .join('\n')
- output += `\n${metadataStr}`
- }
- // 根据级别选择输出方式
- switch (entry.level) {
- case 'error':
- case 'critical':
- console.error(output)
- break
- case 'warn':
- console.warn(output)
- break
- default:
- console.log(output)
- }
- }
- /**
- * 输出到文件
- */
- outputToFile(entry) {
- if (!this.logStream) return
- const logLine = JSON.stringify(entry) + '\n'
- this.logStream.write(logLine)
- this.currentFileSize += Buffer.byteLength(logLine)
- }
- /**
- * 获取带颜色的日志级别
- */
- getColoredLevel(level) {
- const colors = {
- debug: '\x1b[90m',
- info: '\x1b[32m',
- warn: '\x1b[33m',
- error: '\x1b[31m',
- critical: '\x1b[41m', // 红色背景
- }
- const reset = '\x1b[0m'
- return `${colors[level]}${level.toUpperCase().padEnd(8)}${reset}`
- }
- /**
- * 检查是否需要轮转日志文件
- */
- shouldRotate() {
- const maxSizeBytes = (this.config.maxFileSize || 100) * 1024 * 1024
- return this.currentFileSize > maxSizeBytes
- }
- /**
- * 轮转日志文件
- */
- rotateLogFile() {
- if (this.logStream) {
- this.logStream.end()
- }
- if (this.auditStream) {
- this.auditStream.end()
- }
- this.currentFileSize = 0
- this.initializeLogStreams()
- this.info(
- '日志文件已轮转',
- {
- newLogFile: this.currentLogFile,
- },
- 'LOGGER',
- )
- }
- /**
- * 创建子日志器
- */
- child(module, baseMetadata) {
- return {
- debug: (message, metadata) => this.debug(message, { ...baseMetadata, ...metadata }, module),
- info: (message, metadata) => this.info(message, { ...baseMetadata, ...metadata }, module),
- warn: (message, metadata) => this.warn(message, { ...baseMetadata, ...metadata }, module),
- error: (message, error, metadata) => this.error(message, error, { ...baseMetadata, ...metadata }, module),
- critical: (message, error, metadata) => this.critical(message, error, { ...baseMetadata, ...metadata }, module),
- }
- }
- /**
- * 关闭日志器
- */
- close() {
- if (this.logStream) {
- this.logStream.end()
- }
- if (this.auditStream) {
- this.auditStream.end()
- }
- }
- /**
- * 获取统计信息
- */
- getStats() {
- return {
- config: this.config,
- currentLogFile: this.currentLogFile,
- currentFileSize: this.currentFileSize,
- streamActive: !!this.logStream && !this.logStream.destroyed,
- }
- }
- }
- // 创建默认的全局日志器实例
- export const logger = ProductionLogger.getInstance({
- level: process.env.LOG_LEVEL || 'info',
- enableConsole: process.env.NODE_ENV !== 'test',
- enableFile: process.env.NODE_ENV === 'production',
- logDir: process.env.LOG_DIR || './logs',
- })
|