Log42j 源代码分析:日志回滚

xingpingz

前言

一般都会对应用程序日志做回滚处理,本文简要分析 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();
            }
        }
    }

总结

阅读 2.2k

signal
随笔

博学,审问,慎思,明辨,力行

119 声望
62 粉丝
0 条评论
你知道吗?

博学,审问,慎思,明辨,力行

119 声望
62 粉丝
宣传栏