Log4j 2 rollover 過程源碼分析

前言

近日工作中發(fā)現(xiàn)應用容器的磁盤空間打滿,執(zhí)行完日志清理后磁盤空間依然無法釋放。懷疑有文件只是釋放了鏈接,但是沒有實際刪除。排查后發(fā)現(xiàn),應用容器上有個日志訂閱工具,導致log4j日志文件沒有辦法真正執(zhí)行刪除,解除訂閱之后,空間得到釋放。
通過出現(xiàn)的現(xiàn)象可以猜測,在寫日志達到觸發(fā)歸檔條件時會對日志文件執(zhí)行了刪除操作。由此對log4j2日志歸檔的源碼產(chǎn)生了興趣,所以閱讀源碼,寫下此筆記。

RollingFileAppender

RollingFileAppender 負責寫入日志到指定文件,并根據(jù)TriggeringPolicyRolloverPolicy滾動文件。所以我們從它的源碼入手。關鍵代碼如下:

//Writes the log entry rolling over the file when required.
 @Override
    public void append(final LogEvent event) {
        //我的注釋:檢查并觸發(fā)Rollover
        getManager().checkRollover(event);
        super.append(event);
    }

可以看出,在append()執(zhí)行第一步就是檢查是否需要rollover。這個行為是通過RollingFileManager執(zhí)行的。下面讓我們看下它的代碼。

RollingFileManager

/**
     * Determines if a rollover should occur.
     * @param event The LogEvent.
     */
    public synchronized void checkRollover(final LogEvent event) {
        //我的注釋:符合條件則執(zhí)行rollover
        if (triggeringPolicy.isTriggeringEvent(event)) {
            rollover();
        }
    }

符合條件,則執(zhí)行rollover。至于如何判斷的細節(jié)我們暫不關注,目前只需要了解這取決于我們配置的TriggeringPolicies。其中常見的就是:TimeBasedTriggeringPolicySizeBasedTriggeringPolicy。

下面讓我進入 rollover()

public synchronized void rollover() {
        if (!hasOutputStream()) {
            return;
        }
        if (rollover(rolloverStrategy)) {
            try {
                size = 0;
                initialTime = System.currentTimeMillis();
                createFileAfterRollover();
            } catch (final IOException e) {
                logError("Failed to create file after rollover", e);
            }
        }
    }

private boolean rollover(final RolloverStrategy strategy) {

        boolean releaseRequired = false;
        try {
            // Block until the asynchronous operation is completed.
            semaphore.acquire();
            releaseRequired = true;
        } catch (final InterruptedException e) {
            logError("Thread interrupted while attempting to check rollover", e);
            return false;
        }

        boolean success = true;

        try {
            //我的注釋:
            final RolloverDescription descriptor = strategy.rollover(this);
            if (descriptor != null) {
                writeFooter();
                closeOutputStream();
                if (descriptor.getSynchronous() != null) {
                    LOGGER.debug("RollingFileManager executing synchronous {}", descriptor.getSynchronous());
                    try {
                        success = descriptor.getSynchronous().execute();
                    } catch (final Exception ex) {
                        success = false;
                        logError("Caught error in synchronous task", ex);
                    }
                }

                if (success && descriptor.getAsynchronous() != null) {
                    LOGGER.debug("RollingFileManager executing async {}", descriptor.getAsynchronous());
                    asyncExecutor.execute(new AsyncAction(descriptor.getAsynchronous(), this));
                    releaseRequired = false;
                }
                return true;
            }
            return false;
        } finally {
            if (releaseRequired) {
                semaphore.release();
            }
        }
    }

rollover()總結(jié)一下:

  • 獲取鎖
  • 獲取RolloverDescription。descriptor中包含了一系列rollover需要執(zhí)行的行為。它是通過strategy.rollover創(chuàng)建的。這里的strategy我們以DefaultRolloverStrategy的實現(xiàn)為例往下分析。
  • 寫頁腳,writeFooter()
  • 關閉寫入流,closeOutputStream()。
  • 執(zhí)行descriptor中的一系列操作。
  • 返回并釋放鎖。

所以關鍵就在于strategy.rollover()定義了那些行為。似乎離真相越來越近了,讓我們繼續(xù)。

DefaultRolloverStrategy

注意:這里我們使用DefaultRolloverStrategy 來分析,這也是默認和最常用的。

直接上代碼,礦就都在這塊了 :

