目录

目录

# 一. 前言

1. 日志系列文章

2. 说明

二. log4j配置

1. log4j使用

1.1 依赖引入

1.2 添加配置文件

1.3 使用log4j打印日志

2. 常用配置示例(log4j.properties)

3. 配置说明

3.1 配置根logger 和 自定义logger

3.2 配置日志输出地appender

3.3 配置日志输出地的输出格式

3.4 其他常用配置

三. log4j源码

四. 拓展

1. 自定义DailyRollingFileAppender实现类, 自动删除过期日志



# 一. 前言

1. 日志系列文章

2. 说明

        本文使用log4j.properties展开讲解

二. log4j配置

1. log4j使用

1.1 依赖引入

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

1.2 添加配置文件

        log4j常用的配置文件有两种. 该文章后续都基于log4j.properties来讲解

  • log4j.xml
  • log4j.properties​​​

        其中, 两个文件并存时, 仅使用log4j.xml

        文件的配置信息见 [2. 常用配置示例]

1.3 使用log4j打印日志

package com.chenlongji.log4jstudy.controller;

import org.apache.log4j.Logger;

/**
 * @author clj
 */
public class LogTest {
    private static Logger logger = Logger.getLogger(LogTest.class);

    public static void main(String[] args) {
        // 注: 部分打印不出来, 那就是配置的日志级别太高
        logger.debug("这是debug级别的日志");
        logger.info("这是info级别的日志");
        logger.warn("这是warn级别的日志");
        logger.error("这是error级别的日志");
    }
}

2. 常用配置示例(log4j.properties)

        这里仅展示最常用的一些属性配置, 具体的配置请看 [3. 配置说明]

#是否打印log4j框架内的日志, 默认值为false
log4j.debug=false

# 配置根logger, 格式: [level],appenderName1,appenderName2,…
log4j.rootLogger=warn,console,errorDailyFile

# 配置自定义的logger, 格式: log4j.logger.[关注项目的包路径]
log4j.logger.com.chenlongji=info,console,infoRollingFile
# 打印时是否添加父类logger
log4j.additivity.com.chenlongji=false

#################
# 输出到控制台
#################
#console 控制台输出源
log4j.appender.console=org.apache.log4j.ConsoleAppender
# 输出日志的格式(布局)实现类
log4j.appender.console.layout=org.apache.log4j.PatternLayout
# 输出日志的具体格式
log4j.appender.console.layout.conversionPattern=%d [%t] %-5p [%c\:%L] %m%n
# 输出日志的级别 不填则认为是ALL级别
log4j.appender.console.threshold=info

#################
# 输出到文件(RollingFileAppender类型)
#################
# 滚动文件输出源, 文件大小到达指定尺寸的时候产生一个新的文件
log4j.appender.infoRollingFile=org.apache.log4j.RollingFileAppender
# 输出文件的路径 例如:./src/logs/clj2023.log
log4j.appender.infoRollingFile.file=D://logs/info.log
# 文件最大容量(到达后创建新的文件), 默认值为10MB. 这里后缀可以为KB,MB,GB
log4j.appender.infoRollingFile.maxFileSize=500MB
# 输出日志的格式(布局)实现类. 注: layout必须是小写
log4j.appender.infoRollingFile.layout=org.apache.log4j.PatternLayout
# 输出日志的具体格式
log4j.appender.infoRollingFile.layout.conversionPattern=%d [%t] %-5p [%c\:%L] %m%n
# 输出日志的级别 不填则理解为ALL级别
log4j.appender.infoRollingFile.threshold=info

#################
# 输出到文件(DailyRollingFileAppender类型)
#################
# 按天滚动生成日志文件输出源
log4j.appender.errorDailyFile=org.apache.log4j.DailyRollingFileAppender
# 输出文件的路径 例如:./src/logs/error.log
log4j.appender.errorDailyFile.file=D://logs/error.log
# 输出日志的格式(布局)实现类
log4j.appender.errorDailyFile.layout=org.apache.log4j.PatternLayout
# 输出日志的具体格式
log4j.appender.errorDailyFile.layout.conversionPattern=%d [%t] %-5p [%c\:%L] %m%n
# 最小分隔为分钟, 设置小于分钟也是按分钟滚动生成文件
log4j.appender.errorDailyFile.datePattern='.'yyyy-MM-dd'.log'
# 输出日志的级别 不填则理解为ALL级别
log4j.appender.errorDailyFile.threshold=warn

