log4j2 自动删除过期日志文件的配置及实现原理

  日志文件自动删除功能必不可少,当然你可以让运维去做这事,只是这不地道。而日志组件是一个必备组件,让其多做一件删除的工作,无可厚非。本文就来探讨下 log4j 的日志文件自动删除实现吧。

0.自动删除配置参考样例: (log4j2.xml)

<?xml version="1.0" encoding="UTF-8" ?>
<Configuration status="warn" monitorInterval="30" strict="true"
        schema="Log4J-V2.2.xsd">
  <Properties>
    <Property name="log_level">info</Property>
  </Properties>
  <Appenders>
    <!-- 输出到控制台 -->
    <Console name="Console" target="SYSTEM_OUT">
      <ThresholdFilter level="${log_level}" onMatch="ACCEPT" onMismatch="DENY" />
      <PatternLayout pattern="%d{yyyy-MM-dd'T'HH:mm:ss.SSS} [%t] %p - %c - %m%n" />
    </Console>
    <!-- 与properties文件中位置存在冲突,如有问题,请注意调整 -->
    <RollingFile name="logFile" fileName="logs/app/test.log"
           filePattern="logs/app/history/test-%d{MM-dd-yyyy}-%i.log.gz">
      <ThresholdFilter level="${log_level}" onMatch="ACCEPT" onMismatch="DENY" />
      <PatternLayout pattern="%d{yyyy-MM-dd'T'HH:mm:ss.SSS} [%p] [%c:%L] -- %m%n" />
      <Policies>
        <!-- 按天递计算频率 -->
        <TimeBasedTriggeringPolicy interval="1" />
        <SizeBasedTriggeringPolicy size="500 MB" />
        <OnStartupTriggeringPolicy />
      </Policies>
      <!-- 删除策略配置 -->
      <DefaultRolloverStrategy max="5">
        <Delete basePath="logs/app/history" maxDepth="1">
          <IfFileName glob="*.log.gz"/>
          <IfLastModified age="7d"/>
        </Delete>
        <Delete basePath="logs/app/history" maxDepth="1">
          <IfFileName glob="*.docx"/>
        </Delete>
        <Delete basePath="logs/app/history" maxDepth="1">
          <IfFileName glob="*.vsdx"/>
        </Delete>
      </DefaultRolloverStrategy>
    </RollingFile>
    <Async name="Async" bufferSize="2000" blocking="false">
      <AppenderRef ref="logFile"/>
    </Async>
  </Appenders>

  <Loggers>
    <Root level="${log_level}">
      <AppenderRef ref="Console" />
      <AppenderRef ref="Async" />
    </Root>
    <!-- 配置个例 -->
    <Logger name="com.xx.filter" level="info" />
  </Loggers>
</Configuration>

  如果仅想停留在使用层面,如上log4j2.xml配置文件足矣!

  不过,至少得注意一点,以上配置需要基于log4j2, 而如果你是 log4j1.x,则需要做下无缝升级:主要就是换下jar包版本,换个桥接包之类的,比如下参考配置:

<dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
      </dependency>
      <!-- 桥接:告诉commons logging使用Log4j2 -->
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.7.26</version>
      </dependency>

      <!-- 此处老版本,需注释掉 -->
      <!--<dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
      </dependency>-->

      <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-compress</artifactId>
        <version>1.10</version>
      </dependency>
      <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.8.2</version>
      </dependency>
      <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.8.2</version>
      </dependency>
      <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.8.2</version>
      </dependency>
      <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-web</artifactId>
        <version>2.8.2</version>
      </dependency>

 如果还想多了解一点其运行原理,就跟随本文的脚步吧:

1.自动清理大体运行流程

  自动删除工作的运行原理大体流程如下。(大抵都是如此)

    1. 加载log4j2.xml配置文件;
    2. 读取appenders,并添加到log4j上下文中;
    3. 加载 policy, 加载 rollover 配置;
    4. 写入日志时判断是否满足rollover配置, 默认是一天运行一次, 可自行添加各种运行测试, 比如大小、启动时;

  所以,删除策略的核心是每一次添加日志时。代码验证如下:

