log4j2 RollingFileAppender 帶zip 源碼分析

背景:
線上出現復現率很小的問題,log4j2帶SizeBasedTriggeringPolicy和CronTriggeringPolicy兩種策略的自定義log,出現覆蓋現象
經分析,不是觸發size 2MB的問題,是cron發生的問題


log文件 分析
最重要的是獲取

1.被壓縮文件的創建時間
2.zip文件的lastmodify時間

因爲是用創建時間+5分來命名zip文件的 ★


源碼分析
每次log.info 最後都會調入RollingFileAppender   就是log.info都會判斷是否要rollover,crontrigger是到時間了判斷,size是每打印一句就會判斷

    public void append(LogEvent event) {
        ((RollingFileManager)this.getManager()).checkRollover(event);
        super.append(event);
    }
然後經過這一步根據上面的兩個策略判斷是否需要rollover 

this.triggeringPolicy = {CompositeTriggeringPolicy@4082} 
"CompositeTriggeringPolicy(policies=[SizeBasedTriggeringPolicy(size=1024), CronTriggeringPolicy(schedule=0 0/1 * * * ?)])"
 triggeringPolicies = {TriggeringPolicy[2]@4312} 
  0 = {SizeBasedTriggeringPolicy@4316} "SizeBasedTriggeringPolicy(size=1024)"
  1 = {CronTriggeringPolicy@4317} "CronTriggeringPolicy(schedule=0 0/1 * * * ?)"
 state = {LifeCycle$State@4119} "STARTED"
  name = "STARTED"
  ordinal = 3

public synchronized void checkRollover(LogEvent event) {   //event 將所有的log的信息都封裝進去
        if (this.triggeringPolicy.isTriggeringEvent(event)) {
            this.rollover();
        }
    }
在這裏的判斷中 cronPolicy直接返回false,因爲cron是直接在異步線程池裏操作的
  public boolean isTriggeringEvent(LogEvent event) {
        return false;
    }

/*
sizebasedPolicy 會根據文件大小來判斷是否要更新manager的時間  然後在rollover()//這個策略沒有觸發,不用管
   public boolean isTriggeringEvent(LogEvent event) {
        boolean triggered = this.manager.getFileSize() > this.maxFileSize;
        if (triggered) {
            this.manager.getPatternProcessor().updateTime();
        }

        return triggered;
    }
*/

然後執行manager的rollover()邏輯 ★這裏是主要邏輯

public synchronized void rollover() {
        if (this.hasOutputStream()) {
            if (this.rollover(this.rolloverStrategy)) {  ★這裏move()
                try {
                    this.size = 0L;
                    this.initialTime = System.currentTimeMillis();
                    this.createFileAfterRollover();  ★這裏create()
                } catch (IOException var2) {
                    this.logError("Failed to create file after rollover", var2);
                }
            }

        }
    }
    