3. 配置说明

配置文件主要部分就三个

  • 配置根logger 和 自定义logger
  • 配置日志输出地appender
  • 配置日志输出地的输出格式

需要了解的知识点

  • log4j支持的日志级别为(从高到低), 常用ERROR, WARN, INFO, DEBUG
           级别          level值
    • OFF          (2147483647)
    • FATAL       (50000)
    • ERROR    (40000)
    • WARN      (30000)
    • INFO        (20000)
    • DEBUG    (10000)
    • TRACE    (5000)
    • ALL          (-2147483648)

注:

  • 本文章log4j配置文件中的key的单词首字母都使用小写, 虽然log4j会将大写首字母转换, 但log4j部分单词是写死的(如layout是写死小写的). 
    全单词首字母小写示例: log4j.appender.console.threshold

3.1 配置根logger 和 自定义logger

  • logger的配置示例
     
    # 根logger
    log4j.rootLogger=warn,console
    
    
    # 自定义的logger
    log4j.logger.com.chenlongji=info,console
  •  根logger和自定义logger的关系

           a. 按绑定关系先执行本身logger下的所有appender, 若当前logger的additivity=false则结束
           b. 再执行父类的所有appender, 若当前logger的additivity=false则结束
           ....
          直到执行完rootLogger

    • 关系: 二者都是Logger类型
    • 绑定关系: 类似链表结构, 自定义logger的最终父节点为rootLogger
      哑节点logger   com.chenlongji.log4jstudy.controller.LogTest
             -- parent节点logger  com.chenlongji
                       -- parent节点logger  root
    • 执行顺序

                先执行com.chenlongji.log4jstudy.controller.LogTest 该logger的所有appender(日志级别必须大于等于当前输出的级别才会打印日志), 若当前logger的additive=false, 结束

                再执行com.chenlongji 该logger的所有appender, 若当前logger的additive=false, 结束

                ...

                最后执行root logger所有appender

  •  配置logger
    上面讲了绑定和执行关系, 这里讲logger的配置. 
    • 根logger配置
      • 格式: 
        log4j.rootLogger=[level],appenderName1,appenderName2,…
      • 示例:
        log4j.rootLogger=warn,console
      • 说明: (value无值时不报错, 但是logger无意义, 不讨论)
        • level为日记级别, 必填. 可选值: debug,info,warn,error, 大小写都可以
          • 注: level不填也不会报错, 但第一个appenderName会被跳过, 且level=debug
        • appenderName为appender的名字, 必填. 必须和appender名字大小写相同(appender下面再讲)
    • 自定义节点logger配置
      • 格式:
        log4j.logger.[包路径]=[level],appenderName1,appenderName2,…
      • 示例: 
        log4j.logger.com.chenlongji=info,console
      • 说明: (value无值时不报错, 但是logger无意义, 不讨论) 
        • level为日记级别, 必填. 可选值: debug,info,warn,error, 大小写都可以
          • 注: level不填也不会报错, 但第一个appenderName会被跳过, 且level=debug
        • appenderName为appender的名字, 必填. 必须和appender名字大小写相同(appender下面再讲)
      • 属性additive:
        • 属性值格式 
          log4j.additivity.[自定义logger的包路径]=[boolean值]
        • 示例:
          log4j.additivity.com.chenlongji=true
        • 说明:
          • 打印时是否添加父类logger, 非必填. 默认值为true
          • 为true时, 打印完本logger后, 会找父类logger继续执行, 直到某个logger的additive属性为false或找完所有

