Does anybody know how to configure or extend lo4j2

2019-07-07 06:18发布

问题:

Log4j2 does not recreate log files if they were deleted in runtime. For example, careless admin have removed log-files where app currently write own logs.
Actual result: logs doesn't write to file.
Wanted result: log4j2 recreate file after first attempt to write into it and continue to work with this file.

Manual recreating by cron or somehow else is not working because log4j2 "remembers" file descriptor of file and continiue to work with it even after old file was deleted and new was created.

On the StackOverflow I found only one workaround (https://stackoverflow.com/a/51593404/5747662) and it looks like this:

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

I don't like it beсause:
1) It "little bit" slow
Each time when we will write event we will also execute checkLogFileExist() and check file in filesystem.
2) It doesn't works for Log4j2
There is no method activateOptions() in Log4j2 infractucture.

So does anybody faced with same problem? How do you solved it?

UPDATE
I have tried to initialyze Triggering Policy to manually "rollover" deleted file, but it's not working for me.
My code:

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

Also my config:

<?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:

I'm finally find the solution. Thanx @Alexander in comments for tip.

Short: We can manually initialize rollover process when detect file deletition.

Longer:
I implement it this way:
1) Create FileWatchService which will (1) subscribe for the log-file deletiiton events in your log folder and (2) notify you when these events will occur. It can be done by java.nio.file.WatchService (https://docs.oracle.com/javase/tutorial/essential/io/notification.html). I will provide my code below.
2) Create some other class which will initialize rollover when FileWatchService will notify about file deletition. I'm also will provide my full code below, but main magic will be occur this way:

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


My code looks like this (not ideal but it's working for me):

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

Another class for initilize rollover (I have called it 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();
    }
}


标签: java log4j2