// 在每次添加日志时判定
  // org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender#append
  /**
   * Write the log entry rolling over the file when required.
   *
   * @param event The LogEvent.
   */
  @Override
  public void append(final LogEvent event) {
    final RollingRandomAccessFileManager manager = getManager();
    // 重点:直接检查是否需要 rollover, 如需要直接进行
    manager.checkRollover(event);

    // Leverage the nice batching behaviour of async Loggers/Appenders:
    // we can signal the file manager that it needs to flush the buffer
    // to disk at the end of a batch.
    // From a user's point of view, this means that all log events are
    // _always_ available in the log file, without incurring the overhead
    // of immediateFlush=true.
    manager.setEndOfBatch(event.isEndOfBatch()); // FIXME manager's EndOfBatch threadlocal can be deleted

    // LOG4J2-1292 utilize gc-free Layout.encode() method: taken care of in superclass
    super.append(event);
  }

  // org.apache.logging.log4j.core.appender.rolling.RollingFileManager#checkRollover
  /**
   * Determines if a rollover should occur.
   * @param event The LogEvent.
   */
  public synchronized void checkRollover(final LogEvent event) {
    // 由各触发策略判定是否需要进行 rolling
    // 如需要, 则调用 rollover()
    if (triggeringPolicy.isTriggeringEvent(event)) {
      rollover();
    }
  }

  所以,何时进行删除?答案是在适当的时机,这个时机可以是任意时候。

2. log4j 日志滚动

  日志滚动,可以是重命名,也可以是删除文件。但总体判断是否可触发滚动的前提是一致的。我们这里主要关注文件删除。我们以时间作为依据看下判断过程。

 // 1. 判断是否是 触发事件时机
  // org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy#isTriggeringEvent
  /**
   * Determines whether a rollover should occur.
   * @param event  A reference to the currently event.
   * @return true if a rollover should occur.
   */
  @Override
  public boolean isTriggeringEvent(final LogEvent event) {
    if (manager.getFileSize() == 0) {
      return false;
    }
    final long nowMillis = event.getTimeMillis();
    // TimeBasedTriggeringPolicy, 是基于时间判断的, 此处为每天一次
    if (nowMillis >= nextRolloverMillis) {
      nextRolloverMillis = manager.getPatternProcessor().getNextTime(nowMillis, interval, modulate);
      return true;
    }
    return false;
  }
  // org.apache.logging.log4j.core.appender.rolling.RollingFileManager#rollover()
  public synchronized void rollover() {
    if (!hasOutputStream()) {
      return;
    }
    // strategy 是xml配置的策略
    if (rollover(rolloverStrategy)) {
      try {
        size = 0;
        initialTime = System.currentTimeMillis();
        createFileAfterRollover();
      } catch (final IOException e) {
        logError("Failed to create file after rollover", e);
      }
    }
  }
  // RollingFileManager 统一管理触发器
  // org.apache.logging.log4j.core.appender.rolling.RollingFileManager#rollover
  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 {
      // 由各触发器运行 rollover 逻辑
      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);
          }
        }
        // 如果配置了异步器, 则使用异步进行 rollover
        if (success && descriptor.getAsynchronous() != null) {
          LOGGER.debug("RollingFileManager executing async {}", descriptor.getAsynchronous());
          // CompositeAction, 使用异步线程池运行用户的 action
          asyncExecutor.execute(new AsyncAction(descriptor.getAsynchronous(), this));
          // 在异步运行action期间,锁是不会被释放的,以避免线程安全问题
          // 直到异步任务完成,再主动释放锁
          releaseRequired = false;
        }
        return true;
      }
      return false;
    } finally {
      if (releaseRequired) {
        semaphore.release();
      }
    }

  }

  此处滚动有两个处理点,1. 每个滚动策略可以自行处理业务; 2. RollingFileManager 统一管理触发同步和异步的滚动action;