3.2 配置日志输出地appender

  1. 配置的格式
    格式: log4j.appender.[appender名称]=[appender实现类的全限定名]
    示例:
    # 滚动文件输出源, 文件大小到达指定尺寸的时候产生一个新的文件
    log4j.appender.infoRollingFile=org.apache.log4j.RollingFileAppender
    # 输出文件的路径 例如:./src/logs/clj2023.log
    log4j.appender.infoRollingFile.file=D://logs/info.log
    # 文件最大容量(到达后创建新的文件), 默认值为10MB. 这里后缀可以为KB,MB,GB
    log4j.appender.infoRollingFile.maxFileSize=500MB
    # 输出日志的格式(布局)实现类. 注: layout必须是小写
    log4j.appender.infoRollingFile.layout=org.apache.log4j.PatternLayout
    # 输出日志的具体格式
    log4j.appender.infoRollingFile.layout.conversionPattern=%d [%t] %-5p [%c\:%L] %m%n
    # 输出日志的级别 不填则理解为ALL级别
    log4j.appender.infoRollingFile.threshold=info
  2. appender常见类型
    1. ConsoleAppender  输出到控制台
    2. RollingFileAppender  滚动输出文件
    3. DailyRollingFileAppender  按日期滚动输出文件
  3. ConsoleAppender配置
    1. 属性name
      1. 示例值: log4j.appender.myConsole=org.apache.log4j.ConsoleAppender
      2. 必填性: 必填
      3. 默认值: 无
      4. 说明: 给appender取的名字
    2. 属性target
      1. 示例值: log4j.appender.myConsole.target=System.out
      2. 必填性: 非必填
      3. 默认值: System.out
      4. 说明: 输出的方式. 可选System.out或System.err
    3. 属性immediateFlush
      1. 示例值: log4j.appender.myConsole.immediateFlush=true
      2. 必填性: 非必填
      3. 默认值: true
      4. 说明: 是否立刻输出
    4. 属性encoding
      1. 示例值: log4j.appender.myConsole.encoding=UTF-8
      2. 必填性: 非必填
      3. 默认值: 系统默认字符集
      4. 说明: 输出内容的编码格式
    5. 属性layout
      1. 示例值: log4j.appender.myConsole.layout=org.apache.log4j.PatternLayout
      2. 必填性: 必填
      3. 默认值: 无
      4. 说明: 输出日志的格式(布局)实现类, 下面3.3再详细讲解
    6. 属性layout.conversionPattern
      1. 示例值: log4j.appender.myConsole.layout.conversionPattern=%d [%t] %-5p [%c\:%L] %m%n
      2. 必填性: 必填
      3. 默认值: 无
      4. 说明: 输出日志的具体格式, 下面3.3再详细讲解
    7. 属性threshold
      1. 示例值: log4j.appender.myConsole.threshold=info
      2. 必填性: 非必填
      3. 默认值: all
      4. 说明: 本appender的日志级别
  4. RollingFileAppender配置
    相同属性请查看ConsoleAppender配置
    1. 属性name
      1. 示例值: log4j.appender.myRollingFile=org.apache.log4j.RollingFileAppender
    2. 属性immediateFlush
    3. 属性encoding
    4. 属性layout
    5. 属性layout.conversionPattern
    6. 属性threshold
    7. 属性fileName
      1. 示例值: log4j.appender.myRollingFile.file=D://logs/info.log
      2. 必填性: 必填
      3. 默认值: 无
      4. 说明: 输出的文件名
    8. 属性fileAppend
      1. 示例值: log4j.appender.myRollingFile.append=true
      2. 必填性: 非必填
      3. 默认值: true
      4. 说明: 是否在文件尾追加日志, false则覆盖原内容
    9. 属性bufferIO
      1. 示例值: log4j.appender.myRollingFile.bufferIO=false
      2. 必填性: 非必填
      3. 默认值: false
      4. 说明: 是否开启缓冲区, 该值与immediateFlush互斥, 开启时immediateFlush会置为false
    10. 属性bufferSize
      1. 示例值: log4j.appender.myRollingFile.bufferSize=1024
      2. 必填性: 非必填
      3. 默认值: 8*1024
      4. 说明: 缓冲区大小
    11. 属性maxFileSize
      1. 示例值: log4j.appender.myRollingFile.maxFileSize=500MB
      2. 必填性: 非必填
      3. 默认值: 10*1024*1024 (10MB)
      4. 说明: 文件最大容量(到达后创建新的文件), 默认值为10MB. 这里后缀可以为KB,MB,GB. 单位大小写都可以
    12. 属性maxBackupIndex
      1. 示例值: log4j.appender.myRollingFile.maxBackupIndex=2
      2. 必填性: 非必填
      3. 默认值: 1
      4. 说明: 滚动文件的最大数, 例如MaxBackupIndex=2, 可以生成info.log1, info.log2, info.log三个文件
  5. DailyRollingFileAppender配置
    相同属性请查看上面其他Appender配置
    1. 属性name
      1. log4j.appender.myDailyFile=org.apache.log4j.DailyRollingFileAppender
    2. 属性immediateFlush
    3. 属性encoding
    4. 属性layout
    5. 属性layout.conversionPattern
    6. 属性threshold
    7. 属性fileName
    8. 属性fileAppend
    9. 属性bufferIO
    10. 属性bufferSize
    11. 属性datePattern
      1. 示例值: log4j.appender.myDailyFile.datePattern='.'yyyy-MM-dd'.log'
      2. 必填性: 非必填
      3. 默认值: '.'yyyy-MM-dd
      4. 说明: 文件日期格式, 也是分隔粒度. 最小分隔为分钟, 设置小于分钟也是按分钟滚动生成文件
  6. 自定义appender实现类
    见拓展