//這裏封裝了一個帶倆action的description  rename和zipAction 都是cron衍生出來的
public RolloverDescription rollover(final RollingFileManager manager) throws SecurityException {

rename:
writeFooter();  不知道寫的啥,暫時先不管...
success = descriptor.getSynchronous().execute();//同步的rename 直接改  aaa(文件名) move到對應的位置


zip:
if (success && descriptor.getAsynchronous() != null) {//異步的zip  coreSize爲0 maxeSize爲INTEGER_MAX的線程池,執行將上步remove過來的文件zip的操作
                    LOGGER.debug("RollingFileManager executing async {}", descriptor.getAsynchronous());
                    asyncExecutor.execute(new AsyncAction(descriptor.getAsynchronous(), this));
                    releaseRequired = false;
                }


aaalog是源文件
aaa是生成的zip文件和中間文件
                
synchronous = {FileRenameAction@16811} "FileRenameAction[D:\app1\files3\aaalog.txt to D:\app1\files3\20191102\aaa, renameEmptyFiles=false]"
asynchronous = {ZipCompressAction@16179} "ZipCompressAction[D:\app1\files3\20191102\aaa to D:\app1\files3\20191102\aaa.zip, level=9, deleteSource=true]"


source = {File@19877} "D:\app1\files3\aaalog.txt"
destination = {File@19878} "D:\app1\files3\20191102\aaa"

重命名其實就是move

public static boolean execute(final File source, final File destination, final boolean renameEmptyFiles) {
 Files.move(Paths.get(source.getAbsolutePath()), Paths.get(destination.getAbsolutePath()),//★這裏是真實的move操作
                            StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
                    LOGGER.trace("Renamed file {} to {} with Files.move", source.getAbsolutePath(),
                            destination.getAbsolutePath());
                    return true;

try {
                source.delete();            //定時跑的時候,如果aaalog爲空,就直接刪除
            } catch (final Exception exDelete) {
                LOGGER.error("Unable to delete empty file {}: {} {}", source.getAbsolutePath(),
                        exDelete.getClass().getName(), exDelete.getMessage());
            }


進入cron
RollingFileManager.initialize(); 
裏面有初始化cron線程池的操作
     public void initialize(final RollingFileManager aManager) {
            this.manager = aManager;
            final Date now = new Date();
            final Date lastRollForFile = cronExpression.getPrevFireTime(new Date(this.manager.getFileTime()));
            final Date lastRegularRoll = cronExpression.getPrevFireTime(new Date());
            aManager.getPatternProcessor().setCurrentFileTime(lastRegularRoll.getTime());
            LOGGER.debug("LastRollForFile {}, LastRegularRole {}", lastRollForFile, lastRegularRoll);
            aManager.getPatternProcessor().setPrevFileTime(lastRegularRoll.getTime());
            aManager.getPatternProcessor().setTimeBased(true);
            if (checkOnStartup && lastRollForFile != null && lastRegularRoll != null &&
                    lastRollForFile.before(lastRegularRoll)) {
                lastRollDate = lastRollForFile;
                rollover();
            }

            final ConfigurationScheduler scheduler = configuration.getScheduler();
            if (!scheduler.isExecutorServiceSet()) {
                // make sure we have a thread pool
                scheduler.incrementScheduledItems();
            }
            if (!scheduler.isStarted()) {
                scheduler.start();
            }
            lastRollDate = lastRegularRoll;
            future = scheduler.scheduleWithCron(cronExpression, now, new CronTrigger());
            LOGGER.debug(scheduler.toString());
        } 

crontriggerstage 會在初始化的時候 執行這一句
    future = scheduler.scheduleWithCron(cronExpression, now, new CronTrigger());

每次都會跑  跑的就是
 private void rollover() {
        manager.rollover(cronExpression.getPrevFireTime(new Date()), lastRollDate);
        if (future != null) {
            lastRollDate = future.getFireTime();
        }
    }

核心代碼  ★  爲啥跟上面一樣呢?因爲有個size策略 所以每次log.info都會執行,又有個cron策略,所以線程池也會執行

 if (rollover(rolloverStrategy)) {  這裏有個同步的action和異步的action
            try {
                size = 0;
                initialTime = System.currentTimeMillis();
                createFileAfterRollover();  //★這裏直接同步創建新的源文件
            } catch (final IOException e) {
                logError("Failed to create file after rollover", e);
            }
        }

//★ 同上,這裏創建文件
 @Override
    protected OutputStream createOutputStream() throws IOException {
        final String filename = getFileName();
        LOGGER.debug("Now writing to {} at {}", filename, new Date());
        final File file = new File(filename);
        final FileOutputStream fos = new FileOutputStream(file, isAppend);
        if (file.exists() && file.length() == 0) {
            try {
            // 而且重命名的時間和當前時間一致 把源文件的創建時間設置成當前時間
                FileTime now = FileTime.fromMillis(System.currentTimeMillis());
                Files.setAttribute(file.toPath(), "creationTime", now);
            } catch (Exception ex) {
                LOGGER.warn("Unable to set current file tiem for {}", filename);
            }
        }
        defineAttributeView(Paths.get(filename));
        return fos;
    }


cronpolicy 

manager.rollover(cronExpression.getPrevFireTime(new Date()), lastRollDate);
        if (future != null) {
            lastRollDate = future.getFireTime();
        }

分析文件創建,刪除,zip時間代碼如下


獲取descriptor  裏面倆操作 
一個同步的rename 一個異步的zip
rename是直接執行的  下面的代碼  這步驟是同步執行的,將 source move 到指定位置
 Files.move(Paths.get(source.getAbsolutePath()), Paths.get(destination.getAbsolutePath()),
                            StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
                    LOGGER.trace("Renamed file {} to {} with Files.move", source.getAbsolutePath(),
                            destination.getAbsolutePath());
                    return true;

source是怎麼重新出現的 因爲是定時任務 所以就是定時的時間 createFileAfterRollover() 創建的就是當時的時間
protected OutputStream createOutputStream() throws IOException {
        final String filename = getFileName();
        LOGGER.debug("Now writing to {} at {}", filename, new Date());
        final File file = new File(filename);
        final FileOutputStream fos = new FileOutputStream(file, isAppend);
        if (file.exists() && file.length() == 0) {
            try {
                FileTime now = FileTime.fromMillis(System.currentTimeMillis());
                Files.setAttribute(file.toPath(), "creationTime", now);
            } catch (Exception ex) {
                LOGGER.warn("Unable to set current file tiem for {}", filename);
            }
        }
        defineAttributeView(Paths.get(filename));
        return fos;
    }

又怎麼被刪除   只要爲空  每次定時任務掃到就會刪掉

又出現  同上


/* This executor pool will create a new Thread for every work async action to be performed. Using it allows
       us to make sure all the Threads are completed when the Manager is stopped. */
    private final ExecutorService asyncExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0, TimeUnit.MILLISECONDS,
            new EmptyQueue(), threadFactory);
            
            

一種合理的解釋:線程池裏新建了15分鐘 的zip任務,由於某種原因卡住了,卡到20才執行完,
此時20的已經創建好,且zip完了,就會覆蓋,至於是哪個覆蓋哪個就不知道了            

zip的創建時間
public static boolean execute(final File source, final File destination, final boolean deleteSource,
            final int level) throws IOException {
        if (source.exists()) {
            try (final FileInputStream fis = new FileInputStream(source);
                    final ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(destination))) {
                zos.setLevel(level);

                final ZipEntry zipEntry = new ZipEntry(source.getName());
                zos.putNextEntry(zipEntry);

                final byte[] inbuf = new byte[BUF_SIZE];
                int n;

                while ((n = fis.read(inbuf)) != -1) {//創建時間
                    zos.write(inbuf, 0, n);
                }
            }

            if (deleteSource && !source.delete()) {
                LOGGER.warn("Unable to delete " + source.toString() + '.');
            }

            return true;
        }

        return false;
    }


總結爲 定時move,同步create  異步 zip(modifytime)
我們的邏輯爲,如果 zip modifytime-createtime>5分 重命名文件+5分 文件本來名就是move  createtime 的名
如果<5分,就不重命名

正常情況下
move和createtime 是一致的  都是0/5這樣的分鐘數
modifytime 是異步的zip創建時間

異常情況見圖片吧,概率很低

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章