设计模式与日志系统:工厂模式创建Logger、装饰器增强日志、责任链处理日志
日志系统,说白了就是给程序装个黑匣子。我做了十几年项目,见过太多线上事故查起来像大海捞针——就是因为日志没设计好。今天咱们聊聊怎么用三种设计模式,搭一个既灵活又强大的日志框架。
一、为什么日志系统需要设计模式?
你想想看,一个日志系统要解决哪些问题?
- 创建问题:不同场景需要不同Logger(文件日志、控制台日志、远程日志)
- 增强问题:基础日志不够用,要加时间戳、线程信息、格式化输出
- 路由问题:不同级别的日志要去不同地方(DEBUG写文件,ERROR发邮件)
这三个问题,正好对应三种设计模式。我在项目中遇到过不少团队把日志代码写成一团乱麻,后来用这套组合拳,代码清晰多了。
核心思路:工厂模式管创建,装饰器模式管增强,责任链模式管分发。各司其职,互不干扰。
二、工厂模式:统一管理Logger创建
先看工厂模式。我个人习惯用简单工厂+反射,这样新增Logger类型不用改代码。
// Java 示例:Logger工厂
public class LoggerFactory {
private static Map<String, Class<? extends Logger>> registry = new HashMap<>();
static {
registry.put("file", FileLogger.class);
registry.put("console", ConsoleLogger.class);
registry.put("remote", RemoteLogger.class);
}
public static Logger createLogger(String type) {
try {
Class<? extends Logger> clazz = registry.get(type);
return clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("创建Logger失败: " + type);
}
}
}
// C++ 示例:Logger工厂
class LoggerFactory {
public:
static Logger* createLogger(const std::string& type) {
if (type == "file") return new FileLogger();
else if (type == "console") return new ConsoleLogger();
else if (type == "remote") return new RemoteLogger();
else return nullptr;
}
};
避坑指南:我曾经在工厂里直接new对象,结果每次加新Logger都要改工厂代码。后来改成注册表模式,新增类型只需注册类名,工厂完全不用动。
三、装饰器模式:给日志动态加功能
基础Logger只能输出原始消息。但实际项目中,我们需要时间戳、线程ID、调用位置等信息。装饰器模式就是干这个的。
// Java 示例:装饰器模式
public interface Logger {
void log(String message);
}
public class TimestampDecorator implements Logger {
private Logger wrapped;
public TimestampDecorator(Logger logger) {
this.wrapped = logger;
}
@Override
public void log(String message) {
String timestamp = LocalDateTime.now().format(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
wrapped.log("[" + timestamp + "] " + message);
}
}
public class ThreadDecorator implements Logger {
private Logger wrapped;
public ThreadDecorator(Logger logger) {
this.wrapped = logger;
}
@Override
public void log(String message) {
wrapped.log("[Thread-" + Thread.currentThread().getId() + "] " + message);
}
}
// C++ 示例:装饰器模式
class TimestampDecorator : public Logger {
private:
Logger* wrapped;
public:
TimestampDecorator(Logger* logger) : wrapped(logger) {}
void log(const std::string& message) override {
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
std::string ts = std::ctime(&time);
ts.pop_back(); // 去掉换行
wrapped->log("[" + ts + "] " + message);
}
};
使用的时候,可以像搭积木一样组合:
Logger logger = new ThreadDecorator(
new TimestampDecorator(
LoggerFactory.createLogger("file")
)
);
logger.log("用户登录成功");
关键点:装饰器模式遵循开闭原则。想加新功能?写个新装饰器类就行,不用改现有代码。
四、责任链模式:日志分级处理
日志是有级别的:DEBUG、INFO、WARN、ERROR。不同级别应该走不同通道。责任链模式正好解决这个问题。
// Java 示例:责任链模式
public abstract class LogHandler {
protected LogHandler next;
protected LogLevel level;
public void setNext(LogHandler handler) {
this.next = handler;
}
public void handle(LogLevel level, String message) {
if (this.level.compareTo(level) <= 0) {
write(message);
}
if (next != null) {
next.handle(level, message);
}
}
protected abstract void write(String message);
}
public class ConsoleHandler extends LogHandler {
public ConsoleHandler() { this.level = LogLevel.DEBUG; }
protected void write(String message) {
System.out.println("[控制台] " + message);
}
}
public class FileHandler extends LogHandler {
public FileHandler() { this.level = LogLevel.INFO; }
protected void write(String message) {
// 写入文件
}
}
public class EmailHandler extends LogHandler {
public EmailHandler() { this.level = LogLevel.ERROR; }
protected void write(String message) {
// 发送邮件
}
}
// C++ 示例:责任链模式
class LogHandler {
protected:
LogHandler* next;
LogLevel level;
public:
void setNext(LogHandler* handler) { next = handler; }
void handle(LogLevel lvl, const std::string& msg) {
if (level <= lvl) write(msg);
if (next) next->handle(lvl, msg);
}
virtual void write(const std::string& msg) = 0;
};
组装责任链:
LogHandler console = new ConsoleHandler();
LogHandler file = new FileHandler();
LogHandler email = new EmailHandler();
console.setNext(file);
file.setNext(email);
// DEBUG级别:控制台输出
console.handle(LogLevel.DEBUG, "调试信息");
// ERROR级别:控制台 + 文件 + 邮件
console.handle(LogLevel.ERROR, "数据库连接失败");
注意:责任链的顺序很重要。我曾经把邮件处理器放在最前面,结果DEBUG日志也发邮件,测试邮箱直接炸了。正确的做法是:低级别在前,高级别在后。
五、三种模式组合实战
现在我们把三种模式串起来。完整的日志系统是这样工作的:
- 工厂模式:根据配置创建基础Logger(文件、控制台等)
- 装饰器模式:给Logger加上时间戳、线程信息等增强功能
- 责任链模式:根据日志级别决定输出到哪些通道
// 完整使用示例
public class LogSystem {
public static void main(String[] args) {
// 1. 工厂创建基础Logger
Logger baseLogger = LoggerFactory.createLogger("file");
// 2. 装饰器增强
Logger decorated = new TimestampDecorator(
new ThreadDecorator(baseLogger));
// 3. 责任链处理
LogHandler chain = buildChain(decorated);
// 4. 使用
chain.handle(LogLevel.INFO, "系统启动完成");
chain.handle(LogLevel.ERROR, "内存溢出");
}
private static LogHandler buildChain(Logger logger) {
ConsoleHandler console = new ConsoleHandler(logger);
FileHandler file = new FileHandler(logger);
EmailHandler email = new EmailHandler(logger);
console.setNext(file);
file.setNext(email);
return console;
}
}
我的经验:实际项目中,我会把配置外移到YAML文件。工厂根据配置创建Logger,装饰器根据配置决定加哪些功能,责任链根据配置决定路由规则。这样改日志行为只需要改配置,不用改代码。
六、整体架构图
下面这张图展示了三种模式如何协作:
七、总结与避坑
这三种模式组合起来,日志系统就变得非常灵活。我总结几个关键点:
| 模式 | 职责 | 常见坑 |
|---|---|---|
| 工厂模式 | 创建Logger实例 | 不要硬编码类型,用配置或注册表 |
| 装饰器模式 | 动态增强功能 | 注意装饰顺序,时间戳应该在内容最外层 |
| 责任链模式 | 分级路由处理 | 链的顺序决定优先级,低级别在前 |
性能提醒:装饰器链不要过长。我曾经见过有人加了七八层装饰器,每次日志调用都要走一大串方法,性能直接掉了一个数量级。一般2-3层就够了。
嗯,这套方案我在好几个项目里用过,从几十万QPS的高并发系统到嵌入式设备都能跑。关键是——代码结构清晰,后期维护起来特别省心。