3.3 配置日志输出地的输出格式

        日志的输出格式. 实现类有

  1. EnhancedPatternLayout (PatternLayout 的增强版本, 不展开讲, 感兴趣的自行看下源码)
  2. HTMLLayout (html的格式, 输出 耗时 + 线程名 + 日志内容 +ogger名 + 日志内容等 )
  3. PatternLayout (通过设置模式, 个性化指定要输出的内容)
  4. SimpleLayout (简易模式, 只输出 日志级别 + 日志内容)
  5. TTCCLayout (输出 耗时 + 线程名 + 日志级别 + logger名 + ndc + 日志内容)
  6. XMLLayout (xml的格式, 输出 logger名 + 时间 +日志级别 + 线程名 + ndc + 日志内容等 )

        下面讲下最常用的PatternLayout的使用

  • 配置格式
    • log4j.appender.[appenderName].layout=[layout实现类的全限定名]
    • log4j.appender.[appenderName].layout.conversionPattern=[自定义的模式]
  • 示例
    # 输出日志的格式(布局)实现类
    log4j.appender.console.layout=org.apache.log4j.PatternLayout
    # 输出日志的具体格式
    log4j.appender.console.layout.conversionPattern=%d [%t] %-5p [%c\:%L] %m%n
  • 模式参数说明
    • %c: 输出日志的logger名, 即Logger.getLogger中的名字或全限定类名
    • %C: 输出日志消息产生时所在类的全限定类名
    • %d: 输出日志的时间格式, 默认格式为ISO8601(yyyy-MM-dd HH:mm:ss,SSS). 可在后面指定格式, 例如: %d{yyyy-MM-dd HH:mm:ss}
    • %F: 输出日志消息产生时所在的类文件名 (示例: LogTest.java)
    • %l: 输出日志的详细位置. 相当于%c.%M(%F:%L的组合),  示例: com.chenlongji.log4jstudy.controller.LogTest.testRolling(LogTest.java:24)
    • %L: 输出日志时所在的行数
    • %m: 输出日志的具体内容
    • %M: 输出日志所在类的方法名 (示例: testRolling)
    • %n: 输出当前系统的换行符
    • %p: 输出日志的级别
    • %r: 输出自日志程序启动到输出当前日志的耗时数. 没啥意义
    • %t: 输出日志产生的线程名
    • %x: 输出和当前线程相关联的NDC(嵌套诊断环境), 只能存储一个值
    • %X: 输出映射调试上下文信息, (使用map, 可以存储多个值). 常用于将traceId传递到日志中, 进行日志上下文搜索. 使用示例:  tid=%X{traceId}, 花括号内表示要取出值的key
      使用说明: 使用MDC跟踪日志_mdc日志_livel_java的博客-CSDN博客
    • %%: 输出一个%号
    • [num]: 阿拉伯数字, 表示输出的长度
      • 示例 %5p, 表示输出日志级别, 占位为5, 多余长度补空格, 默认是右对齐
    • -[num]: 左对齐, 实际长度不足补空格, 实际长度超出也全显示
    • .[num]: 实际长度大于num会截掉左侧部分

3.4 其他常用配置

#是否打印log4j框架内的日志, 默认值为false
#log4j.debug=true

# 全局日志级别, 默认值为all, 设置后低于该级别的日志都不能输出
#log4j.threshold=info

三. log4j源码

        见文章 [log4j源码浅析]

四. 拓展

1. 自定义DailyRollingFileAppender实现类, 自动删除过期日志

步骤:

  1. 复制org.apache.log4j.DailyRollingFileAppender类, 创建MyDailyRollingFileAppender类
  2. 给新增的类添加 最大备份文件数属性maxBackupIndex, 并为其添加get和set方法
  3. 在rollOver() 方法中的 boolean result = file.renameTo(target); 之后添加移除过期文件逻辑方法removeFiles(File file)
  4. 实现removeFiles(File file) 方法
  5. 在配置文件中设置appender实现类 和 maxBackupIndex属性
    log4j.appender.myDailyFile.maxBackupIndex=5
    