3. DefaultRolloverStrategy 默认滚动策略驱动

  DefaultRolloverStrategy 作为一个默认的滚动策略实现,可以配置多个 Action, 然后处理删除操作。

  删除有两种方式: 1. 当次滚动的文件数过多,会立即进行删除; 2. 配置单独的 DeleteAction, 根据配置的具体策略进行删除。(但该Action只会被返回给外部调用,自身则不会执行)

 // org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy#rollover
  /**
   * 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 {
    int fileIndex;
    // 默认 minIndex=1
    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();
      // 删除case1: 获取符合条件的文件数,同时清理掉大于 max 配置的日志文件
      // 如配置 max=5, 当前只有4个满足时, 不会立即清理文件, 但也不会阻塞后续流程
      // 只要没有出现错误, fileIndex 不会小于0
      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;

    FileExtension fileExtension = manager.getFileExtension();
    if (fileExtension != null) {
      renameTo = renameTo.substring(0, renameTo.length() - fileExtension.length());
      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);
    }
    // 新建一个重命令的 action, 返回待用
    final FileRenameAction renameAction = new FileRenameAction(new File(currentFileName), new File(renameTo),
          manager.isRenameEmptyFiles());
    // 异步处理器,会处理用户配置的异步action,如本文配置的 DeleteAction
    // 它将会在稍后被提交到异步线程池中运行
    final Action asyncAction = merge(compressAction, customActions, stopCustomActionsOnError);
    // 封装Rollover返回, renameAction 是同步方法, 其他用户配置的动态action 则是异步方法
    // 删除case2: 封装异步返回action
    return new RolloverDescriptionImpl(currentFileName, false, renameAction, asyncAction);
  }
  private int purge(final int lowIndex, final int highIndex, final RollingFileManager manager) {
    // 默认使用 accending 的方式进行清理文件
    return useMax ? purgeAscending(lowIndex, highIndex, manager) : purgeDescending(lowIndex, highIndex, manager);
  }
  // org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy#purgeAscending
  /**
   * Purges and renames old log files in preparation for rollover. The oldest file will have the smallest index, the
   * newest the highest.
   *
   * @param lowIndex low index. Log file associated with low index will be deleted if needed.
   * @param highIndex high index.
   * @param manager The RollingFileManager
   * @return true if purge was successful and rollover should be attempted.
   */
  private int purgeAscending(final int lowIndex, final int highIndex, final RollingFileManager manager) {
    final SortedMap<Integer, Path> eligibleFiles = getEligibleFiles(manager);
    final int maxFiles = highIndex - lowIndex + 1;

    boolean renameFiles = false;
    // 依次迭代 eligibleFiles, 删除
    while (eligibleFiles.size() >= maxFiles) {
      try {
        LOGGER.debug("Eligible files: {}", eligibleFiles);
        Integer key = eligibleFiles.firstKey();
        LOGGER.debug("Deleting {}", eligibleFiles.get(key).toFile().getAbsolutePath());
        // 调用nio的接口删除文件
        Files.delete(eligibleFiles.get(key));
        eligibleFiles.remove(key);
        renameFiles = true;
      } catch (IOException ioe) {
        LOGGER.error("Unable to delete {}, {}", eligibleFiles.firstKey(), ioe.getMessage(), ioe);
        break;
      }
    }
    final StringBuilder buf = new StringBuilder();
    if (renameFiles) {
      // 针对未完成删除的文件,继续处理
      // 比如使用 匹配的方式匹配文件, 则不能被正常删除
      // 还有些未超过maxFiles的文件
      for (Map.Entry<Integer, Path> entry : eligibleFiles.entrySet()) {
        buf.setLength(0);
        // LOG4J2-531: directory scan & rollover must use same format
        manager.getPatternProcessor().formatFileName(strSubstitutor, buf, entry.getKey() - 1);
        String currentName = entry.getValue().toFile().getName();
        String renameTo = buf.toString();
        int suffixLength = suffixLength(renameTo);
        if (suffixLength > 0 && suffixLength(currentName) == 0) {
          renameTo = renameTo.substring(0, renameTo.length() - suffixLength);
        }
        Action action = new FileRenameAction(entry.getValue().toFile(), new File(renameTo), true);
        try {
          LOGGER.debug("DefaultRolloverStrategy.purgeAscending executing {}", action);
          if (!action.execute()) {
            return -1;
          }
        } catch (final Exception ex) {
          LOGGER.warn("Exception during purge in RollingFileAppender", ex);
          return -1;
        }
      }
    }
    // 此处返回的 findIndex 一定是 >=0 的
    return eligibleFiles.size() > 0 ?
        (eligibleFiles.lastKey() < highIndex ? eligibleFiles.lastKey() + 1 : highIndex) : lowIndex;
  }

