4

免责声明:我很抱歉这个问题太长了。我已经添加了代码,因为我已经探索了这里提出的建议,并在提出我最初的问题后进行了额外的研究。

我正在使用 Java 开发一个开源项目,并希望在我的应用程序中实现日志记录,以便在/如果用户报告应用程序问题时进行错误跟踪和调试。我正在查看标准 Java API 中的 java.util.logging 包。当捕获到异常时,我已经添加了一些日志记录代码。例如,我有

Logger.getLogger(FindCardsPanel.class.getName()).log(Level.SEVERE, "Storage I/O error", ex);

每行代码的唯一区别是类名和消息字符串。

我已经读过使用记录器的正确方法是为每个类名字符串使用一个。这有点道理,因为它在过滤日志消息方面提供了很大的灵活性。

我当前的问题是所有日志消息当前都转到stderr。我需要将它们写入文件。我找到了促进这一点的 FileHandler 类。但是,由于我有几十个类,所以我有几十个记录器名称。我是否需要将 FileHandler 添加到其中的每一个?这似乎需要做很多工作,尤其是当我决定在我的代码库中添加另一个类时。

我知道记录器有某种树状层次结构。这会自动发生吗?如果没有,我如何创建自己的层次结构?无论它是否是自动的,我应该在层次结构中的哪个位置添加我的 FileHandler 以便所有日志记录都转到同一个文件?

编辑:

根据@spdaley 提供的链接,我创建了以下 SSCCE:

记录SSCCE.java

package loggingsscce;

import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;

public class LoggingSSCCE {
    private static String LOG_FILE_NAME = "loggingsscce.log";

    public static void main(String[] args) throws IOException {
        LoggingSSCCE.initLogger();

        LogTest lta = new LogTestA();
        lta.doLog();

        LogTest ltb = new LogTestB();
        ltb.doLog();
    }

    private static void initLogger() throws IOException {
        FileHandler handler = null;

        try {
            boolean append = true;
            handler = new FileHandler(LoggingSSCCE.LOG_FILE_NAME, append);
            handler.setFormatter(new SimpleFormatter());

            Logger logger = Logger.getLogger("");
            logger.setLevel(Level.ALL);
            logger.addHandler(handler);
        } finally {
            handler.close();
        }
    }
}

日志测试.java

package loggingsscce;

interface LogTest {
    public void doLog();
}

LogTestA.java

package loggingsscce;

import java.util.logging.Level;
import java.util.logging.Logger;

class LogTestA implements LogTest {
    @Override
    public void doLog() {
        Logger.getLogger(LogTestA.class.getName()).log(Level.INFO, "LogTestA.doLog()");
    }
}

LogTestB.java

package loggingsscce;

import java.util.logging.Level;
import java.util.logging.Logger;

class LogTestB implements LogTest {
    @Override
    public void doLog() {
        Logger.getLogger(LogTestA.class.getName()).log(Level.INFO, "LogTestB.doLog()");
    }
}

当我在 NetBeans 中运行它时,输出窗口显示

run:
Sep 09, 2012 5:36:56 PM loggingsscce.LogTestA doLog
INFO: LogTestA.doLog()
Sep 09, 2012 5:36:56 PM loggingsscce.LogTestB doLog
INFO: LogTestB.doLog()
BUILD SUCCESSFUL (total time: 0 seconds)

但“loggingsscce.log”文件是空的。

那么我错过了什么?

另一个编辑:

我在http://onjava.com/pub/a/onjava/2002/06/19/log.html?page=2找到了一个我修改过的例子:

import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.LogManager;
import java.util.logging.SimpleFormatter;

public class HelloWorld {
  private static Logger theLogger = Logger.getLogger(HelloWorld.class.getName());

  public static void main( String[] args ) throws IOException {
    Logger rootLogger = Logger.getLogger("");
    Handler handler = new FileHandler("hello.log");

    handler.setFormatter(new SimpleFormatter());
    rootLogger.addHandler(handler);

    // The root logger's handlers default to INFO. We have to
    // crank them up. We could crank up only some of them
    // if we wanted, but we will turn them all up.
    Handler[] handlers = Logger.getLogger( "" ).getHandlers();
    for ( int index = 0; index < handlers.length; index++ ) {
      handlers[index].setLevel( Level.FINE );
    }

    // We also have to set our logger to log finer-grained
    // messages
    theLogger.setLevel(Level.FINE);
    HelloWorld hello = new HelloWorld("Hello world!");
    hello.sayHello();
  }

  private String theMessage;

  public HelloWorld(String message) {
    theMessage = message;
  }

  public void sayHello() {
    theLogger.fine("Hello logging!");
    System.err.println(theMessage);
  }
}