修改点示例:

 

 

完整MyDailyRollingFileAppender代码:

package com.chenlongji.log4jstudy;

import org.apache.log4j.FileAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggingEvent;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.nio.file.Files;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author clj
 */
public class MyDailyRollingFileAppender extends FileAppender {


  // The code assumes that the following constants are in a increasing
  // sequence.
  static final int TOP_OF_TROUBLE=-1;
  static final int TOP_OF_MINUTE = 0;
  static final int TOP_OF_HOUR   = 1;
  static final int HALF_DAY      = 2;
  static final int TOP_OF_DAY    = 3;
  static final int TOP_OF_WEEK   = 4;
  static final int TOP_OF_MONTH  = 5;

    /**
     * 最大备份文件数
     */
    private int maxBackupIndex = 60;


  /**
     The date pattern. By default, the pattern is set to
     "'.'yyyy-MM-dd" meaning daily rollover.
   */
  private String datePattern = "'.'yyyy-MM-dd";

  /**
     The log file will be renamed to the value of the
     scheduledFilename variable when the next interval is entered. For
     example, if the rollover period is one hour, the log file will be
     renamed to the value of "scheduledFilename" at the beginning of
     the next hour. 

     The precise time when a rollover occurs depends on logging
     activity. 
  */
  private String scheduledFilename;

  /**
     The next time we estimate a rollover should occur. */
  private long nextCheck = System.currentTimeMillis () - 1;

  Date now = new Date();

  SimpleDateFormat sdf;

  RollingCalendar rc = new RollingCalendar();

  int checkPeriod = TOP_OF_TROUBLE;

  // The gmtTimeZone is used only in computeCheckPeriod() method.
  static final TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT");


  /**
     The default constructor does nothing. */
  public MyDailyRollingFileAppender() {
  }

  /**
    Instantiate a <code>MyDailyRollingFileAppender</code> and open the
    file designated by <code>filename</code>. The opened filename will
    become the ouput destination for this appender.

    */
  public MyDailyRollingFileAppender(Layout layout, String filename,
                                    String datePattern) throws IOException {
    super(layout, filename, true);
    this.datePattern = datePattern;
    activateOptions();
  }

  /**
     The <b>DatePattern</b> takes a string in the same format as
     expected by {@link SimpleDateFormat}. This options determines the
     rollover schedule.
   */
  public void setDatePattern(String pattern) {
    datePattern = pattern;
  }

  /** Returns the value of the <b>DatePattern</b> option. */
  public String getDatePattern() {
    return datePattern;
  }

    /**
     Set the maximum number of backup files to keep around.
     <p>The <b>MaxBackupIndex</b> option determines how many backup
     files are kept before the oldest is erased. This option takes
     a positive integer value. If set to zero, then there will be no
     backup files and the log file will be truncated when it reaches
     <code>MaxFileSize</code>.
     */
    public void setMaxBackupIndex(int maxBackups) {
        this.maxBackupIndex = maxBackups;
    }

    public int getMaxBackupIndex(){
        return maxBackupIndex;
    }

  @Override
  public void activateOptions() {
    super.activateOptions();
    if(datePattern != null && fileName != null) {
      now.setTime(System.currentTimeMillis());
      sdf = new SimpleDateFormat(datePattern);
      int type = computeCheckPeriod();
      printPeriodicity(type);
      rc.setType(type);
      File file = new File(fileName);
      scheduledFilename = fileName+sdf.format(new Date(file.lastModified()));

    } else {
      LogLog.error("Either File or DatePattern options are not set for appender ["
		   +name+"].");
    }
  }

  void printPeriodicity(int type) {
    switch(type) {
    case TOP_OF_MINUTE:
      LogLog.debug("Appender ["+name+"] to be rolled every minute.");
      break;
    case TOP_OF_HOUR:
      LogLog.debug("Appender ["+name
		   +"] to be rolled on top of every hour.");
      break;
    case HALF_DAY:
      LogLog.debug("Appender ["+name
		   +"] to be rolled at midday and midnight.");
      break;
    case TOP_OF_DAY:
      LogLog.debug("Appender ["+name
		   +"] to be rolled at midnight.");
      break;
    case TOP_OF_WEEK:
      LogLog.debug("Appender ["+name
		   +"] to be rolled at start of week.");
      break;
    case TOP_OF_MONTH:
      LogLog.debug("Appender ["+name
		   +"] to be rolled at start of every month.");
      break;
    default:
      LogLog.warn("Unknown periodicity for appender ["+name+"].");
    }
  }


