Node.js编程题✅
本章节涵盖Node.js开发中的典型编程题,包括热更新实现、日志系统设计等实际应用场景。
什么是热更新,Node.js如何实现热更新?
答案
核心概念
热更新(Hot Reload)是指在应用运行时动态更新代码,无需重启整个应用程序。在Node.js中,热更新主要通过监听文件变化、清除模块缓存、重新加载模块来实现。
实现原理
1. 文件监听机制
- 使用
fs.watch()或第三方库如chokidar监听文件变化 - 检测
.js、.json等模块文件的修改、删除事件
2. 模块缓存管理
- Node.js使用
require.cache缓存已加载的模块 - 热更新时需要删除对应模块的缓存
- 重新
require()模块获取最新代码
3. 依赖关系处理
- 清理被修改模块的依赖模块缓存
- 处理循环依赖的情况
- 保持应用状态的一致性
示例实现:
- 热更新实现
- 实现细节
// Node.js 热更新实现示例 const fs = require('fs'); const path = require('path'); const { pathToFileURL } = require('url'); const chokidar = require('chokidar'); // npm install chokidar /** * 简单的模块热更新实现 */ class SimpleHotReload { constructor() { this.cache = new Map(); // 模块缓存 this.watchers = new Map(); // 文件监听器 this.callbacks = new Map(); // 变更回调 } // 加载模块并监听文件变化 require(modulePath) { const absolutePath = path.resolve(modulePath); // 如果已经缓存,直接返回 if (this.cache.has(absolutePath)) { return this.cache.get(absolutePath); } try { // 动态导入模块 delete require.cache[absolutePath]; const module = require(absolutePath); // 缓存模块 this.cache.set(absolutePath, module); // 监听文件变化 this.watchFile(absolutePath); return module; } catch (error) { console.error(`加载模块失败: ${modulePath}`, error); return null; } } // 监听文件变化 watchFile(filePath) { if (this.watchers.has(filePath)) { return; } const watcher = chokidar.watch(filePath, { ignoreInitial: true, persistent: true }); watcher.on('change', () => { console.log(`文件变化检测到: ${filePath}`); this.reloadModule(filePath); }); watcher.on('unlink', () => { console.log(`文件被删除: ${filePath}`); this.unloadModule(filePath); }); this.watchers.set(filePath, watcher); } // 重新加载模块 reloadModule(filePath) { try { // 清除require缓存 delete require.cache[filePath]; // 重新加载模块 const newModule = require(filePath); const oldModule = this.cache.get(filePath); // 更新缓存 this.cache.set(filePath, newModule); // 触发回调 const callback = this.callbacks.get(filePath); if (callback) { callback(newModule, oldModule); } console.log(`模块热更新成功: ${filePath}`); } catch (error) { console.error(`模块热更新失败: ${filePath}`, error); } } // 卸载模块 unloadModule(filePath) { // 清除缓存 this.cache.delete(filePath); delete require.cache[filePath]; // 关闭文件监听 const watcher = this.watchers.get(filePath); if (watcher) { watcher.close(); this.watchers.delete(filePath); } // 清除回调 this.callbacks.delete(filePath); } // 注册模块变更回调 onModuleChange(modulePath, callback) { const absolutePath = path.resolve(modulePath); this.callbacks.set(absolutePath, callback); } // 关闭所有监听器 destroy() { for (const [filePath, watcher] of this.watchers) { watcher.close(); } this.watchers.clear(); this.cache.clear(); this.callbacks.clear(); } } /** * HTTP服务器热更新实现 */ class HTTPServerHotReload { constructor(port = 3000) { this.port = port; this.server = null; this.hotReload = new SimpleHotReload(); } start() { // 加载路由模块 const router = this.hotReload.require('./router'); // 创建HTTP服务器 const http = require('http'); this.server = http.createServer((req, res) => { try { // 获取当前路由模块 const currentRouter = this.hotReload.cache.get(path.resolve('./router')); if (currentRouter && typeof currentRouter.handle === 'function') { currentRouter.handle(req, res); } else { res.writeHead(500); res.end('Router module not found or invalid'); } } catch (error) { console.error('Request handling error:', error); res.writeHead(500); res.end('Internal Server Error'); } }); // 监听路由模块变化 this.hotReload.onModuleChange('./router', (newRouter, oldRouter) => { console.log('路由模块已热更新'); // 可以在这里执行一些清理工作 if (oldRouter && typeof oldRouter.cleanup === 'function') { oldRouter.cleanup(); } }); this.server.listen(this.port, () => { console.log(`HTTP服务器启动在端口 ${this.port}`); console.log('支持热更新,修改 router.js 文件试试'); }); } stop() { if (this.server) { this.server.close(); } this.hotReload.destroy(); } } /** * 配置文件热更新实现 */ class ConfigHotReload { constructor(configPath) { this.configPath = path.resolve(configPath); this.config = {}; this.callbacks = []; this.watcher = null; this.loadConfig(); this.watchConfig(); } // 加载配置文件 loadConfig() { try { const content = fs.readFileSync(this.configPath, 'utf8'); this.config = JSON.parse(content); console.log('配置文件加载成功:', this.configPath); } catch (error) { console.error('配置文件加载失败:', error); this.config = {}; } } // 监听配置文件变化 watchConfig() { this.watcher = chokidar.watch(this.configPath, { ignoreInitial: true }); this.watcher.on('change', () => { console.log('配置文件变化检测到'); const oldConfig = { ...this.config }; this.loadConfig(); // 触发变更回调 this.callbacks.forEach(callback => { try { callback(this.config, oldConfig); } catch (error) { console.error('配置变更回调执行失败:', error); } }); }); } // 获取配置 get(key) { return key ? this.config[key] : this.config; } // 注册配置变更回调 onChange(callback) { this.callbacks.push(callback); } // 销毁监听器 destroy() { if (this.watcher) { this.watcher.close(); } this.callbacks = []; } } // 演示用法 function demonstrateHotReload() { console.log('=== Node.js 热更新演示 ===\n'); // 1. 简单模块热更新演示 console.log('1. 简单模块热更新演示'); const hotReload = new SimpleHotReload(); // 创建一个示例模块文件 const moduleContent = ` // 示例模块 - 计算器 module.exports = { add: (a, b) => { console.log(\`执行加法: \${a} + \${b} = \${a + b}\`); return a + b; }, version: '1.0.0' }; `; fs.writeFileSync('./example-module.js', moduleContent.trim()); // 加载模块 let calculator = hotReload.require('./example-module.js'); console.log('初始版本:', calculator.version); console.log('执行计算:', calculator.add(2, 3)); // 监听模块变化 hotReload.onModuleChange('./example-module.js', (newModule, oldModule) => { console.log('模块已更新!'); console.log('新版本:', newModule.version); console.log('旧版本:', oldModule.version); calculator = newModule; // 更新引用 }); // 2. 配置热更新演示 setTimeout(() => { console.log('\n2. 配置热更新演示'); // 创建配置文件 const configContent = { port: 3000, debug: false, maxConnections: 100 }; fs.writeFileSync('./config.json', JSON.stringify(configContent, null, 2)); // 配置热更新 const config = new ConfigHotReload('./config.json'); config.onChange((newConfig, oldConfig) => { console.log('配置已更新!'); console.log('新配置:', JSON.stringify(newConfig, null, 2)); }); console.log('当前配置:', JSON.stringify(config.get(), null, 2)); // 3. 演示配置变更 setTimeout(() => { console.log('\n3. 模拟配置文件变更'); const updatedConfig = { port: 8080, debug: true, maxConnections: 200, newFeature: 'enabled' }; fs.writeFileSync('./config.json', JSON.stringify(updatedConfig, null, 2)); }, 2000); // 4. 演示模块文件变更 setTimeout(() => { console.log('\n4. 模拟模块文件变更'); const updatedModuleContent = ` // 示例模块 - 增强版计算器 module.exports = { add: (a, b) => { const result = a + b; console.log(\`执行加法: \${a} + \${b} = \${result}\`); return result; }, subtract: (a, b) => { const result = a - b; console.log(\`执行减法: \${a} - \${b} = \${result}\`); return result; }, version: '2.0.0' }; `; fs.writeFileSync('./example-module.js', updatedModuleContent.trim()); // 测试更新后的功能 setTimeout(() => { console.log('测试更新后的模块:'); console.log('加法:', calculator.add(5, 3)); if (calculator.subtract) { console.log('减法:', calculator.subtract(10, 4)); } }, 1000); }, 4000); // 清理资源 setTimeout(() => { console.log('\n清理演示资源...'); hotReload.destroy(); config.destroy(); // 删除示例文件 try { fs.unlinkSync('./example-module.js'); fs.unlinkSync('./config.json'); } catch (error) { // 忽略删除错误 } console.log('演示结束'); }, 8000); }, 1000); } // Express.js 风格的热更新中间件 function createHotReloadMiddleware(options = {}) { const { watchDirs = ['./routes', './middleware'], ignored = /node_modules/ } = options; const hotReload = new SimpleHotReload(); return { // 中间件函数 middleware: (req, res, next) => { // 为每个请求重新加载模块 req.hotReload = hotReload; next(); }, // 监听指定目录 watch: () => { watchDirs.forEach(dir => { const watcher = chokidar.watch(dir, { ignored, ignoreInitial: false, persistent: true }); watcher.on('change', (filePath) => { console.log(`文件变化: ${filePath}`); // 清除该文件及其依赖的缓存 delete require.cache[path.resolve(filePath)]; }); }); }, // 销毁 destroy: () => { hotReload.destroy(); } }; } // 如果直接运行此文件,则执行演示 if (require.main === module) { demonstrateHotReload(); } module.exports = { SimpleHotReload, HTTPServerHotReload, ConfigHotReload, createHotReloadMiddleware, demonstrateHotReload };
基础热更新流程:
// 1. 监听文件变化
const watcher = chokidar.watch('./src', {
ignoreInitial: true
});
watcher.on('change', (filePath) => {
// 2. 清除模块缓存
delete require.cache[require.resolve(filePath)];
// 3. 重新加载模块
const newModule = require(filePath);
// 4. 触发更新回调
onModuleChange(newModule);
});
HTTP服务器热更新:
// 路由热更新示例
const server = http.createServer((req, res) => {
// 每次请求时获取最新的路由模块
const currentRouter = hotReload.require('./router');
currentRouter.handle(req, res);
});
应用场景:
开发环境:
- 提升开发效率,无需频繁重启应用
- 保持应用状态,如WebSocket连接、内存数据
- 快速验证代码修改效果
生产环境:
- 配置文件热更新,如数据库连接配置
- 业务规则热更新,如价格策略、权限配置
- 模板文件热更新,如邮件模板、页面模板
注意事项:
- 内存泄漏: 未正确清理的定时器、事件监听器会导致内存泄漏
- 状态一致性: 模块更新可能导致应用状态不一致
- 依赖管理: 复杂的依赖关系需要仔细处理
- 错误处理: 新代码可能包含语法错误,需要优雅处理
面试官视角
该题考察候选人对Node.js模块系统和文件监听的理解:
- 要点清单: 理解require.cache机制;掌握文件监听API;能设计热更新方案;了解注意事项
- 加分项: 有实际热更新开发经验;了解生产环境应用;能处理复杂依赖关系;考虑性能和稳定性
- 常见失误: 只知道基本概念;不了解缓存清理;忽视内存泄漏;缺乏错误处理
延伸阅读
- Node.js模块系统详解 — 官方模块系统文档
- 《深入浅出Node.js》 — Node.js模块机制深度解析
如何设计一个日志系统?
答案
核心概念
日志系统是应用程序的重要基础设施,用于记录、管理和分析应用运行时的各种信息。一个完善的日志系统应该包括日志级别、格式化、传输、轮转等功能。
日志系统架构
1. 日志级别管理
- ERROR: 错误信息,需要立即处理
- WARN: 警告信息,需要关注
- INFO: 一般信息,记录重要事件
- DEBUG: 调试信息,开发时使用
- TRACE: 跟踪信息,详细执行流程
2. 日志格式化
- 时间戳: ISO 8601格式或自定义格式
- 级别标识: 清晰的级别标记
- 分类标签: 模块或服务标识
- 消息内容: 实际日志信息
- 元数据: 上下文信息,如用户ID、请求ID
3. 传输器设计
- Console传输器: 控制台输出
- File传输器: 文件存储,支持轮转
- HTTP传输器: 远程日志服务
- Database传输器: 数据库存储
示例实现:
- 日志系统实现
- 设计模式
// Node.js 日志系统设计实现 const fs = require('fs'); const path = require('path'); const util = require('util'); const EventEmitter = require('events'); /** * 日志级别定义 */ const LOG_LEVELS = { ERROR: { name: 'ERROR', value: 0, color: '\x1b[31m' }, // 红色 WARN: { name: 'WARN', value: 1, color: '\x1b[33m' }, // 黄色 INFO: { name: 'INFO', value: 2, color: '\x1b[32m' }, // 绿色 DEBUG: { name: 'DEBUG', value: 3, color: '\x1b[36m' }, // 青色 TRACE: { name: 'TRACE', value: 4, color: '\x1b[37m' } // 白色 }; const RESET_COLOR = '\x1b[0m'; /** * 日志格式化器 */ class LogFormatter { constructor(options = {}) { this.template = options.template || '[{timestamp}] [{level}] [{category}] {message}'; this.timestampFormat = options.timestampFormat || 'iso'; this.colorize = options.colorize !== false; } format(logEntry) { const { level, message, category, timestamp, metadata } = logEntry; let formattedMessage = this.template .replace('{timestamp}', this.formatTimestamp(timestamp)) .replace('{level}', level.name) .replace('{category}', category || 'APP') .replace('{message}', this.formatMessage(message)); // 添加元数据 if (metadata && Object.keys(metadata).length > 0) { formattedMessage += ' | ' + JSON.stringify(metadata); } // 添加颜色 if (this.colorize && process.stdout.isTTY) { formattedMessage = level.color + formattedMessage + RESET_COLOR; } return formattedMessage; } formatTimestamp(timestamp) { switch (this.timestampFormat) { case 'iso': return timestamp.toISOString(); case 'locale': return timestamp.toLocaleString(); case 'epoch': return timestamp.getTime().toString(); default: return timestamp.toISOString(); } } formatMessage(message) { if (typeof message === 'string') { return message; } if (message instanceof Error) { return `${message.message}\n${message.stack}`; } return util.inspect(message, { depth: 3, colors: false }); } } /** * 日志传输器基类 */ class LogTransport extends EventEmitter { constructor(options = {}) { super(); this.level = options.level || LOG_LEVELS.INFO; this.formatter = options.formatter || new LogFormatter(options); this.silent = options.silent || false; } shouldLog(level) { return !this.silent && level.value <= this.level.value; } log(logEntry) { throw new Error('log method must be implemented by subclass'); } } /** * 控制台传输器 */ class ConsoleTransport extends LogTransport { constructor(options = {}) { super(options); this.stream = options.stream || ( options.level && options.level.value <= LOG_LEVELS.WARN.value ? process.stderr : process.stdout ); } log(logEntry) { if (!this.shouldLog(logEntry.level)) { return; } const formatted = this.formatter.format(logEntry); this.stream.write(formatted + '\n'); this.emit('logged', logEntry); } } /** * 文件传输器 */ class FileTransport extends LogTransport { constructor(options = {}) { super(options); this.filename = options.filename || 'app.log'; this.maxSize = options.maxSize || 10 * 1024 * 1024; // 10MB this.maxFiles = options.maxFiles || 5; this.dirname = options.dirname || './logs'; this.writeStream = null; this.currentSize = 0; this.ensureDirectory(); this.createWriteStream(); } ensureDirectory() { if (!fs.existsSync(this.dirname)) { fs.mkdirSync(this.dirname, { recursive: true }); } } createWriteStream() { const filepath = path.join(this.dirname, this.filename); // 检查现有文件大小 try { const stats = fs.statSync(filepath); this.currentSize = stats.size; } catch (error) { this.currentSize = 0; } this.writeStream = fs.createWriteStream(filepath, { flags: 'a' }); this.writeStream.on('error', (error) => { this.emit('error', error); }); } log(logEntry) { if (!this.shouldLog(logEntry.level)) { return; } const formatted = this.formatter.format(logEntry) + '\n'; const messageSize = Buffer.byteLength(formatted, 'utf8'); // 检查是否需要轮转日志文件 if (this.currentSize + messageSize > this.maxSize) { this.rotateLogFile(); } this.writeStream.write(formatted); this.currentSize += messageSize; this.emit('logged', logEntry); } rotateLogFile() { this.writeStream.end(); // 轮转现有文件 for (let i = this.maxFiles - 1; i >= 1; i--) { const oldFile = path.join(this.dirname, `${this.filename}.${i}`); const newFile = path.join(this.dirname, `${this.filename}.${i + 1}`); try { if (fs.existsSync(oldFile)) { if (i === this.maxFiles - 1) { fs.unlinkSync(oldFile); // 删除最老的文件 } else { fs.renameSync(oldFile, newFile); } } } catch (error) { this.emit('error', error); } } // 重命名当前文件 const currentFile = path.join(this.dirname, this.filename); const rotatedFile = path.join(this.dirname, `${this.filename}.1`); try { if (fs.existsSync(currentFile)) { fs.renameSync(currentFile, rotatedFile); } } catch (error) { this.emit('error', error); } // 创建新的写入流 this.currentSize = 0; this.createWriteStream(); } close() { if (this.writeStream) { this.writeStream.end(); } } } /** * HTTP传输器 - 将日志发送到远程服务器 */ class HTTPTransport extends LogTransport { constructor(options = {}) { super(options); this.url = options.url || 'http://localhost:3000/logs'; this.method = options.method || 'POST'; this.headers = options.headers || { 'Content-Type': 'application/json' }; this.batchSize = options.batchSize || 10; this.flushInterval = options.flushInterval || 5000; this.batch = []; this.timer = null; this.startBatchTimer(); } log(logEntry) { if (!this.shouldLog(logEntry.level)) { return; } this.batch.push(logEntry); if (this.batch.length >= this.batchSize) { this.flush(); } } startBatchTimer() { this.timer = setInterval(() => { if (this.batch.length > 0) { this.flush(); } }, this.flushInterval); } flush() { if (this.batch.length === 0) { return; } const logs = this.batch.splice(0); const payload = JSON.stringify({ logs }); // 使用内置http模块发送请求 const url = new URL(this.url); const http = url.protocol === 'https:' ? require('https') : require('http'); const options = { hostname: url.hostname, port: url.port, path: url.pathname, method: this.method, headers: { ...this.headers, 'Content-Length': Buffer.byteLength(payload) } }; const req = http.request(options, (res) => { if (res.statusCode < 200 || res.statusCode >= 300) { this.emit('error', new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`)); } else { this.emit('sent', logs); } }); req.on('error', (error) => { this.emit('error', error); // 将失败的日志重新放回批次队列 this.batch.unshift(...logs); }); req.write(payload); req.end(); } close() { if (this.timer) { clearInterval(this.timer); } this.flush(); } } /** * 主日志器类 */ class Logger extends EventEmitter { constructor(options = {}) { super(); this.category = options.category || 'default'; this.transports = options.transports || []; this.level = options.level || LOG_LEVELS.INFO; this.metadata = options.metadata || {}; } addTransport(transport) { this.transports.push(transport); transport.on('error', (error) => { this.emit('error', error); }); return this; } removeTransport(transport) { const index = this.transports.indexOf(transport); if (index !== -1) { this.transports.splice(index, 1); } return this; } log(level, message, metadata = {}) { if (!this.shouldLog(level)) { return; } const logEntry = { level, message, category: this.category, timestamp: new Date(), metadata: { ...this.metadata, ...metadata } }; // 发送到所有传输器 this.transports.forEach(transport => { try { transport.log(logEntry); } catch (error) { this.emit('error', error); } }); this.emit('logged', logEntry); } shouldLog(level) { return level.value <= this.level.value; } // 便捷方法 error(message, metadata) { return this.log(LOG_LEVELS.ERROR, message, metadata); } warn(message, metadata) { return this.log(LOG_LEVELS.WARN, message, metadata); } info(message, metadata) { return this.log(LOG_LEVELS.INFO, message, metadata); } debug(message, metadata) { return this.log(LOG_LEVELS.DEBUG, message, metadata); } trace(message, metadata) { return this.log(LOG_LEVELS.TRACE, message, metadata); } // 创建子日志器 child(options = {}) { return new Logger({ category: options.category || this.category, level: options.level || this.level, metadata: { ...this.metadata, ...options.metadata }, transports: this.transports }); } // 关闭所有传输器 close() { this.transports.forEach(transport => { if (typeof transport.close === 'function') { transport.close(); } }); } } /** * 日志系统管理器 */ class LogSystem { constructor() { this.loggers = new Map(); this.defaultConfig = { level: LOG_LEVELS.INFO, transports: [ new ConsoleTransport({ level: LOG_LEVELS.DEBUG, colorize: true }) ] }; } // 获取或创建日志器 getLogger(category = 'default', options = {}) { if (this.loggers.has(category)) { return this.loggers.get(category); } const config = { ...this.defaultConfig, ...options }; const logger = new Logger({ category, ...config }); this.loggers.set(category, logger); return logger; } // 配置默认设置 configure(config) { this.defaultConfig = { ...this.defaultConfig, ...config }; return this; } // 关闭所有日志器 shutdown() { for (const [category, logger] of this.loggers) { logger.close(); } this.loggers.clear(); } } // 创建全局日志系统实例 const logSystem = new LogSystem(); // 演示用法 function demonstrateLogSystem() { console.log('=== Node.js 日志系统演示 ===\n'); // 1. 基础用法演示 console.log('1. 基础日志记录演示'); const logger = logSystem.getLogger('demo'); logger.info('应用启动成功'); logger.warn('这是一个警告消息'); logger.error('这是一个错误消息'); logger.debug('调试信息,可能不会显示'); // 2. 带元数据的日志 console.log('\n2. 带元数据的日志记录'); logger.info('用户登录', { userId: '12345', ip: '192.168.1.1', userAgent: 'Mozilla/5.0...' }); // 3. 错误对象日志 console.log('\n3. 错误对象日志记录'); try { throw new Error('示例错误'); } catch (error) { logger.error(error); } // 4. 文件日志传输器 console.log('\n4. 文件日志传输器演示'); const fileLogger = new Logger({ category: 'file-demo', transports: [ new FileTransport({ filename: 'demo.log', level: LOG_LEVELS.INFO, maxSize: 1024, // 1KB for demo maxFiles: 3 }) ] }); // 生成一些日志来演示文件轮转 for (let i = 1; i <= 20; i++) { fileLogger.info(`这是第 ${i} 条日志消息,用于演示文件轮转功能。`.repeat(3)); } // 5. 子日志器演示 console.log('\n5. 子日志器演示'); const childLogger = logger.child({ category: 'auth-service', metadata: { service: 'authentication', version: '1.0.0' } }); childLogger.info('认证服务初始化完成'); childLogger.error('认证失败', { username: 'john_doe', reason: 'invalid_password' }); // 6. HTTP传输器演示(模拟) console.log('\n6. HTTP传输器演示(仅创建,不实际发送)'); const httpTransport = new HTTPTransport({ url: 'http://localhost:3000/logs', batchSize: 5, level: LOG_LEVELS.WARN }); const httpLogger = new Logger({ category: 'http-demo', transports: [httpTransport] }); // 模拟一些日志 httpLogger.warn('这将被发送到HTTP端点'); httpLogger.error('HTTP传输器错误测试'); // 7. 自定义格式化器 console.log('\n7. 自定义格式化器演示'); const customFormatter = new LogFormatter({ template: '{timestamp} | {level} | {message}', timestampFormat: 'locale', colorize: true }); const customLogger = new Logger({ category: 'custom', transports: [ new ConsoleTransport({ formatter: customFormatter, level: LOG_LEVELS.DEBUG }) ] }); customLogger.info('使用自定义格式的日志消息'); customLogger.debug('自定义调试信息'); // 清理资源 setTimeout(() => { console.log('\n清理演示资源...'); fileLogger.close(); httpTransport.close(); // 删除演示日志文件 try { const logDir = './logs'; if (fs.existsSync(logDir)) { const files = fs.readdirSync(logDir); files.forEach(file => { if (file.startsWith('demo.log')) { fs.unlinkSync(path.join(logDir, file)); } }); fs.rmdirSync(logDir); } } catch (error) { console.error('清理文件时出错:', error); } console.log('日志系统演示完成'); }, 3000); } // Express.js 集成中间件 function createLoggerMiddleware(options = {}) { const logger = logSystem.getLogger('http', options); return (req, res, next) => { const start = Date.now(); // 记录请求开始 logger.info('Request started', { method: req.method, url: req.url, ip: req.ip, userAgent: req.get('User-Agent') }); // 监听响应完成 res.on('finish', () => { const duration = Date.now() - start; const level = res.statusCode >= 400 ? LOG_LEVELS.ERROR : LOG_LEVELS.INFO; logger.log(level, 'Request completed', { method: req.method, url: req.url, statusCode: res.statusCode, duration: `${duration}ms`, contentLength: res.get('content-length') }); }); next(); }; } // 如果直接运行此文件,则执行演示 if (require.main === module) { demonstrateLogSystem(); } module.exports = { LOG_LEVELS, LogFormatter, LogTransport, ConsoleTransport, FileTransport, HTTPTransport, Logger, LogSystem, logSystem, createLoggerMiddleware, demonstrateLogSystem };
观察者模式:
class Logger extends EventEmitter {
log(level, message, metadata) {
const logEntry = { level, message, timestamp: new Date(), metadata };
// 触发事件
this.emit('logged', logEntry);
// 发送给所有传输器
this.transports.forEach(transport => {
transport.log(logEntry);
});
}
}
策略模式:
class LogFormatter {
format(logEntry) {
// 根据配置选择不同的格式化策略
return this.formatStrategy.format(logEntry);
}
}
责任链模式:
class LoggerChain {
setNext(logger) {
this.nextLogger = logger;
return logger;
}
handle(logEntry) {
if (this.canHandle(logEntry)) {
this.process(logEntry);
}
if (this.nextLogger) {
this.nextLogger.handle(logEntry);
}
}
}
高级特性
1. 日志轮转
- 按大小轮转: 文件超过指定大小时创建新文件
- 按时间轮转: 按天、小时轮转日志文件
- 压缩归档: 旧日志文件压缩存储
- 自动清理: 删除过期的日志文件
2. 结构化日志
- JSON格式: 便于机器解析和检索
- 统一字段: 标准的字段名称和格式
- 元数据丰富: 包含足够的上下文信息
3. 性能优化
- 异步写入: 避免阻塞主线程
- 批量写入: 减少I/O操作次数
- 缓冲机制: 内存缓冲提升性能
- 采样日志: 高频日志采样记录
4. 监控告警
- 错误统计: 统计错误日志数量和类型
- 性能指标: 记录应用性能数据
- 告警触发: 根据日志内容触发告警
- 健康检查: 日志系统自身的健康监控
生产环境考虑:
安全性:
- 敏感信息过滤: 避免记录密码、令牌等
- 访问控制: 限制日志文件访问权限
- 数据脱敏: 对个人信息进行脱敏处理
可靠性:
- 故障恢复: 传输器故障时的处理机制
- 数据完整性: 确保日志不丢失
- 备份策略: 重要日志的备份方案
可扩展性:
- 插件化架构: 支持自定义传输器和格式化器
- 配置管理: 支持动态配置更新
- 多实例支持: 支持分布式应用日志聚合
集成示例:
// Express.js 集成
const express = require('express');
const { logSystem } = require('./log-system');
const app = express();
const logger = logSystem.getLogger('web');
// 请求日志中间件
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info('Request processed', {
method: req.method,
url: req.url,
statusCode: res.statusCode,
duration: `${duration}ms`
});
});
next();
});
// 错误处理
app.use((error, req, res, next) => {
logger.error('Request error', {
error: error.message,
stack: error.stack,
url: req.url
});
next(error);
});
面试官视角
该题考察候选人的系统设计能力:
- 要点清单: 理解日志系统架构;掌握设计模式应用;考虑性能和可靠性;有实际开发经验
- 加分项: 有大型系统日志设计经验;了解ELK等日志栈;考虑分布式日志聚合;有监控告警经验
- 常见失误: 设计过于简单;不考虑性能问题;忽视安全性;缺乏扩展性设计
延伸阅读
- Winston日志库设计 — Node.js主流日志库
- 《高性能日志系统设计》 — 企业级日志系统设计实践