Configuring Java FileHandler Logging to create dir

2020-02-10 15:01发布

问题:

I'm trying to configure the Java Logging API's FileHandler to log my server to a file within a folder in my home directory, but I don't want to have to create those directories on every machine it's running.

For example in the logging.properties file I specify:

java.util.logging.FileHandler
java.util.logging.FileHandler.pattern=%h/app-logs/MyApplication/MyApplication_%u-%g.log

This would allow me to collect logs in my home directory (%h) for MyApplication and would rotate them (using the %u, and %g variables).

Log4j supports this when I specify in my log4j.properties:

log4j.appender.rolling.File=${user.home}/app-logs/MyApplication-log4j/MyApplication.log

It looks like there is a bug against the Logging FileHandler: Bug 6244047: impossible to specify driectorys to logging FileHandler unless they exist

It sounds like they don't plan on fixing it or exposing any properties to work around the issue (beyond having your application parse the logging.properties or hard code the path needed):

It looks like the java.util.logging.FileHandler does not expect that the specified directory may not exist. Normally, it has to check this condition anyway. Also, it has to check the directory writing permissions as well. Another question is what to do if one of these check does not pass.

One possibility is to create the missing directories in the path if the user has proper permissions. Another is to throw an IOException with a clear message what is wrong. The latter approach looks more consistent.

回答1:

It seems like log4j version 1.2.15 does it.

Here is the snippet of the code which does it

public
 synchronized
 void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize)
                                                        throws IOException {
    LogLog.debug("setFile called: "+fileName+", "+append);

    // It does not make sense to have immediate flush and bufferedIO.
    if(bufferedIO) {
      setImmediateFlush(false);
    }

    reset();
    FileOutputStream ostream = null;
    try {
          //
          //   attempt to create file
          //
          ostream = new FileOutputStream(fileName, append);
    } catch(FileNotFoundException ex) {
          //
          //   if parent directory does not exist then
          //      attempt to create it and try to create file
          //      see bug 9150
          //
          String parentName = new File(fileName).getParent();
          if (parentName != null) {
             File parentDir = new File(parentName);
             if(!parentDir.exists() && parentDir.mkdirs()) {
                ostream = new FileOutputStream(fileName, append);
             } else {
                throw ex;
             }
          } else {
             throw ex;
          }
    }
    Writer fw = createWriter(ostream);
    if(bufferedIO) {
      fw = new BufferedWriter(fw, bufferSize);
    }
    this.setQWForFiles(fw);
    this.fileName = fileName;
    this.fileAppend = append;
    this.bufferedIO = bufferedIO;
    this.bufferSize = bufferSize;
    writeHeader();
    LogLog.debug("setFile ended");
}

This piece of code is from FileAppender, RollingFileAppender extends FileAppender.

Here it is not checking whether we have permission to create the parent folders, but if the parent folders is not existing then it will try to create the parent folders.

EDITED

If you want some additional functionalily, you can always extend RollingFileAppender and override the setFile() method.



回答2:

You can write something like this.

package org.log;

import java.io.IOException;
import org.apache.log4j.RollingFileAppender;

public class MyRollingFileAppender extends RollingFileAppender {

    @Override
    public synchronized void setFile(String fileName, boolean append,
        boolean bufferedIO, int bufferSize) throws IOException {
        //Your logic goes here
        super.setFile(fileName, append, bufferedIO, bufferSize);
    }

}

Then in your configuration

log4j.appender.fileAppender=org.log.MyRollingFileAppender

This works perfectly for me.



回答3:

To work around the limitations of the Java Logging framework, and the unresolved bug: Bug 6244047: impossible to specify driectorys to logging FileHandler unless they exist

I've come up with 2 approaches (although only the first approach will actually work), both require your static void main() method for your app to initialize the logging system.

e.g.

public static void main(String[] args) {    

    initLogging();
     ...
    }

The first approach hard-codes the log directories you expect to exist and creates them if they don't exist.

private static void initLogging() {
    try {
        //Create logging.properties specified directory for logging in home directory
        //TODO: If they ever fix this bug (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6244047) in the Java Logging API we wouldn't need this hack
        File homeLoggingDir = new File (System.getProperty("user.home")+"/webwars-logs/weblings-gameplatform/");
        if (!homeLoggingDir.exists() ) {
            homeLoggingDir.mkdirs();
            logger.info("Creating missing logging directory: " + homeLoggingDir);
        }
    } catch(Exception e) {
        e.printStackTrace();
    }

    try {
        logger.info("[GamePlatform] : Starting...");
    } catch (Exception exc) {
        exc.printStackTrace();

    }
}

The second approach could catch the IOException and create the directories listed in the exception, the problem with this approach is that the Logging framework has already failed to create the FileHandler so catching and resolving the error still leaves the logging system in a bad state.



回答4:

As a possible solution I think there are 2 approaches (look at some of the previous answers). I can extend a Java Logging Handler class and write my own custom handler. I could also copy the log4j functionality and adapt it to the Java Logging framework.

Here's an example of copying the basic FileHandler and creating a CustomFileHandler see pastebin for full class:

The key is the openFiles() method where it tries to create a FileOutputStream and checking and creating the parent directory if it doesn't exist (I also had to copy package protected LogManager methods, why did they even make those package protected anyways):

// Private method to open the set of output files, based on the
// configured instance variables.
private void openFiles() throws IOException {
    LogManager manager = LogManager.getLogManager();

...

    // Create a lock file. This grants us exclusive access
    // to our set of output files, as long as we are alive.
    int unique = -1;
    for (;;) {
        unique++;
        if (unique > MAX_LOCKS) {
            throw new IOException("Couldn't get lock for " + pattern);
        }
        // Generate a lock file name from the "unique" int.
        lockFileName = generate(pattern, 0, unique).toString() + ".lck";
        // Now try to lock that filename.
        // Because some systems (e.g. Solaris) can only do file locks
        // between processes (and not within a process), we first check
        // if we ourself already have the file locked.
        synchronized (locks) {
            if (locks.get(lockFileName) != null) {
                // We already own this lock, for a different FileHandler
                // object. Try again.
                continue;
            }
            FileChannel fc;
            try {
                File lockFile = new File(lockFileName);
                if (lockFile.getParent() != null) {
                    File lockParentDir = new File(lockFile.getParent());
                    // create the log dir if it does not exist
                    if (!lockParentDir.exists()) {
                        lockParentDir.mkdirs();
                    }
                }

                lockStream = new FileOutputStream(lockFileName);
                fc = lockStream.getChannel();
            } catch (IOException ix) {
                // We got an IOException while trying to open the file.
                // Try the next file.
                continue;
            }
            try {
                FileLock fl = fc.tryLock();
                if (fl == null) {
                    // We failed to get the lock. Try next file.
                    continue;
                }
                // We got the lock OK.
            } catch (IOException ix) {
                // We got an IOException while trying to get the lock.
                // This normally indicates that locking is not supported
                // on the target directory. We have to proceed without
                // getting a lock. Drop through.
            }
            // We got the lock. Remember it.
            locks.put(lockFileName, lockFileName);
            break;
        }
    }

... }



回答5:

I generally try to avoid static code but to work around this limitaton here is my approach that worked on my project just now.

I subclassed java.util.logging.FileHandler and implemented all constructors with their super calls. I put a static block of code in the class that creates the folders for my app in the user.home folder if they don't exist.

In my logging properties file I replaced java.util.logging.FileHandler with my new class.