  // This method computes the roll over period by looping over the
  // periods, starting with the shortest, and stopping when the r0 is
  // different from from r1, where r0 is the epoch formatted according
  // the datePattern (supplied by the user) and r1 is the
  // epoch+nextMillis(i) formatted according to datePattern. All date
  // formatting is done in GMT and not local format because the test
  // logic is based on comparisons relative to 1970-01-01 00:00:00
  // GMT (the epoch).

  int computeCheckPeriod() {
    RollingCalendar rollingCalendar = new RollingCalendar(gmtTimeZone, Locale.getDefault());
    // set sate to 1970-01-01 00:00:00 GMT
    Date epoch = new Date(0);
    if(datePattern != null) {
      for(int i = TOP_OF_MINUTE; i <= TOP_OF_MONTH; i++) {
	SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern);
	simpleDateFormat.setTimeZone(gmtTimeZone); // do all date formatting in GMT
	String r0 = simpleDateFormat.format(epoch);
	rollingCalendar.setType(i);
	Date next = new Date(rollingCalendar.getNextCheckMillis(epoch));
	String r1 =  simpleDateFormat.format(next);
	//System.out.println("Type = "+i+", r0 = "+r0+", r1 = "+r1);
	if(r0 != null && r1 != null && !r0.equals(r1)) {
	  return i;
	}
      }
    }
    return TOP_OF_TROUBLE; // Deliberately head for trouble...
  }

  /**
     Rollover the current file to a new file.
  */
  void rollOver() throws IOException {

    /* Compute filename, but only if datePattern is specified */
    if (datePattern == null) {
      errorHandler.error("Missing DatePattern option in rollOver().");
      return;
    }

    String datedFilename = fileName+sdf.format(now);
    // It is too early to roll over because we are still within the
    // bounds of the current interval. Rollover will occur once the
    // next interval is reached.
    if (scheduledFilename.equals(datedFilename)) {
      return;
    }

    // close current file, and rename it to datedFilename
    this.closeFile();

    File target  = new File(scheduledFilename);
    if (target.exists()) {
      target.delete();
    }

    File file = new File(fileName);
    boolean result = file.renameTo(target);
    if(result) {
      LogLog.debug(fileName +" -> "+ scheduledFilename);
    } else {
      LogLog.error("Failed to rename ["+fileName+"] to ["+scheduledFilename+"].");
    }

    // 删除文件
    removeFiles(file);

    try {
      // This will also close the file. This is OK since multiple
      // close operations are safe.
      this.setFile(fileName, true, this.bufferedIO, this.bufferSize);
    }
    catch(IOException e) {
      errorHandler.error("setFile("+fileName+", true) call failed.");
    }
    scheduledFilename = datedFilename;
  }

    /**
   * This method differentiates MyDailyRollingFileAppender from its
   * super class.
   *
   * <p>Before actually logging, this method will check whether it is
   * time to do a rollover. If it is, it will schedule the next
   * rollover time and then rollover.
   * */
  @Override
  protected void subAppend(LoggingEvent event) {
    long n = System.currentTimeMillis();
    if (n >= nextCheck) {
      now.setTime(n);
      nextCheck = rc.getNextCheckMillis(now);
      try {
	rollOver();
      }
      catch(IOException ioe) {
          if (ioe instanceof InterruptedIOException) {
              Thread.currentThread().interrupt();
          }
	      LogLog.error("rollOver() failed.", ioe);
      }
    }
    super.subAppend(event);
   }

    /**
     * 删除文件
     */
    private void removeFiles(File file) {
        //获取日志文件列表,控制数量,实现清理策略
        try{
            if (file.getParentFile().exists()){
                File[] files = file.getParentFile().listFiles(new LogFileFilter(file.getName()));
                if (files == null) {
                    return;
                }
                Long[] dateArray = new Long[files.length];
                for (int i = 0; i < files.length; i++) {
                    File fileItem = files[i];
                    String fileDateStr = fileItem.getName().replace(file.getName(), "");
                    try {
                        Date fileDate = sdf.parse(fileDateStr);
                        long fileDateLong = fileDate.getTime();
                        dateArray[i] = fileDateLong;
                    } catch (ParseException e) {
                        LogLog.error("Parse File Date Throw Exception : " + e.getMessage());
                    }
                }
                Arrays.sort(dateArray);
                if (dateArray.length > maxBackupIndex) {
                    for (int i = 0; i < dateArray.length - maxBackupIndex; i++) {
                        String dateFileName = file.getPath() + sdf.format(dateArray[i]);
                        File dateFile = new File(dateFileName);
                        Files.delete(dateFile.toPath());
                    }
                }
            }
        } catch (Exception e){
            errorHandler.error("removeFiles failed." + e.getMessage());
        }
    }

    /**
     * 文件过滤器
     */
    class LogFileFilter implements FileFilter {
        private String logName;

        public LogFileFilter(String logName) {
            this.logName = logName;
        }

        @Override
        public boolean accept(File file) {
            if (logName == null || file.isDirectory()) {
                return false;
            } else {
                LogLog.debug(file.getName());
                return file.getName().startsWith(logName);
            }
        }
    }

}

