用ES6 class实现支持继承的日志记录器,基类Logger封装时间戳、级别和格式化,子类通过extends和super()继承并实现output方法;ConsoleLogger输出到控制台,FileLogger写入文件,CompositeLogger组合多日志器实现多端输出。

<p>用 JavaScript 的面向对象思想实现支持继承的日志记录器,核心是利用 <strong>ES6 class</strong> 语法 + <strong>extends</strong> + <strong>super()</strong>,同时封装通用日志行为(如时间戳、级别、格式化),让子类可复写或扩展逻辑。</p>
<H3>基础 Logger 类:封装共性功能</H3>
<p>定义一个基类 <code>Logger</code>,负责统一处理日志时间、级别、输出格式,不直接打印,而是提供 <code>log()</code> 模板方法,由子类决定如何输出:</p>
<font color="#666">
<pre>
class Logger {
constructor(name = 'Logger') {
this.name = name;
}
// 格式化日志条目(可被子类复写)
format(level, message, ...args) {
const time = new Date().toISOString();
return `[${time}] [${level.toUpperCase()}] [${this.name}] ${message}`;
}
// 模板方法:定义流程,具体输出交给子类
log(level, message, ...args) {
const formatted = this.format(level, message, ...args);
this.output(formatted, level, ...args); // 关键:output 由子类实现
}
// 默认不实现,强制子类提供
output() {
throw new Error('Logger subclass must implement output()');
}
// 便捷方法
info(msg, ...args) { this.log('info', msg, ...args); }
warn(msg, ...args) { this.log('warn', msg, ...args); }
error(msg, ...args) { this.log('error', msg, ...args); }
}
</pre>
</font>
<H3>ConsoleLogger:继承并实现控制台输出</H3>
<p>最常见子类,把日志输出到浏览器控制台或 Node.js 的 <code>console</code>,支持不同级别调用原生方法:</p>
<font color="#666">
<pre>
class ConsoleLogger extends Logger {
constructor(name = 'ConsoleLogger') {
super(name);
}
output(formatted, level, ...args) {
const method = level === 'warn' ? 'warn' : level === 'error' ? 'error' : 'log';
console[method](formatted, ...args); // 保留额外参数(如对象、样式)
}
}
// 使用示例
const logger = new ConsoleLogger('API');
logger.info('Request started', { url: '/users' });
// → [2024-05-20T10:30:00.123Z] [INFO] [API] Request started { url: '/users' }
</pre>
</font>
<H3>FileLogger:扩展为文件写入(Node.js 环境)</H3>
<p>在 Node.js 中继承 <code>Logger</code>,复写 <code>format</code> 增加毫秒和进程 ID,并用 <code>fs.appendFile</code> 写入文件:</p>
<font color="#666">
<pre>
const fs = require('fs').promises;
class FileLogger extends Logger {
constructor(name = 'FileLogger', filePath = './app.log') {
super(name);
this.filePath = filePath;
}
// 增强格式:带毫秒和 PID
format(level, message, ...args) {
const now = new Date();
const time = `${now.toISOString().slice(0, -1)}:${String(now.getMilliseconds()).padStart(3, '0')}Z`;
const pid = process.pid;
return `[${time}] [${level.toUpperCase()}] [${pid}] [${this.name}] ${message}`;
}
async output(formatted, level, ...args) {
try {
await fs.appendFile(this.filePath, formatted + '\n');
// 可选:错误级别同时输出到 console.error
if (level === 'error') console.error(formatted, ...args);
} catch (err) {
console.error('[FileLogger] Failed to write log:', err.message);
}
}
}
</pre>
</font>
<H3>CompositeLogger:组合多个日志器(多目标输出)</H3>
<p>不直接继承 <code>Logger</code>,而是聚合多个 <code>Logger</code> 实例,实现“一份日志,多端输出”,体现面向对象的组合优于继承原则:</p>
<font color="#666">
<pre>
class CompositeLogger extends Logger {
constructor(name = 'CompositeLogger', loggers = []) {
super(name);
this.loggers = Array.isArray(loggers) ? loggers : [loggers];
}
output(formatted, level, ...args) {
this.loggers.forEach(logger => {
if (typeof logger.log === 'function') {
logger.log(level, formatted, ...args);
}
});
}
add(logger) {
if (logger instanceof Logger) this.loggers.push(logger);
}
}
// 使用:同时输出到控制台和文件
const consoleLog = new ConsoleLogger('Main');
const fileLog = new FileLogger('Main', './main.log');
const composite = new CompositeLogger('App', [consoleLog, fileLog]);
composite.info('App started');
</pre>
</font>