这会从命令行生成以下输出:

lib_lab_ref08@LBP-REF87XVMDP1 /e/devel/src/java/stackoverflow
$ javac HelloWorld.java

lib_lab_ref08@LBP-REF87XVMDP1 /e/devel/src/java/stackoverflow
$ java HelloWorld
Sep 09, 2012 6:13:33 PM HelloWorld sayHello
FINE: Hello logging!
Hello world!

lib_lab_ref08@LBP-REF87XVMDP1 /e/devel/src/java/stackoverflow
$ cat hello.log
Sep 09, 2012 6:13:33 PM HelloWorld sayHello
FINE: Hello logging!

lib_lab_ref08@LBP-REF87XVMDP1 /e/devel/src/java/stackoverflow
$

所以这个单类示例工作得很好。当我在以前的代码中在多个文件中有多个类时有什么区别?

对 LoggingSSCCE 类的修改:

我添加了一个静态字段:

private static FileHandler HANDLER = null;

并改变了initLogger()方法:

private static void initLogger() throws IOException {
    LoggingSSCCE.HANDLER = new FileHandler(LoggingSSCCE.LOG_FILE_NAME);
    LoggingSSCCE.HANDLER.setFormatter(new SimpleFormatter());

    Logger logger = Logger.getLogger("");
    logger.setLevel(Level.ALL);
    logger.addHandler(LoggingSSCCE.HANDLER);
}

这适用于“loggingsscce.log”的以下内容:

Sep 09, 2012 6:50:16 PM loggingsscce.LogTestA doLog
INFO: LogTestA.doLog()
Sep 09, 2012 6:50:16 PM loggingsscce.LogTestB doLog
INFO: LogTestB.doLog()

我仍然无法在我更复杂的 Swing 项目中完成这一切。我怀疑我的 FileHandler 可能被垃圾收集器关闭了?

4

2 回答 2

4

编辑:

原始代码中的问题是您提前关闭了处理程序。

原文:

我发现您的(原始)代码存在两个问题:

问题一,您调用getLogger("")的是 init,但调用getLogger(xxx.class.getName())的是您的实现。这些记录器中的每一个都是不同的记录器,您只需为它设置文件处理程序""

问题二,被问题一掩盖了,你提前关闭了处理程序。

您可以在一个文件中尝试多个类:

记录SSCCE.java

package loggingsscce;

import java.io.IOException;
import java.util.Hashtable;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;

public class LoggingSSCCE {
    private static String LOG_FILE_NAME = "loggingsscce.log";
    Hashtable<String, Logger> loggers = new Hastable<String, Logger>();
    FileHandler handler = null;

    public static void main(String[] args) throws IOException {

        LogTest lta = new LogTestA();
        lta.doLog();

        LogTest ltb = new LogTestB();
        ltb.doLog();
    }

    public static Logger getLogger(String loggerName) throws IOException {
        if ( loggers.get(loggerName) != null )
            return loggers.get(loggerName);

        if ( handler == null ) {
            boolean append = true;
            handler = new FileHandler(LoggingSSCCE.LOG_FILE_NAME, append);
            handler.setFormatter(new SimpleFormatter());
        }

        Logger logger = Logger.getLogger(loggerName);
        logger.setLevel(Level.ALL);
        logger.addHandler(handler);
        loggers.put(loggerName, logger);
        return logger;
    }
}

日志测试.java

package loggingsscce;

interface LogTest {
    public void doLog();
}

LogTestA.java

package loggingsscce;

import java.util.logging.Level;
import java.util.logging.Logger;

class LogTestA implements LogTest {
    @Override
    public void doLog() {
        LoggingSSCCE.getLogger(LogTestA.class.getName()).log(Level.INFO, "LogTestA.doLog()");
    }
}

LogTestB.java

package loggingsscce;

import java.util.logging.Level;
import java.util.logging.Logger;

class LogTestB implements LogTest {
    @Override
    public void doLog() {
        LoggingSSCCE.getLogger(LogTestB.class.getName()).log(Level.INFO, "LogTestB.doLog()");
    }
}

如果您希望这适用于每个类的一个文件或其他一些标准,只需修改LoggingSSCCE.getLogger为每种情况计算适当的文件名。

于 2012-09-10T00:28:35.517 回答
4

立即想到的是,看看log4j。它是一个开源日志框架,广泛用于各种库和应用程序。

编辑:

java.util.logging 是基本的 API。它真的不会做任何你没有明确告诉它在代码中做的事情。log4j 是一个具有完整配置工具和其他好东西的库。它位于 java.util.logging 之上,并使用比(相对)低级 java.util.logging 更易于使用的功能对其进行扩展。

于 2012-09-09T22:47:41.393 回答