/**
 *  RollingCalendar is a helper class to MyDailyRollingFileAppender.
 *  Given a periodicity type and the current time, it computes the
 *  start of the next interval.  
 * */
class RollingCalendar extends GregorianCalendar {
  private static final long serialVersionUID = -3560331770601814177L;

  int type = MyDailyRollingFileAppender.TOP_OF_TROUBLE;

  RollingCalendar() {
    super();
  }  

  RollingCalendar(TimeZone tz, Locale locale) {
    super(tz, locale);
  }  

  void setType(int type) {
    this.type = type;
  }

  public long getNextCheckMillis(Date now) {
    return getNextCheckDate(now).getTime();
  }

  public Date getNextCheckDate(Date now) {
    this.setTime(now);

    switch(type) {
    case MyDailyRollingFileAppender.TOP_OF_MINUTE:
	this.set(Calendar.SECOND, 0);
	this.set(Calendar.MILLISECOND, 0);
	this.add(Calendar.MINUTE, 1);
	break;
    case MyDailyRollingFileAppender.TOP_OF_HOUR:
	this.set(Calendar.MINUTE, 0);
	this.set(Calendar.SECOND, 0);
	this.set(Calendar.MILLISECOND, 0);
	this.add(Calendar.HOUR_OF_DAY, 1);
	break;
    case MyDailyRollingFileAppender.HALF_DAY:
	this.set(Calendar.MINUTE, 0);
	this.set(Calendar.SECOND, 0);
	this.set(Calendar.MILLISECOND, 0);
	int hour = get(Calendar.HOUR_OF_DAY);
	if(hour < 12) {
	  this.set(Calendar.HOUR_OF_DAY, 12);
	} else {
	  this.set(Calendar.HOUR_OF_DAY, 0);
	  this.add(Calendar.DAY_OF_MONTH, 1);
	}
	break;
    case MyDailyRollingFileAppender.TOP_OF_DAY:
	this.set(Calendar.HOUR_OF_DAY, 0);
	this.set(Calendar.MINUTE, 0);
	this.set(Calendar.SECOND, 0);
	this.set(Calendar.MILLISECOND, 0);
	this.add(Calendar.DATE, 1);
	break;
    case MyDailyRollingFileAppender.TOP_OF_WEEK:
	this.set(Calendar.DAY_OF_WEEK, getFirstDayOfWeek());
	this.set(Calendar.HOUR_OF_DAY, 0);
	this.set(Calendar.MINUTE, 0);
	this.set(Calendar.SECOND, 0);
	this.set(Calendar.MILLISECOND, 0);
	this.add(Calendar.WEEK_OF_YEAR, 1);
	break;
    case MyDailyRollingFileAppender.TOP_OF_MONTH:
	this.set(Calendar.DATE, 1);
	this.set(Calendar.HOUR_OF_DAY, 0);
	this.set(Calendar.MINUTE, 0);
	this.set(Calendar.SECOND, 0);
	this.set(Calendar.MILLISECOND, 0);
	this.add(Calendar.MONTH, 1);
	break;
    default:
	throw new IllegalStateException("Unknown periodicity type.");
    }
    return getTime();
  }
}

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