Кто-нибудь знает, как настроить или расширить lo4j2 для воссоздания журналов после удаления? - PullRequest
0 голосов
/ 01 октября 2018

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
Нетметод activateOptions() в Log4j2-инфраструктуре.

Так кто-нибудь сталкивался с такой же проблемой?Как вы решили это?

ОБНОВЛЕНИЕ
Я попытался инициализировать Политику запуска, чтобы вручную "откатить" удаленный файл, но у меня это не работает.
Мой код:

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 голосов
/ 04 октября 2018

Я наконец-то нашел решение.Спасибо @Alexander в комментариях к подсказке.

Short: Мы можем вручную инициализировать процесс ролловера при обнаружении удаления файла.

Longer:
Я реализую это следующим образом:
1) Создайте FileWatchService, который (1) подпишется на события удаления файла журнала в вашей папке журнала и (2) уведомит вас, когда эти события произойдут.Это можно сделать с помощью java.nio.file.WatchService (https://docs.oracle.com/javase/tutorial/essential/io/notification.html).. Я предоставлю свой код ниже.
2) Создайте некоторый другой класс, который будет инициализировать опрокидывание, когда FileWatchService сообщит об удалении файла.Я также предоставлю свой полный код ниже, но основное волшебство будет происходить следующим образом:

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();
    }
}
...