配置Java FileHandler日志记录以创建不存在的目录

我正在尝试配置Java Logging API的FileHandler以将我的服务器记录到我的主目录中的文件夹中的文件,但我不想在它运行的每台机器上创建这些目录。

例如,在logging.properties文件中,我指定:

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

这将允许我在MyApplication的主目录(%h)中收集日志并将它们旋转(使用%u和%g变量)。

当我在log4j.properties中指定时,Log4j支持这个:

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

看起来Logging FileHandler存在一个错误: 错误6244047:不可能指定driectorys来记录FileHandler,除非它们存在

听起来他们不打算修复它或暴露任何属性来解决问题(除了让你的应用程序解析logging.properties或硬代码所需的路径):

看起来java.util.logging.FileHandler看起来不希望指定的目录可能不存在。 通常,无论如何都必须检查这种情况。 此外,它还必须检查目录写入权限。 另一个问题是,如果其中一项检查未通过,该怎么办。

如果用户具有适当的权限,一种可能性是在路径中创建缺少的目录。 另一种方法是抛出一个带有明确错误信息的IOException。 后一种方法看起来更加一致。

似乎log4j版本1.2.15做到了。

这是执行它的代码片段

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

这段代码来自FileAppender,RollingFileAppender扩展了FileAppender。

这里没有检查我们是否有权创建父文件夹,但如果父文件夹不存在,那么它将尝试创建父文件夹。

EDITED

如果你想要一些额外的function,你可以随时扩展RollingFileAppender并覆盖setFile()方法。

你可以写这样的东西。

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

然后在您的配置中

 log4j.appender.fileAppender=org.log.MyRollingFileAppender 

这对我来说很完美。

要解决Java Logging框架的局限性和未解决的bug: Bug 6244047:不可能指定driectorys来记录FileHandler,除非它们存在

我想出了两种方法(虽然只有第一种方法实际上可以工作),都需要你的应用程序的静态void main()方法来初始化日志记录系统。

例如

 public static void main(String[] args) { initLogging(); ... } 

第一种方法对您希望存在的日志目录进行硬编码,如果它们不存在则创建它们。

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

第二种方法可以捕获IOException并创建exception中列出的目录,这种方法的问题是Logging框架已经无法创建FileHandler,因此捕获和解决错误仍然会使日志记录系统处于错误状态。

作为一种可能的解决方案,我认为有两种方法(看看之前的一些答案)。 我可以扩展Java Logging Handler类并编写自己的自定义处理程序。 我还可以复制log4jfunction并使其适应Java Logging框架。

下面是复制基本FileHandler和创建CustomFileHandler的示例,请参阅完整类的pastebin :

关键是openFiles()方法,它尝试创建一个FileOutputStream并检查并创建父目录(如果它不存在)(我还必须复制包受保护的LogManager方法,为什么他们甚至不管怎样保护这些包):

 // 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 (eg 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; } } 

…}

我通常会尽量避免使用静态代码,但是要解决这个问题,我的方法就是刚刚适用于我的项目。

我将java.util.logging.FileHandler子类化,并使用超级调用实现了所有构造函数。 我在类中放置了一个静态代码块,如果它们不存在,则会在user.home文件夹中为我的应用程序创建文件夹。

在我的日志属性文件中,我用我的新类替换了java.util.logging.FileHandler。