资讯专栏INFORMATION COLUMN

Log42j 源代码分析:日志回滚

libin19890520 / 1158人阅读

摘要:前言一般都会对应用程序日志做回滚处理,本文简要分析日志回滚实现触发策略使用接口来抽象日志回滚触发策略,使用了设计模式方法用于初始化策略,方法用于判断是否需要回滚,接口的不同实现类对应不同的策略组合模式,聚合不同的策略类基于时间的回滚策略基于

前言

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66823.html

相关文章

  • Log42j 代码分析:plugin(插件)机制

    摘要:前言使用插件机制加载各种组件,本文简要分析插件机制实现注解注解提供了一种便捷的方法将一个类声明成的插件,比如,单例类用来保存插件信息,暴露了一些方法从配置文件中加载内置插件,使用了单例设计模式线程安全的数据结构使用了一些多线程编程的最佳实践 前言 log4j2 使用插件机制加载各种组件:appender, logger .etc,本文简要分析 log4j2 插件机制实现 Plugin ...

    learning 评论0 收藏0
  • 分布式事务中间件Seata的设计原理

    摘要:如上图所示,的实际上是已中间件的形式放在应用层,不用依赖数据库对协议的支持,完全剥离了分布式事务方案对数据库在协议支持上的要求。 微信公众号「后端进阶」,专注后端技术分享:Java、Golang、WEB框架、分布式中间件、服务治理等等。 在微服务架构体系下,我们可以按照业务模块分层设计,单独部署,减轻了服务部署压力,也解耦了业务的耦合,避免了应用逐渐变成一个庞然怪物,从而可以轻松扩展,...

    Kylin_Mountain 评论0 收藏0
  • Python模块分析:第4节-logging日志模块

    摘要:上一篇文章模块分析第节模块一日志记录的级别优先级,记录调试的详细信息,只在调试时开启优先级,记录普通的消息,报告错误和警告等待。监听端口号上一篇文章模块分析第节模块 上一篇文章:Python模块分析:第3节-typing模块 一、日志记录的级别 debug:优先级10,记录调试的详细信息,只在调试时开启 info:优先级20,记录普通的消息,报告错误和警告等待。 warning:优...

    MartinHan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<