首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Log4j 定時滾動日誌與模塊名標識實戰

     編輯:Java 相關知識

需求背景

項目需要:

  1. 按分鐘級間隔滾動日誌文件(如每 2 分鐘生成新文件)。
  2. 日誌行自動攜帶模塊名(如 ORDER|用戶下單成功),便於多模塊混合部署時快速篩選。

方案一:繼承 DailyRollingFileAppender 實現分鐘級滾動

DailyRollingFileAppender 基於 DatePattern 滾動,最小粒度為分鐘(yyyy-MM-dd-HH-mm),但間隔不可配。通過覆寫 RollingCalendar 實現可配間隔:

核心類:MinuteRollingAppender

public class MinuteRollingAppender extends FileAppender {
    private static final String DATE_PATTERN = "'.'yyyy-MM-dd-HH-mm'.log'";
    private int intervalMinutes = 10; // 可配間隔,默認 10 分鐘

    private final RollingCalendar calendar = new RollingCalendar();
    private String scheduledFilename;

    public void setIntervalMinutes(int intervalMinutes) { this.intervalMinutes = intervalMinutes; }

    @Override
    public void activateOptions() {
        super.activateOptions();
        calendar.setIntervalMinutes(intervalMinutes);
        scheduledFilename = fileName + new SimpleDateFormat(DATE_PATTERN).format(new Date());
    }

    @Override
    protected void subAppend(LoggingEvent event) {
        long now = System.currentTimeMillis();
        if (now >= calendar.getNextCheckMillis()) {
            rollOver();
            calendar.updateNextCheckMillis(now);
        }
        super.subAppend(event);
    }

    void rollOver() {
        // 關閉當前文件、重命名、重新打開...
    }

    // 內部類 RollingCalendar 維護下次滾動時間點
    class RollingCalendar extends GregorianCalendar {
        private int intervalMinutes = 10;
        public void setIntervalMinutes(int m) { intervalMinutes = m; }
        public long getNextCheckMillis() { return getNextCheckDate().getTime(); }
        public Date getNextCheckDate() {
            setTime(new Date());
            set(Calendar.SECOND, 0);
            set(Calendar.MILLISECOND, 0);
            add(Calendar.MINUTE, intervalMinutes);
            return getTime();
        }
    }
}

log4j.xml 配置

<appender name="minuteRoll" class="com.example.MinuteRollingAppender">
    <param name="File" value="logs/app.log"/>
    <param name="Append" value="true"/>
    <param name="IntervalMinutes" value="2"/> <!-- 每 2 分鐘滾動 -->
    <layout class="org.apache.log4j.PatternLayout">
        <param name="ConversionPattern" value="%p %d %c - %m%n"/>
    </layout>
</appender>

方案二:基於 ScheduledExecutorService 定時任務滾動(推薦)

避免在日誌寫入路徑上計算時間,改用獨立調度線程,性能更優、邏輯更清晰:

public class ScheduledRollingAppender extends FileAppender {
    private static final String DATE_PATTERN = "'.'yyyy-MM-dd-HH-mm'.log'";
    private int intervalMinutes = 10;
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
    private final SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN);

    public void setIntervalMinutes(int m) { this.intervalMinutes = m; }

    @Override
    public void activateOptions() {
        super.activateOptions();
        scheduler.scheduleAtFixedRate(this::doRollOver, intervalMinutes, intervalMinutes, TimeUnit.MINUTES);
    }

    private synchronized void doRollOver() {
        String dated = fileName + sdf.format(new Date());
        closeFile();
        File target = new File(dated);
        if (target.exists()) target.delete();
        boolean ok = new File(fileName).renameTo(target);
        if (!ok) LogLog.error("Rename failed: " + fileName);
        try { setFile(fileName, true, bufferedIO, bufferSize); }
        catch (IOException e) { errorHandler.error("Reopen failed", e); }
    }

    @Override
    public void close() {
        scheduler.shutdown();
        super.close();
    }
}

並發安全:doRollOver 加 synchronized,防止滾動與寫入競爭。生產環境建議使用 ReentrantReadWriteLock 進一步優化。

模塊名標識:自定義 Appender 注入上下文

繼承 RollingFileAppender,覆寫 subAppend 向日誌事件注入模塊名:

public class ModuleAppender extends RollingFileAppender {
    private String moduleName; // 配置文件注入

    public void setModuleName(String moduleName) { this.moduleName = moduleName; }

    @Override
    public void subAppend(LoggingEvent event) {
        if (moduleName != null) {
            String original = event.getRenderedMessage();
            String enriched = moduleName + "|" + original;
            // 創建新事件攜帶豐富消息
            LoggingEvent newEvent = new LoggingEvent(
                event.getFQNOfLoggerClass(),
                event.getLogger(),
                event.getTimeStamp(),
                event.getLevel(),
                enriched,
                event.getThreadName(),
                event.getThrowableInformation(),
                event.getNDC(),
                event.getLocationInformation(),
                event.getProperties()
            );
            super.subAppend(newEvent);
        } else {
            super.subAppend(event);
        }
    }
}

配置示例

<appender name="orderLog" class="com.example.ModuleAppender">
    <param name="File" value="logs/order.log"/>
    <param name="ModuleName" value="ORDER"/>
    <param name="MaxFileSize" value="100MB"/>
    <param name="MaxBackupIndex" value="10"/>
    <layout class="org.apache.log4j.PatternLayout">
        <param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n"/>
    </layout>
</appender>

<logger name="com.example.order">
    <appender-ref ref="orderLog"/>
</logger>

日誌輸出效果:

2024-01-15 10:30:45 [http-nio-8080-exec-1] INFO  OrderService - ORDER|用戶 1001 下單成功,訂單號 20240115001

生產環境避坑指南

問題原因解決
滾動時丟失日誌rename 與寫入競爭synchronized / ReadWriteLock 保護滾動關鍵區
定時任務堆積Timer 單線程,任務耗時導致延遲改用 ScheduledExecutorService 線程池
磁盤寫滿無大小/個數限制配置 MaxFileSize、MaxBackupIndex
模塊名未生效Logger 未綁定對應 Appenderlogger 配置 additivity="false" 並指定 appender-ref

現代化替代方案

Log4j 1.x 已停止維護,強烈建議遷移至 Logback(原生支持 TimeBasedRollingPolicy 分鐘級滾動、SiftingAppender 動態分模塊文件)或 Log4j2(異步日誌、GC-free、插件化架構)。Logback 配置片段:

<appender name="MINUTE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>logs/app.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>logs/app.%d{yyyy-MM-dd_HH-mm}.log</fileNamePattern>
        <maxHistory>720</maxHistory> <!-- 保留 720 個分鐘 = 5 天 -->
    </rollingPolicy>
    <encoder><pattern>%d %p %c - %m%n</pattern></encoder>
</appender>
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved