Log4j2不会重新创建日志文件。例如,粗心的管理员已删除了应用当前编写自己的日志的日志文件。
实际结果:日志未写入文件。
想要的结果:log4j2在首次尝试写入文件后重新创建文件并继续使用该文件。
通过cron或其他方式进行的手动重新创建无法正常工作,因为log4j2会“记住”文件的文件描述符,并且即使删除了旧文件并创建了新文件也可以继续使用它。
在StackOverflow上,我只找到一种解决方法(https://stackoverflow.com/a/51593404/5747662),它看起来像这样:
package org.apache.log4j;
import java.io.File;
import org.apache.log4j.spi.LoggingEvent;
public class ModifiedRollingFileAppender extends RollingFileAppender {
@Override
public void append(LoggingEvent event) {
checkLogFileExist();
super.append(event);
}
private void checkLogFileExist(){
File logFile = new File(super.fileName);
if (!logFile.exists()) {
this.activateOptions();
}
}
}
我不喜欢它,因为:
1)它“有点”缓慢
每次编写事件时,我们还将执行checkLogFileExist()
并检查文件系统中的文件。
2)它不适用于Log4j2
Log4j2中断中没有方法activateOptions()
。
那么有人遇到同样的问题吗?你怎么解决的?
更新
我尝试初始化“触发策略”以手动“翻转”已删除的文件,但是它对我不起作用。
我的代码:
final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
// loggerName is name of logger which should work with the file has been deleted.
LoggerConfig loggerConfig = ctx.getConfiguration().getLoggerConfig(loggerName);
// I also know what appender (appenderName) should work with this file.
RollingFileAppender appender = (RollingFileAppender) loggerConfig.getAppenders().get(appenderName);
appender.getTriggeringPolicy().initialize(appender.getManager());
也是我的配置:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="ERROR">
<Appenders>
<RollingFile name="FILE_LOG">
<FileName>../log/temp/server.log</FileName>
<FilePattern>../log/server/SERVER_%d{yyyy-MM-dd-hh-mm}.log</FilePattern>
<PatternLayout pattern="%d{dd.MM.yyyy HH:mm:ss} [%t] %-5level %msg%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="100 MB" />
</Policies>
</RollingFile>
<RollingFile name="OUTPUT_LOG">
<FileName>../log/temp/output.log</FileName>
<FilePattern>../log/output/OUTPUT_%d{yyyy-MM-dd-hh-mm}.log</FilePattern>
<PatternLayout>
<Pattern>%d{dd.MM.yyyy HH:mm:ss} %msg</Pattern>
</PatternLayout>
<Policies>
<CronTriggeringPolicy schedule="0 0 * * * ?"/>
<OnStartupTriggeringPolicy />
<SizeBasedTriggeringPolicy size="50 MB" />
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="OUTPUT" level="debug" additivity="false">
<AppenderRef ref="OUTPUT_LOG" />
</Logger>
<Root level="debug">
<AppenderRef ref="FILE_LOG" />
</Root>
</Loggers>
</Configuration>
答案 0 :(得分:2)
我终于找到了解决方案。谢谢@Alexander的留言提示。
简短:我们可以在检测到文件删除时手动初始化翻转过程。
更长:
我是这样实现的:
1)创建FileWatchService
,该FileWatchService
将(1)订阅您的日志文件夹中的日志文件删除事件,并且(2)当这些事件发生时通知您。可以通过java.nio.file.WatchService(https://docs.oracle.com/javase/tutorial/essential/io/notification.html)来完成。我将在下面提供我的代码。
2)创建一些其他类,这些类将在final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
// You should know only appender name.
RollingFileAppender appender = (RollingFileAppender) ctx.getConfiguration().getAppenders().get(appenderName);
if (appender != null) {
// Manually start rollover logic.
appender.getManager().rollover();
}
通知文件删除时初始化过渡。我还将在下面提供我的完整代码,但主要的魔术将通过这种方式发生:
FileWatchService
我的代码看起来像这样(不理想,但对我有用):
public class FileWatchService implements Runnable {
private final org.apache.logging.log4j.Logger logger = LogManager.getLogger(FileWatchService.class);
private WatchService watchService = null;
private Map<WatchKey,Path> keys = null;
private String tempPath;
public FileWatchService(String tempPath) {
try {
this.watchService = FileSystems.getDefault().newWatchService();
this.keys = new HashMap<WatchKey,Path>();
this.tempPath = tempPath;
Path path = Paths.get(tempPath);
register(path);
logger.info("Watch service has been initiated.");
}
catch (Exception e) {
logger.error("The error occurred in process of registering watch service", e);
}
}
// Method which register folder to watch service.
private void register(Path tempPath) throws IOException {
logger.debug("Registering folder {} for watching.", tempPath.getFileName());
// Registering only for delete events.
WatchKey key = tempPath.register(watchService, ENTRY_DELETE);
keys.put(key, tempPath);
}
@Override
public void run() {
try {
Thread.currentThread().setName("FileWatchService");
this.processEvents();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void processEvents() throws InterruptedException {
WatchKey key;
// Waiting until event occur.
while ((key = watchService.take()) != null) {
// Poll all events when event occur.
for (WatchEvent<?> event : key.pollEvents()) {
// Getting type of event - delete, modify or create.
WatchEvent.Kind kind = event.kind();
// We are interested only for delete events.
if (kind == ENTRY_DELETE) {
// Sending "notification" to appender watcher service.
logger.debug("Received event about file deletion. File: {}", event.context());
AppenderWatcher.hadleLogFileDeletionEvent(this.tempPath + event.context());
}
}
key.reset();
}
}
}
:
AppenderWatcher
另一个用于初始化翻转的类(我称之为public class AppenderWatcher {
private static final org.apache.logging.log4j.Logger logger = LogManager.getLogger(AppenderWatcher.class);
public static void hadleLogFileDeletionEvent(String logFile) {
File file = new File(logFile);
if (!checkFileExist(file)) {
logger.info("File {} is not exist. Starting manual rollover...", file.toString());
// Getting possible appender name by log-file.
String appenderName = getAppenderNameByFileName(logFile);
// Getting appender from list of all appender
RollingFileAppender appender = (RollingFileAppender) getAppender(appenderName);
if (appender != null) {
// Manually start rollover logic.
appender.getManager().rollover();
logger.info("Rollover finished");
}
else {
logger.error("Can't get appender {}. Please, check lo4j2 config.", appenderName);
}
} else {
logger.warn("Received notification what file {} was deleted, but it exist.", file.getAbsolutePath());
}
}
// Method which checks is file exist. It need to prevent conflicts with Log4J rolling file logic.
// When Log4j rotate file it deletes it first and create after.
private static boolean checkFileExist(File logFile) {
return logFile.exists();
}
// Method which gets appender by name from list of all configured appenders.
private static Appender getAppender(String appenderName) {
final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
return ctx.getConfiguration().getAppenders().get(appenderName);
}
// Method which returns name of appender by log file name.
// ===Here I'm explaining some customer specific moments of log4j config.
private static String getAppenderNameByFileName(String fileName) {
return getLoggerNameByFileName(fileName) + "_LOG";
}
// This method fully customer specific.
private static String getLoggerNameByFileName(String fileName) {
// File name looks like "../log/temp/uber.log" (example).
String[] parts = fileName.split("/");
// Last part should look like "uber.log"
String lastPart = parts[parts.length - 1];
// We need only "uber" part.
String componentName = lastPart.substring(0, lastPart.indexOf("."));
return componentName.toUpperCase();
}
}
):
Middleware