配置Java FileHandler Logging以在目录不存在时创建目录

14
我正在尝试配置Java Logging API的FileHandler,将我的服务器日志记录到我的主目录中的一个文件夹中,但我不想在每台机器上都创建这些目录。
例如,在logging.properties文件中,我指定了以下内容:
java.util.logging.FileHandler
java.util.logging.FileHandler.pattern=%h/app-logs/MyApplication/MyApplication_%u-%g.log

这样我就可以在我的家目录(%h)中收集MyApplication的日志,并且可以使用%u和%g变量对其进行轮转。
当我在log4j.properties中指定时,Log4j支持这一功能。
log4j.appender.rolling.File=${user.home}/app-logs/MyApplication-log4j/MyApplication.log

看起来对于Logging FileHandler存在一个bug: Bug 6244047: 无法指定logging FileHandler的目录,除非它们存在 听起来他们似乎没有计划修复它或公开任何属性来解决这个问题(除了让您的应用程序解析logging.properties或硬编码所需的路径):
引用: 看起来java.util.logging.FileHandler并不期望指定的目录可能不存在。通常情况下,它必须检查这个条件。此外,它还必须检查目录的写入权限。另一个问题是如果其中一个检查不通过该怎么办。
一种可能性是如果用户具有适当的权限,则在路径中创建缺失的目录。另一种方法是抛出一个带有清晰消息的IOException,说明出了什么问题。后一种方法看起来更一致。
6个回答

9

看起来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。

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

编辑后

如果您想要一些额外的功能,您可以始终扩展RollingFileAppender并重写setFile()方法。


3
如果我在log4j框架内,那个代码片段是可行的,但我正在使用Java Logging框架,没有办法拦截FileHandler创建调用的钩子(除了在第一次访问Logger时捕获异常,例如logger.info(“test”))。 - Dougnukem
我的意思是您可以编写自己的appender。 - Arun P Johny
2
用户没有询问关于Log4J。 - wax_lyrical
是的,这是Log4j的问题。我将其从1.2.8升级到1.2.17,问题得到了解决。如果目标文件夹不存在,则会开始创建文件夹,但显然线程必须有权限更新目标文件夹。 - Parivesh Jain

5
你可以写出类似这样的内容。
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框架中创建自定义的Logging处理程序,但我敢打赌它与Log4j框架相似。 - Dougnukem

4
为了解决Java Logging框架的限制和未解决的错误:Bug 6244047: impossible to specify driectorys to logging FileHandler unless they exist,我提出了两种方法(尽管只有第一种方法实际上有效),这两种方法都需要您的应用程序的静态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 并创建列在异常中的目录,但这种方法的问题在于 Logging 框架已经无法创建 FileHandler,因此即使捕获并解决错误,日志系统仍处于糟糕的状态。

你认为这种方法是否适用于所有场景,比如在Web应用程序中登录。在那里,我们需要在记录器初始化之前调用拦截器。由于我们可以编写自己的日志附加器,我认为这将是更好的选择,并且可以解决大多数情况。 - Arun P Johny
我同意,在日志框架中完成,并附上我的答案。 - Dougnukem
我刚试着创建了自己的CustomFileHandler,通过复制java.util.logging.FileHandler,但有一些初始问题。你需要在java.util.logging.*包中才能访问Manager.getStringProperty方法。所以我尝试创建一个具有该包的类,但运行时加载该类时会出现安全异常: 无法加载日志处理程序"java.util.logging.CustomFileHandler" java.lang.SecurityException:禁止使用包名称:java.util.logging - Dougnukem
1
由于log4j使用Apache许可证,版本2.0,我们可以使用log4j类和接口来创建自定义类。我认为扩展现有的log4j FileAppender比编写新的日志处理程序更容易。我上面给出的示例扩展了log4j中的RollingFileAppender。在RollingFileAppender中,我们有一个名为setFile()的方法,它创建要记录的文件。因此,您可以覆盖此方法以获得所需的结果。 - Arun P Johny
1
由于在setFile()中有fileName,它是文件的绝对路径,因此您可以在此处检查文件/父目录是否存在。如果不存在,则可以创建父目录或按照您的喜好抛出适当的错误消息。 - Arun P Johny
我不建议任何人编写新的日志记录器,因为有很多成熟的库可用。我们应该集中精力于我们的关键重点领域,如果其他提供者提供支持工具,则可以使用它们。如果找不到适合100%的内容,则尝试自定义可用的工具(如果许可证允许)。 - Arun P Johny

1

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

以下是复制基本FileHandler并创建CustomFileHandler的示例,请参见pastebin for full class

关键在于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 (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;
        }
    }

... }


1
我通常避免使用静态代码,但为了解决这个限制,这是我刚刚在我的项目中使用的方法。
我创建了java.util.logging.FileHandler的子类,并实现了所有构造函数及其超级调用。我在该类中放置了一个静态块的代码,如果用户主目录中不存在应用程序文件夹,则会创建它们。
在我的日志属性文件中,我用我的新类替换了java.util.logging.FileHandler。

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接