4. 符合过滤条件的文件查找

  当配置了 max 参数,这个参数是如何匹配的呢?比如我某个文件夹下有很历史文件,是否都会匹配呢?

 // 文件查找规则
  // org.apache.logging.log4j.core.appender.rolling.AbstractRolloverStrategy#getEligibleFiles
  protected SortedMap<Integer, Path> getEligibleFiles(final RollingFileManager manager) {
    return getEligibleFiles(manager, true);
  }
  protected SortedMap<Integer, Path> getEligibleFiles(final RollingFileManager manager,
                            final boolean isAscending) {
    final StringBuilder buf = new StringBuilder();
    // 此处的pattern 即是在appender上配置的 filePattern, 一般会受限于 MM-dd-yyyy-$i.log.gz
    String pattern = manager.getPatternProcessor().getPattern();
    // 此处会将时间替换为当前, 然后按照此规则进行匹配要处理的文件
    manager.getPatternProcessor().formatFileName(strSubstitutor, buf, NotANumber.NAN);
    return getEligibleFiles(buf.toString(), pattern, isAscending);
  }
  // 细节匹配要处理的文件
  protected SortedMap<Integer, Path> getEligibleFiles(String path, String logfilePattern, boolean isAscending) {
    TreeMap<Integer, Path> eligibleFiles = new TreeMap<>();
    File file = new File(path);
    File parent = file.getParentFile();
    if (parent == null) {
      parent = new File(".");
    } else {
      parent.mkdirs();
    }
    if (!logfilePattern.contains("%i")) {
      return eligibleFiles;
    }
    Path dir = parent.toPath();
    String fileName = file.getName();
    int suffixLength = suffixLength(fileName);
    if (suffixLength > 0) {
      fileName = fileName.substring(0, fileName.length() - suffixLength) + ".*";
    }
    String filePattern = fileName.replace(NotANumber.VALUE, "(\\d+)");
    Pattern pattern = Pattern.compile(filePattern);

    try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
      for (Path entry: stream) {
        // 该匹配相当精确
        // 只会删除当天或者在时间交替的时候删除上一天的数据咯
        // 如果在这个时候进行了重启操作,就再也不会删除此文件了
        Matcher matcher = pattern.matcher(entry.toFile().getName());
        if (matcher.matches()) {
          Integer index = Integer.parseInt(matcher.group(1));
          eligibleFiles.put(index, entry);
        }
      }
    } catch (IOException ioe) {
      throw new LoggingException("Error reading folder " + dir + " " + ioe.getMessage(), ioe);
    }
    return isAscending? eligibleFiles : eligibleFiles.descendingMap();
  }
  // 此处会将 各种格式的文件名,替换为当前时间或者最后一次滚动的文件的时间。所以匹配的时候,并不会匹配超时当前认知范围的文件
  /**
   * Formats file name.
   * @param subst The StrSubstitutor.
   * @param buf string buffer to which formatted file name is appended, may not be null.
   * @param obj object to be evaluated in formatting, may not be null.
   */
  public final void formatFileName(final StrSubstitutor subst, final StringBuilder buf, final boolean useCurrentTime,
                   final Object obj) {
    // LOG4J2-628: we deliberately use System time, not the log4j.Clock time
    // for creating the file name of rolled-over files.
    final long time = useCurrentTime && currentFileTime != 0 ? currentFileTime :
        prevFileTime != 0 ? prevFileTime : System.currentTimeMillis();
    formatFileName(buf, new Date(time), obj);
    final LogEvent event = new Log4jLogEvent.Builder().setTimeMillis(time).build();
    final String fileName = subst.replace(event, buf);
    buf.setLength(0);
    buf.append(fileName);
  }

  AsyncAction 是一个 Runnable 的实现, 被直接提交到线程池运行. AsyncAction -> AbstractAction -> Action -> Runnable

  它是一个统一管理异步Action的包装,主要是管理锁和异常类操作。

 // org.apache.logging.log4j.core.appender.rolling.RollingFileManager.AsyncAction
  /**
   * Performs actions asynchronously.
   */
  private static class AsyncAction extends AbstractAction {

    private final Action action;
    private final RollingFileManager manager;

    /**
     * Constructor.
     * @param act The action to perform.
     * @param manager The manager.
     */
    public AsyncAction(final Action act, final RollingFileManager manager) {
      this.action = act;
      this.manager = manager;
    }

    /**
     * Executes an action.
     *
     * @return true if action was successful. A return value of false will cause
     *     the rollover to be aborted if possible.
     * @throws java.io.IOException if IO error, a thrown exception will cause the rollover
     *               to be aborted if possible.
     */
    @Override
    public boolean execute() throws IOException {
      try {
        // 门面调用 action.execute(), 一般是调用 CompositeAction, 里面封装了多个 action
        return action.execute();
      } finally {
        // 任务执行完成,才会释放外部的锁
        // 虽然不是很优雅,但是很准确很安全
        manager.semaphore.release();
      }
    }
    ...
  }

  // CompositeAction 封装了多个 action 处理
  // org.apache.logging.log4j.core.appender.rolling.action.CompositeAction#run
  /**
   * Execute sequence of actions.
   *
   * @return true if all actions were successful.
   * @throws IOException on IO error.
   */
  @Override
  public boolean execute() throws IOException {
    if (stopOnError) {
      // 依次调用action
      for (final Action action : actions) {
        if (!action.execute()) {
          return false;
        }
      }

      return true;
    }
    boolean status = true;
    IOException exception = null;

    for (final Action action : actions) {
      try {
        status &= action.execute();
      } catch (final IOException ex) {
        status = false;

        if (exception == null) {
          exception = ex;
        }
      }
    }

    if (exception != null) {
      throw exception;
    }

    return status;
  }

  DeleteAction是我们真正关心的动作。

 // CompositeAction 封装了多个 action 处理
  // org.apache.logging.log4j.core.appender.rolling.action.CompositeAction#run
  /**
   * Execute sequence of actions.
   *
   * @return true if all actions were successful.
   * @throws IOException on IO error.
   */
  @Override
  public boolean execute() throws IOException {
    if (stopOnError) {
      // 依次调用action
      for (final Action action : actions) {
        if (!action.execute()) {
          return false;
        }
      }

      return true;
    }
    boolean status = true;
    IOException exception = null;

    for (final Action action : actions) {
      try {
        status &= action.execute();
      } catch (final IOException ex) {
        status = false;

        if (exception == null) {
          exception = ex;
        }
      }
    }

    if (exception != null) {
      throw exception;
    }

    return status;
  }

  // DeleteAction 做真正的删除动作
  // org.apache.logging.log4j.core.appender.rolling.action.DeleteAction#execute()
  @Override
  public boolean execute() throws IOException {
    // 如果没有script配置,则直接委托父类处理
    return scriptCondition != null ? executeScript() : super.execute();
  }
  org.apache.logging.log4j.core.appender.rolling.action.AbstractPathAction#execute()
  @Override
  public boolean execute() throws IOException {
    // 根据指定的basePath, 和过滤条件,选择相关文件
    // 调用 DeleteAction 的 createFileVisitor(), 返回 DeletingVisitor
    return execute(createFileVisitor(getBasePath(), pathConditions));
  }
  // org.apache.logging.log4j.core.appender.rolling.action.DeleteAction#execute(java.nio.file.FileVisitor<java.nio.file.Path>)
  @Override
  public boolean execute(final FileVisitor<Path> visitor) throws IOException {
    // 根据maxDepth设置,遍历所有可能的文件路径
    // 使用 Files.walkFileTree() 实现, 添加到 collected 中
    final List<PathWithAttributes> sortedPaths = getSortedPaths();
    trace("Sorted paths:", sortedPaths);

    for (final PathWithAttributes element : sortedPaths) {
      try {
        // 依次调用 visitFile, 依次判断是否需要删除
        visitor.visitFile(element.getPath(), element.getAttributes());
      } catch (final IOException ioex) {
        LOGGER.error("Error in post-rollover Delete when visiting {}", element.getPath(), ioex);
        visitor.visitFileFailed(element.getPath(), ioex);
      }
    }
    // TODO return (visitor.success || ignoreProcessingFailure)
    return true; // do not abort rollover even if processing failed
  }

  最终,即和想像的一样:找到要查找的文件夹,遍历各文件,用多个条件判断是否满足。删除符合条件的文件。

  只是这其中注意的点:如何删除文件的线程安全性;如何保证删除工作不影响业务线程;很常见的锁和多线程的应用。

