Log42j 源代码分析:日志回滚
前言
一般都会对应用程序日志做回滚处理,本文简要分析 log4j2 日志回滚实现
触发策略
log4j2 使用 TriggeringPolity 接口来抽象日志回滚触发策略,使用了 Strategy + Compose 设计模式
public interface TriggeringPolicy { void initialize(final RollingFileManager manager); boolean isTriggeringEvent(final LogEvent event); }
initialize 方法用于初始化策略,isTriggeringEvent 方法用于判断是否需要回滚,TriggeringPolicy 接口的不同实现类对应不同的策略
// 组合模式,聚合不同的策略类 public final class CompositeTriggeringPolicy implements TriggeringPolicy { ... } // 基于时间的回滚策略 public final class TimeBasedTriggeringPolicy implements TriggeringPolicy { ... } // 基于文件大小的回滚策略 public final class SizeBasedTriggeringPolicy implements TriggeringPolicy { ... }
基于时间的触发策略
回滚策略
log4j2 使用 RolloverStrategy 接口抽象日志回滚策略
public interface RolloverStrategy { RolloverDescription rollover(final RollingFileManager manager) throws SecurityException; }
rollover 方法并不直接执行回滚操作,而是返回一个 RolloverDescription 接口,该接口用于获取日志回滚需要进行的操作: Action
public interface RolloverDescription { String getActiveFileName(); boolean getAppend(); Action getSynchronous(); Action getAsynchronous(); }
回滚动作
log4j2 使用 Action 接口抽象日志回滚过程中的一系列动作,使用了 Command + Compose 设计模式
public interface Action extends Runnable { boolean execute() throws IOException; void close(); boolean isComplete(); }
AbstractAction 类是 Action 接口的抽象实现,使用了 Method template 设计模式,子类通过 override execute 方法执行不同的动作
public synchronized void run() { if (!interrupted) { try { execute(); } catch (final IOException ex) { reportException(ex); } complete = true; interrupted = true; } } public abstract boolean execute() throws IOException;
文件重命名,FileRenameAction
文件删除,DeleteAction
文件压缩,GzCompressAction, ZipCompressAction
聚合,CompositeAction
回滚管理
log4j2 每个 Appender 都有一个 Manager 与之对应(多对一), RollingFileAppender 对应的 Manager 为RollingFileManager, 它管理着日志的写入,回滚 .etc,类层次结构
AbstractManager OutputStreamManager FileManager RollingFileManager
非常经典的 面向对象 设计,单一职责. AbstractManager 保存 Manager 基本信息,例如 name(名字),count(引用计数),并提供静态工厂方法根据名字获取 Manager,这个方法同样值得学习和借鉴
public static <M extends AbstractManager, T> M getManager(final String name, final ManagerFactory<M, T> factory, final T data) { // 获取锁 LOCK.lock(); try { @SuppressWarnings("unchecked") M manager = (M) MAP.get(name); if (manager == null) { // 使用工厂类创建具体的 Manager manager = factory.createManager(name, data); if (manager == null) { throw new IllegalStateException("ManagerFactory [" + factory + "] unable to create manager for [" + name + "] with data [" + data + "]"); } MAP.put(name, manager); } else { manager.updateData(data); } // 增加引用计数 manager.count++; return manager; } finally { // 释放锁 LOCK.unlock(); } }
RollingFileAppender 在 append LogEvent 时会先调用 RollingFileManager 的 checkRollover 方法尝试进行日志回滚,然后再调用父类的 append 方法,这种子类通过 override 方法 "拦截" 父类默认实现增加自己的处理逻辑的方法很常见
// RollingFileAppender.java @Override public void append(final LogEvent event) { getManager().checkRollover(event); super.append(event); }
RollingFileManager 的 checkRollover 方法使用上文提到的 触发策略类 TriggeringPolicy 判断是否符合触发条件,如果符合调用 rollover 方法
public synchronized void checkRollover(final LogEvent event) { if (triggeringPolicy.isTriggeringEvent(event)) { rollover(); } }
不带参数的 rollover 方法最终调用带 RolloverStrategy(回滚策略)类型参数的版本,为了代码显示更加紧凑特意省略掉了日志输出和异常处理逻辑,有几个地方值得品味
使用信号量进行同步,所以不要太频繁打 log 触发回滚,会 block 线程
同步 Action 在当前线程立即执行,异步 Action 则启动一个线程执行
如果异步 Action 很可执行完毕(某些极端情况),finally 语句块会释放 semaphore
private boolean rollover(final RolloverStrategy strategy) { semaphore.acquire(); boolean success = false; Thread thread = null; try { final RolloverDescription descriptor = strategy.rollover(this); if (descriptor != null) { writeFooter(); close(); if (descriptor.getSynchronous() != null) { success = descriptor.getSynchronous().execute(); } if (success && descriptor.getAsynchronous() != null) { thread = new Log4jThread(new AsyncAction( descriptor.getAsynchronous(), this)); thread.start(); } return true; } return false; } finally { if (thread == null || !thread.isAlive()) { semaphore.release(); } } }