views:

3412

answers:

4

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.

+2  A: 

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.

Arun P Johny
That code snippet works if I'm within the log4j framework but I'm using the Java Logging framework, and there are no hooks to intercept the FileHandler creation call (other than catching the exception the first time Logger is accessed e.g. logger.info("test").
Dougnukem
What I mean is you can write your own appender.
Arun P Johny
+3  A: 

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.

Arun P Johny
I'll have to look into what I would have to do to create a custom Logging handler in the Java Logging framework, but I'm willing to bet it's similar to the Log4j framework.
Dougnukem
+1  A: 

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.

Dougnukem
Do you think this approach will all scenarios like logging in a web application. There we need a interceptor to be called before the logger is initialized.Since we can write our own logAppender I think that will be a better choice and it can solve most of the cases.
Arun P Johny
I agree, I'm going to do that in the Logging Framework and I'll append my answer.
Dougnukem
i just tried creating my own CustomFileHandler by copying the java.util.logging.FileHandler, there are a couple initial issues. You need to be in the java.util.logging.* package to access Manager .getStringProperty methods. So I tried creating a class with that package but at runtime there is a security exception when loading that class:Can't load log handler "java.util.logging.CustomFileHandler"java.lang.SecurityException: Prohibited package name: java.util.logging
Dougnukem
Since log4j is using Apache License, Version 2.0, we can use the log4j classes and interfaces to create custom classes.I think it will easy to extend the existing log4j FileAppender than writing a new log handler. The example I gave above extends the RollingFileAppender from log4j. In RollingFileAppender we have a method named setFile(), which creates the file to which it has to log. So you can override this method to get the desired result
Arun P Johny
Since you have the fileName in setFile(), which is the absolute path for the file, you can check whether the file/parent directories exists here. If not either you can create the parent directories or throw a appropriate error message as you like.
Arun P Johny
I do not advise anybody to write a new logger since there are lot of matured libraries available. We should concentrate on our key focus area and other supporting tools can be availed from other providers if available. If not able to find something which suites 100% then try to customize available tools if the licence permits.
Arun P Johny
+1  A: 

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

... }

Dougnukem