5.真正的删除

  真正的删除动作就是在DeleteAction中配置的,但上面可以看它是调用visitor的visitFile方法,所以有必要看看是如何真正处理删除的。(实际上前面在purge时已经做过一次删除操作了,所以别被两个点迷惑了,建议尽量只依赖于Delete配置,可以将外部max设置很大以避免两处生效)

 // org.apache.logging.log4j.core.appender.rolling.action.DeletingVisitor#visitFile
  @Override
  public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
    for (final PathCondition pathFilter : pathConditions) {
      final Path relative = basePath.relativize(file);
      // 遍历所有条件,只要有一个不符合,即不进行删除。
      // 所以,所以条件是 AND 关系, 没有 OR 关系
      // 如果想配置 OR 关系,只能配置多个DELETE
      if (!pathFilter.accept(basePath, relative, attrs)) {
        LOGGER.trace("Not deleting base={}, relative={}", basePath, relative);
        return FileVisitResult.CONTINUE;
      }
    }
    // 直接删除文件
    if (isTestMode()) {
      LOGGER.info("Deleting {} (TEST MODE: file not actually deleted)", file);
    } else {
      delete(file);
    }
    return FileVisitResult.CONTINUE;
  }

  删除策略配置比如:

<RollingFile name="logFile" fileName="logs/app/test.log"
         filePattern="logs/app/history/test-%d{MM-dd-yyyy}-%i.log.gz">
    <ThresholdFilter level="${log_level}" onMatch="ACCEPT" onMismatch="DENY" />
    <PatternLayout pattern="%d{yyyy-MM-dd'T'HH:mm:ss.SSS} [%p] [%c:%L] -- %m%n" />
    <Policies>
      <!-- 按天递计算频率 -->
      <TimeBasedTriggeringPolicy interval="1" />
      <SizeBasedTriggeringPolicy size="500 MB" />
      <OnStartupTriggeringPolicy />
    </Policies>
    <!-- 删除策略配置 -->
    <DefaultRolloverStrategy max="5000">
      <Delete basePath="logs/app/history" maxDepth="1">
        <!-- 配置且关系 -->
        <IfFileName glob="*.log.gz"/>
        <IfLastModified age="7d"/>
      </Delete>
      <!-- 配置或关系 -->
      <Delete basePath="logs/app/history" maxDepth="1">
        <IfFileName glob="*.docx"/>
      </Delete>
      <Delete basePath="logs/app/history" maxDepth="1">
        <IfFileName glob="*.vsdx"/>
      </Delete>
    </DefaultRolloverStrategy>
  </RollingFile>

  另外说明,之所以能够无缝替换,是因为利用了不同实现版本的 org/slf4j/impl/StaticLoggerBinder.class, 而外部都使用 slf4j 接口定义实现的,比如 org.apache.logging.log4j:log4j-slf4j-impl 包的实现。

