我正在尝试配置 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 似乎存在一个错误: Bug 6244047:无法指定目录来记录 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。
这里并不是检查我们是否有权限创建父文件夹,而是如果父文件夹不存在则会尝试创建父文件夹。
已编辑
如果您想要一些附加功能,您可以随时扩展 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 日志框架的限制和未解决的错误:JDK-6244047:无法指定要记录 FileHandler 的目录,除非它们存在。
我想出了两种方法(尽管只有第一种方法实际上有效),两种方法都需要您的应用程序使用
static 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 (https://bugs.java.com/bugdatabase/view_bug?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
并创建异常中列出的目录,这种方法的问题是日志记录框架已经无法创建FileHandler,因此捕获并解决错误仍然会使日志系统处于糟糕的状态状态。
作为一种可能的解决方案,我认为有两种方法(查看之前的一些答案)。我可以扩展 Java 日志记录处理程序类并编写自己的自定义处理程序。我还可以复制 log4j 功能并将其适应 Java 日志框架。
这是复制基本 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 (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;
}
}
... }
我通常会尝试避免静态代码,但为了解决这个限制,这是我刚才在我的项目中使用的方法。
我对 java.util.logging.FileHandler 进行了子类化,并通过其 super 调用实现了所有构造函数。我在类中放置了一个静态代码块,用于在 user.home 文件夹中为我的应用程序创建文件夹(如果它们不存在)。
在我的日志记录属性文件中,我用新类替换了 java.util.logging.FileHandler。