有人知道如何配置或扩展lo4j2以在删除后重新创建日志吗?

时间:2018-10-01 13:27:21

标签: java log4j2

如果日志文件在运行时被删除,

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>

1 个答案:

答案 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