总结

到此这篇关于log4j2 自动删除过期日志文件的配置及实现原理解析的文章就介绍到这了,更多相关log4j2自动删除过期日志文件内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • log4j2异步Logger(详解)

    1 异步Logger的意义 之前的日志框架基本都实现了AsyncAppender,被证明对性能的提升作用非常明显. 在log4j2日志框架中,增加了对Logger的异步实现.那么这一步的解耦,意义何在呢? 如图,按我目前的理解:异步Logger是让业务逻辑把日志信息放入Disruptor队列后可以直接返回(无需等待"挂载的各个Appender"都取走数据) 优点:更高吞吐.调用log方法更低的延迟. 缺点:异常处理麻烦. 可变日志消息问题.更大的CPU开销.需要等待"最慢的A

  • log4j使用教程详解(怎么使用log4j2)

    1. 去官方下载log4j 2,导入jar包,基本上你只需要导入下面两个jar包就可以了(xx是乱七八糟的版本号): log4j-core-xx.jar log4j-api-xx.jar 2. 导入到你的项目中:这个就不说了. 3. 开始使用: 我们知道,要在某个类中使用log4j记录日志,只需要申明下面的成员变量(其实不一定要是成员变量,只是为了方便调用而已) 复制代码 代码如下: private static Logger logger = LogManager.getLogger(MyAp

  • Spring Boot Log4j2的配置使用详解

    后台程序开发及上线时,一般都会用到Log信息打印及Log日志记录,开发时通过Log信息打印可以快速的定位问题所在,帮助我们快捷开发.程序上线后如遇到Bug或错误,此时则需要日志记录来查找发现问题所在. Spring Boot 可以集成很多不同的日志系统,其中最常用的Apache Log4j,而Log4j 2是Log4j的升级版本,Log4j 2相对于Log4j 1.x 有了很多显著的改善.所以这篇博客就直接来说说Spring Boot如何集成并配置使用Log4j2. 1. 导入Log4j2的包

  • spring boot自定义log4j2日志文件的实例讲解

    背景:因为从 spring boot 1.4开始的版本就要用log4j2 了,支持的格式有json和xml两种格式,此次实践主要使用的是xml的格式定义日志说明. spring boot 1.5.8.RELEASE 引入log4j2的开发步骤如下: 1.首先把spring-boot-starter-web以及spring-boot-starter包下面的spring-boot-starter-logging排除,然后引入spring-boot-starter-log4j2包. <dependen

  • log4j2日志异步打印(实例讲解)

    log4j2支持日志的异步打印,日志异步输出的好处在于,使用单独的进程来执行日志打印的功能,可以提高日志执行效率,减少日志功能对正常业务的影响. 异步日志在程序的classpath需要加载disruptor-3.0.0.jar或者更高的版本. Asynchronous Loggers是一个新增特性在Log4j 2 ,可以实现完全异步也可以和同步混合使用,还可以只异步化Appender,以提升系统性能,官方数据显示混合没有完全异步化效果好. 1,完全异步模式: 这种异步日志方式,不需要修改原来的配

  • SpringBoot集成slf4j+log4j2的示例代码

    本文介绍了SpringBoot集成slf4j+log4j2的示例代码,分享给大家,具体如下: Maven依赖 <!--增加log4j2依赖↓--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> <dependency&g

  • 老生常谈Log4j和Log4j2的区别(推荐)

    相信很多程序猿朋友对log4j都很熟悉,log4j可以说是陪伴了绝大多数的朋友开启的编程.我不知道log4j之前是用什么,至少在我的生涯中,是log4j带我开启的日志时代. log4j是Apache的一个开源项目,我们不去考究它的起源时间,但是据我了解,log4j 1已经不再更新了. 回顾log4j,曾给我们留下了多少的回忆,我记得早些年,那时候mybatis还是叫ibatis的时候,我为了配置ibatis控制台打印日志,纠结了多少个夜晚,最后配置出来时的那种喜悦感.废话不多说,下面我就以列举的

  • log4j2 自动删除过期日志文件的配置及实现原理

    日志文件自动删除功能必不可少,当然你可以让运维去做这事,只是这不地道.而日志组件是一个必备组件,让其多做一件删除的工作,无可厚非.本文就来探讨下 log4j 的日志文件自动删除实现吧. 0.自动删除配置参考样例: (log4j2.xml) <?xml version="1.0" encoding="UTF-8" ?> <Configuration status="warn" monitorInterval="30&qu

  • Linux下自动删除归档日志文件的方法

    1.日志删除策略 自动删除7天前的归档日志与备份文件. 2.调度计划 0 0 * * * nohup sh /db2backup/script/auto_rm_logs.sh & 3.日志删除脚本 auto_rm_logs.sh #!/bin/sh #------------------------------------- # rm db2 archive log file and *.tgz files @hury # create @2016-12-13 # script name:aut

  • python删除过期log文件操作实例解析

    本文研究的主要是python删除过期log文件的相关内容,具体介绍如下. 1. 用Python遍历目录 os.walk方法可以很方便的得到目录下的所有文件,会返回一个三元的tupple(dirpath, dirnames, filenames),其中,dirpath是代表目录的路径,dirnames是一个list,包含了dirpath下的所有子目录的名字,filenames是一个list,包含了非目录的文件,如果需要得到全路径,需要使用os.path.join(dirpath,name).例如t

  • Linux下自动删除过期备份和自动异地备份的脚本

    目录 每天自动删除过期备份 每天定时异地备份 每天自动删除过期备份 首先编写一个简单的Shell脚本DeleteExpireBackup.sh: #!/bin/bash # 修改需要删除的路径 location="/database/backup/" # 删除最后修改时间为30天以前的备份文件夹 find $location -mtime +30 -type d | xargs rm -rf {} -mtime:文件内容最后一次修改的时间,+30 代表大于30天的.其他参数可选: 访问

  • Linux定时自动删除旧垃圾文件的Autotrash工具

    Autotrash 是一个命令行程序,它用于自动清除旧的已删除文件.它将清除超过指定天数的在回收站中的文件.你不需要清空回收站或执行 SHIFT+DELETE 以永久清除文件/文件夹.Autortrash 将处理回收站中的内容,并在特定时间段后自动删除它们.简而言之,Autotrash 永远不会让你的垃圾变得太大. 安装 Autotrash Autotrash 默认存在于基于 Debian 系统的仓库中.要在 Debian.Ubuntu.Linux Mint 上安装 autotrash,请运行:

  • Linux使用shell脚本定时删除历史日志文件

    1.tools目录文件结构 [root@www tools]# tree tools/ tools/ ├── bin │ ├── del_history_files │ └── etc ├── del_history_files.cfg 2 directories, 2 files 2.删除历史文件脚本 del_history_files [root@www tools]# more tools/bin/del_history_files #!/bin/sh # 删除指定目录下,文件时间早于指定

  • Windows下wamp php单元测试工具PHPUnit安装及生成日志文件配置方法

    本文实例讲述了Windows下wamp php单元测试工具PHPUnit安装及生成日志文件配置方法.分享给大家供大家参考,具体如下: phpunit下载网站 http://www.phpunit.cn/ 一.安装PHPUnit 1.选择版本 我用的是php版本是5.6.25 所以我选择的是PHPUnit 5.7 2.安装过程 ① 为 PHP 的二进制可执行文件建立一个目录,例如 C:\bin ② 将 ;C:\bin 附加到 PATH 环境变量中  [将 php的目录 ;E:\wamp64\bin

  • C++删除指定文件夹下N天及之前日志文件的方法

    本文实例讲述了C++删除指定文件夹下N天及之前日志文件的方法.分享给大家供大家参考.具体如下: // 功能:删除nDays天及之前的日志文件 // @nDays: 0-不删除日志,3-删除3天及之前的日志(保留今天.昨天.前天的日志) ... void CRecordLog::ClearLog(UINT nDays) // 删除N天前的日志 { if (nDays > 0) { WIN32_FIND_DATA FindFileData; CString sAllFile = m_sLogFold

  • 详解Oracle控制文件及日志文件的管理问题

    目录 一.控制文件的管理 1.控制文件的概述 2.控制文件的创建 1.控制文件的重新建立 3.控制文件的备份 1.将控制文件备份为二进制文件 2.将控制文件备份为文本文件 4.控制文件的恢复 5.添加多路复用的控制文件 二.重做日志文件的管理 1.重做日志文件概述 2.查询重做日志文件信息 3.重做日志文件组及成员的创建 4.重做日志文件组及成员的删除 5.修改重做日志文件的名称或位置 三.归档日志文件的管理 1.归档日志文件概述 2.归档日志信息的查询 3.归档模式的设置 四.总结 一.控制文

  • bat使用forfiles自动批量删除过期文件

    ftp服务器用于保存备份文件,但是也不需要每天的数据都留着,于是乎为了考虑节省硬盘空间,就必须删除一些老文件,只要保存最近一周或者一个月的就可以了 windows自带了一个批量删除的程序,叫做forfiles,XP里貌似没有,博文最下面附件可以下载 首先说一下我的应用吧,ftp服务器端每天备份数据文件后,只想保存最近两周的文件 编写一个bat文件,内容如下 写道 @echo off rem write to log set filename=deletefile.log echo -------

随机推荐