在 Java 应用程序中实现日志记录

Posted

技术标签:

【中文标题】在 Java 应用程序中实现日志记录【英文标题】:Implementing logging in a Java application 【发布时间】:2012-09-09 22:42:49 【问题描述】:

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

我正在开发一个 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:

LoggingSSCCE.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();
        
    

LogTest.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/***
$ javac HelloWorld.java

lib_lab_ref08@LBP-REF87XVMDP1 /e/devel/src/java/***
$ 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/***
$ cat hello.log
Sep 09, 2012 6:13:33 PM HelloWorld sayHello
FINE: Hello logging!

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

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

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 可能被垃圾收集器关闭了?

【问题讨论】:

听起来您正在寻找像 log4j 这样的日志库。 听起来您正在尝试使用标准 Java 日志记录而不是 log4j。我会查看How to get java Logger output to file by default 看看是否能回答您的问题。 @spdaley 感谢您的链接。这个问题正是我想要做的。不幸的是,给出的答案对我不起作用。我已经发布了一个 SSCCE 来说明。 【参考方案1】:

编辑:

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

原文:

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

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

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

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

LoggingSSCCE.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;
    

LogTest.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 以为每个案例计算适当的文件名。

【讨论】:

"问题一,您在 init 中调用 getLogger(""),但在实现中调用 getLogger(xxx.class.getName())。每个记录器都是不同的记录器,并且您只是为“”设置文件处理程序。 HelloWorld 示例执行完全相同的操作并且工作正常。我相信在 finally 块中处理程序的过早关闭是问题所在。让我检查一下。 好吧,我又近了一步。我过早地关闭了 FileHandler。 删除handler.close() 调用后,它工作正常。我认为您的 Hashmap 代码或类似代码已在 java.util.logging API 中实现。 好吧,HelloWorld 示例调用了两次getLogger("") 并且两次都获取了相同的记录器,所以不一样。另一方面,默认情况下,记录器还将其输出发送到父处理程序,因此您可能是对的,“问题之一”可能不是问题。至于我的代码中的 Hashtable,它只是在那里,所以我不会多次将相同的处理程序添加到同一个记录器中。如果您只是将它添加到***记录器 (getLogger("")),是的,不用担心。 它还调用private static Logger theLogger = Logger.getLogger(HelloWorld.class.getName());,这是用于实际记录消息的记录器。因此,它是相同的。 (main() 中对getLogger("") 的多次调用可以通过引用变量进行优化。这是我在此处修改并发布的原始示例的产物——当然不是我喜欢的编码风格。)【参考方案2】:

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

编辑:

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

【讨论】:

在哪里可以找到有关 java.util.logging 和 log4j 之间差异的更多信息?你知道有什么网站可以直接比较吗? java.util.logging 是基本 API。它真的不会做任何你没有明确告诉它在代码中做的事情。 log4j 是一个具有完整配置工具和其他好东西的库。它位于 java.util.logging 之上,并使用比(相对)低级 java.util.logging 更易于使用的功能对其进行扩展 感谢您的信息。我一定会研究 log4j。但是,我的问题仍然没有答案。我在我的问题中添加了一些代码来说明我正在尝试使用 java.util.logging API 做什么。 @Hovercraft Full Of Eels:完成。感谢您的建议。 我将在新答案中添加我对您更具体的编码问题的答案,因为它更容易阅读。

以上是关于在 Java 应用程序中实现日志记录的主要内容,如果未能解决你的问题,请参考以下文章

spring mvc的项目中实现aop日志记录

springboot中实现rabbitmq异步日志记录功能

是否可以使用控制台应用程序在 .net framework 4.5 中实现相扑逻辑?如果是,我们如何实施

在生产环境中实现最佳性能的 APR

jmeter中实现java请求实战日志

AngularJS中实现日志服务