关于log4j2:log4j2-滚动删除日志文件最后访问时间大小

80次阅读

共计 3693 个字符,预计需要花费 10 分钟才能阅读完成。

有时咱们须要滚动删除日志,不然日志会越积越多。从 log4j2 2.5 之后,在日志滚动的时候能够自定义删除操作,比方咱们心愿保留 3 天的日志,能够这么配置:

- name: FileAppender
fileName: /tmp/log/test.log
filePattern: /tmp/log/test.log.%d{yyyy-MM-dd}"
PatternLayout:
pattern: %-d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%c] [%p] %m%n
Policies:
TimeBasedTriggeringPolicy: {}
DefaultRolloverStrategy:
Delete:
basePath: /tmp/log
maxDepth: 1
IfFileName:
glob: "*.log.*"
IfLastModified:
age: 3d

DefaultRolloverStrategy 中的 Delete 局部就是删除相干的配置

  • basePath 定义了扫描日志文件的根门路。
  • maxDepth 定义了遍历的层级,1 示意 bashPath 下的所有文件
  • IfFileName 定义了扫描的文件格式
  • IfLastModified 定义了只有在最初拜访工夫在 3 天以上的才会被删除

这里须要留神

  • 删除操作只会产生在日志滚动时,而滚动的机会取决于 filePattern 和 Triggering Policies(下面配置中 Policies 局部)
  • IfFileName 指定删除的文件格式,只有符合条件都会被删除,并没有限度是通过以后服务输入。下面这样配置是为了只删除历史日志文件。
  • IfFileName 和 IfLastModified 都属于 pathConditions。pathConditions 是一个数组,决定哪些文件会被删除,如果定义了多个,须要多个条件同时满足才会被删除。

以上能满足个别的需要了,然而偶然删除过期数据还不足够,比方某个工夫日志量激增,可能会导致磁盘占满,所以须要加一个兜底策略。

- name: FileAppender
fileName: /tmp/log/test.log
filePattern: /tmp/log/test.log.%d{yyyy-MM-dd}"
PatternLayout:
pattern: %-d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%c] [%p] %m%n
Policies:
TimeBasedTriggeringPolicy: {}
DefaultRolloverStrategy:
Delete:
basePath: /tmp/log
maxDepth: 1
IfFileName:
glob: "*.log.*"
IfAny:
IfLastModified:
age: 3d
IfAccumulatedFileSize:
exceeds: 200GB

这里改了一下条件,IfAny 示意或者,对于文件名合乎 .log. 格局的,最初拜访工夫为 3 天以上或总共超过 200GB 时都会删除文件。

IfAccumulatedFileSize 是一个累加的过程,开始不满足,加到肯定阈值会满足条件,这个程序能够通过 PathSorter 来指定,默认是先拜访最近批改的文件。

测试配置

因为波及删除文件,在理论批改日志配置后,最好先进行测试,能够把 testMode 设置为 true。

Delete:
basePath: /tmp/log
maxDepth: 1
testMode: true

这样理论并不会删除文件,而是把要删除的文件通过 StatusLoggerINFO 级别输入。

StatusLogger 是用来打印 log4j2 外部信息的,用于诊断和调试,能够通过 status 来配置 StatusLogger 的日志级别,默认会输入在 System.out

Configuration:
status: debug

遇到的问题

mac 上日志寄存到 /tmp 无奈删除

这是因为 mac 上 /tmp 是一个符号链接,而代码实现上会判断 basePath 是否是目录,如果不是目录就间接返回了,不会再进行遍历了。

具体代码能够参考 FileTreeWalkervisit 办法,也能够通过如下单元测试进行测试

@Test
public void test() throws IOException {BasicFileAttributes attributes = Files.readAttributes(Paths.get("/tmp"), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
assert !attributes.isDirectory();}

StatusLogger 日志级别被笼罩

配置 StatusLogger 日志级别为 debug 后,日志并未按预期输入,调试发现 StatusLogger 中打日志相干代码如下

@Override
public void logMessage(final String fqcn, final Level level, final Marker marker, final Message msg,
final Throwable t) {
StackTraceElement element = null;
if (fqcn != null) {element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace());
}
final StatusData data = new StatusData(element, level, msg, t, null);
msgLock.lock();
try {messages.add(data);
} finally {msgLock.unlock();
}
if (listeners.size() > 0) {for (final StatusListener listener : listeners) {if (data.getLevel().isMoreSpecificThan(listener.getStatusLevel())) {listener.log(data);
}
}
} else {logger.logMessage(fqcn, level, marker, msg, t);
}
}

如果有 listener 只会调用 listener 进行输入,StatusConfiguration 中默认会配置一个 StatusConsoleListener,然而日志级别配置却和配置文件中的不同。

进一步调试发现,是因为依赖中援用了 com.alibaba.nacos:nacos-client,这个包中也有 log4j2 的配置,status 的级别是 WARN

StatusConfiguration 中的 initialize 办法

public void initialize() {if (!this.initialized) {if (this.status == Level.OFF) {this.initialized = true;} else {final boolean configured = configureExistingStatusConsoleListener();
if (!configured) {registerNewStatusConsoleListener();
}
migrateSavedLogMessages();}
}
}

如果曾经配置了 StatusConsoleListener 则会重新配置

private boolean configureExistingStatusConsoleListener() {
boolean configured = false;
for (final StatusListener statusListener : this.logger.getListeners()) {if (statusListener instanceof StatusConsoleListener) {final StatusConsoleListener listener = (StatusConsoleListener) statusListener;
listener.setLevel(this.status);
this.logger.updateListenerLevel(this.status);
if (this.verbosity == Verbosity.QUIET) {listener.setFilters(this.verboseClasses);
}
configured = true;
}
}
return configured;
}

所以日志级别被笼罩了,一个简略的办法是再通过代码设置一下级别,比方

StatusLogger.getLogger().getListeners().forEach(statusListener -> {if (statusListener instanceof StatusConsoleListener) {((StatusConsoleListener) statusListener).setLevel(Level.INFO);
}
});

正文完
 0