/**
     * Performs the rollover.
     *
     * @param manager The RollingFileManager name for current active log file.
     * @return A RolloverDescription.
     * @throws SecurityException if an error occurs.
     */
    @Override
    public RolloverDescription rollover(final RollingFileManager manager) throws SecurityException {
        //我的注釋:算fileIndex,為歸檔文件命名作準備。
        int fileIndex;
        if (minIndex == Integer.MIN_VALUE) {
            final SortedMap<Integer, Path> eligibleFiles = getEligibleFiles(manager);
            fileIndex = eligibleFiles.size() > 0 ? eligibleFiles.lastKey() + 1 : 1;
        } else {
            if (maxIndex < 0) {
                return null;
            }
            final long startNanos = System.nanoTime();
            fileIndex = purge(minIndex, maxIndex, manager);
            if (fileIndex < 0) {
                return null;
            }
            if (LOGGER.isTraceEnabled()) {
                final double durationMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);
                LOGGER.trace("DefaultRolloverStrategy.purge() took {} milliseconds", durationMillis);
            }
        }
        //我的注釋:拼裝歸檔文件名
        final StringBuilder buf = new StringBuilder(255);
        manager.getPatternProcessor().formatFileName(strSubstitutor, buf, fileIndex);
        final String currentFileName = manager.getFileName();

        String renameTo = buf.toString();
        final String compressedName = renameTo;
        Action compressAction = null;
        //我的注釋:獲取歸檔壓縮文件擴展名類的實例,并依據(jù)不同的壓縮文件類型創(chuàng)建壓縮行為。
        final FileExtension fileExtension = manager.getFileExtension();
        if (fileExtension != null) {
            final File renameToFile = new File(renameTo);
            renameTo = renameTo.substring(0, renameTo.length() - fileExtension.length());
            if (tempCompressedFilePattern != null) {
                buf.delete(0, buf.length());
                tempCompressedFilePattern.formatFileName(strSubstitutor, buf, fileIndex);
                final String tmpCompressedName = buf.toString();
                final File tmpCompressedNameFile = new File(tmpCompressedName);
                final File parentFile = tmpCompressedNameFile.getParentFile();
                if (parentFile != null) {
                    parentFile.mkdirs();
                }
                compressAction = new CompositeAction(
                        Arrays.asList(fileExtension.createCompressAction(renameTo, tmpCompressedName,
                                true, compressionLevel),
                                new FileRenameAction(tmpCompressedNameFile,
                                        renameToFile, true)),
                        true);
            } else {
                compressAction = fileExtension.createCompressAction(renameTo, compressedName,
                        true, compressionLevel);
            }
        }

        if (currentFileName.equals(renameTo)) {
            LOGGER.warn("Attempt to rename file {} to itself will be ignored", currentFileName);
            return new RolloverDescriptionImpl(currentFileName, false, null, null);
        }

        if (compressAction != null && manager.isAttributeViewEnabled()) {
            // Propagate posix attribute view to compressed file
            // @formatter:off
            final Action posixAttributeViewAction = PosixViewAttributeAction.newBuilder()
                                                        .withBasePath(compressedName)
                                                        .withFollowLinks(false)
                                                        .withMaxDepth(1)
                                                        .withPathConditions(new PathCondition[0])
                                                        .withSubst(getStrSubstitutor())
                                                        .withFilePermissions(manager.getFilePermissions())
                                                        .withFileOwner(manager.getFileOwner())
                                                        .withFileGroup(manager.getFileGroup())
                                                        .build();
            // @formatter:on
            compressAction = new CompositeAction(Arrays.asList(compressAction, posixAttributeViewAction), false);
        }
        //我的注釋:創(chuàng)建文件重命名行為
        final FileRenameAction renameAction = new FileRenameAction(new File(currentFileName), new File(renameTo),
                    manager.isRenameEmptyFiles());
        //我的注釋:合并壓縮行為和文件重命名行為,創(chuàng)建RolloverDescription并返回
        final Action asyncAction = merge(compressAction, customActions, stopCustomActionsOnError);
        return new RolloverDescriptionImpl(currentFileName, false, renameAction, asyncAction);
    }

總結(jié)一下:

  • 計算歸檔文件的文件名。
  • 根據(jù)歸檔文件擴展類型,創(chuàng)建壓縮行為 fileExtension.createCompressAction()。FileExtension是一組枚舉,包含支持的文件壓縮類型及壓縮行為。
  • 判斷是否需要 AttributeView,并添加posixAttributeViewAction。這里我們不關心,也就不問了。
  • 創(chuàng)建文件重命名行為renameAction。FileRenameAction.excute()代碼在這就不貼了,就是復制文件到指定位置。
  • 合并所有歸檔需要的行為,并創(chuàng)建RolloverDescription實例,最后返回。

總結(jié)

至此,log4j2 的歸檔實現(xiàn)主要流程,我們大致了解了。文字描述一下就是:

  1. 停止寫入原日志文件。
  2. 復制源文件到指定位置。
  3. 執(zhí)行壓縮。
  4. 創(chuàng)建新的日志文件,繼續(xù)寫入。

參考:
Log4j – Log4j 2 Appenders
log4j2 版本:log4j-core-2.9.1

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 本文主要內(nèi)容: 1. 概述 我們?yōu)榱朔治龀绦虻膱?zhí)行情況,需要把我們關心的一些調(diào)試信息、錯誤信息等保存到日志中,Lo...
    morninz閱讀 6,138評論 0 4
  • WinRAR - 最新版本的更新 版本 5.50 1. WinRAR 和命令行 RAR 默認使用 RAR ...
    王舒璇閱讀 2,509評論 0 2
  • ORA-00001: 違反唯一約束條件 (.) 錯誤說明:當在唯一索引所對應的列上鍵入重復值時,會觸發(fā)此異常。 O...
    我想起個好名字閱讀 5,943評論 0 9
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,629評論 1 32
  • 1.常見的日志框架以及選擇日志門面:JCL(springboot不用這個)、SLF4J、jboss-logging...
    noobBird閱讀 358評論 0 0

友情鏈接更多精彩內(nèi)容