为某些记录器设置日志级别,而不影响其他记录器

3
我正在开发一个Java应用程序,使用java.util.logging进行日志记录。该应用程序使用多个外部库(JDBC、JMS客户端、JSR-160实现等),其中一些库也使用java.util.logging。
当我在命令行上指定标志时,我想将我的 Loggers 的日志级别设置为ALL,但到目前为止,我只找到了设置所有logger级别的方法,而不仅仅是我的Logger。
我的所有Logger都被称为“com.mycompany.myapp.SomeClass”,当我将“com.mycompany.myapp”的级别设置为ALL时,没有额外的信息被记录。当我将根Logger的级别设置为ALL时,所有Logger的所有信息都会被记录到控制台,这是太多的信息!
如何在不让其他Logger淹没我的日志文件的情况下将我的Logger设置为ALL?

我认为你刚刚指出了java.util.logging相对于log4j等日志框架的主要缺陷之一。虽然我没有足够的经验来给出权威的答案或解决方法建议。 - araqnid
你的意思是说你对于 java.util.logging 不够了解而无法回答问题,但却认为这证明了 java.util.logging 的缺陷?呃……笑死了? - Jay
4个回答

4
实际上,我不确定为什么你会遇到你所描述的问题。我创建了一个简单的JUnit测试(如下),并且设置日志级别的工作方式与我的期望完全相同(这也似乎符合您预期的方式)。
您是否尝试在自定义记录器中记录低于INFO级别的消息?正如我包含的测试所示, 默认的日志处理程序默认设置为INFO。您需要更改该处理程序的级别才能查看FINE消息(也显示)。
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.Test;

public class SimpleLoggerTest {

    private void logMessages(Logger logger) {
        logger.warning(getLoggerName(logger) + ": warning message");
        logger.info(getLoggerName(logger) + ": info message");
        logger.fine(getLoggerName(logger) + ": fine message");
    }


    private String getLoggerName(Logger logger) {
        String loggerName = logger.getName();
        if (loggerName.isEmpty()) {
            return "[root logger]";
        }
        return loggerName;
    }

    private void listHandlerLevels(Logger logger) {
        for (Handler handler : logger.getHandlers()) {
            logger.info(getLoggerName(logger) + ": handler level = " + handler.getLevel());
        }
        Logger parentLogger = logger.getParent();
        if (null != parentLogger) {
            for (Handler handler : parentLogger.getHandlers()) {
                logger.info("parent logger handler (" + getLoggerName(parentLogger) + "): handler level = " + handler.getLevel());
            }
        }
    }

    private void setHandlerLevels(Logger logger, Level level) {
        for (Handler handler : logger.getHandlers()) {
            handler.setLevel(level);
        }
        Logger parentLogger = logger.getParent();
        if (null != parentLogger) {
            for (Handler handler : parentLogger.getHandlers()) {
                handler.setLevel(level);
            }
        }
    }
    @Test
    public void testLoggingLevel() {
        Logger myLogger = Logger.getLogger(SimpleLoggerTest.class.getName());
        Logger rootLogger = myLogger.getParent();

        // list the default handler levels
        listHandlerLevels(myLogger);
        listHandlerLevels(rootLogger);

        // log some messages
        logMessages(myLogger);
        logMessages(rootLogger);

        // change the logger levels
        myLogger.setLevel(Level.ALL);
        rootLogger.setLevel(Level.WARNING);

        // list the handler levels again
        listHandlerLevels(myLogger);
        listHandlerLevels(rootLogger);

        // log some messages (again)
        logMessages(myLogger);
        logMessages(rootLogger);

        // change Handler levels to FINE
        setHandlerLevels(myLogger, Level.FINE);

        // list the handler levels (last time)
        listHandlerLevels(myLogger);
        listHandlerLevels(rootLogger);

        // log some messages (last time)
        logMessages(myLogger);
        logMessages(rootLogger);
    }
}

生成以下输出...

May 13, 2009 10:46:53 AM SimpleLoggerTest listHandlerLevels
INFO: parent logger handler ([root logger]): handler level = INFO
May 13, 2009 10:46:53 AM java.util.logging.LogManager$RootLogger log
INFO: [root logger]: handler level = INFO
May 13, 2009 10:46:53 AM SimpleLoggerTest logMessages
WARNING: SimpleLoggerTest: warning message
May 13, 2009 10:46:53 AM SimpleLoggerTest logMessages
INFO: SimpleLoggerTest: info message
May 13, 2009 10:46:53 AM java.util.logging.LogManager$RootLogger log
WARNING: [root logger]: warning message
May 13, 2009 10:46:53 AM java.util.logging.LogManager$RootLogger log
INFO: [root logger]: info message
May 13, 2009 10:46:53 AM SimpleLoggerTest listHandlerLevels
INFO: parent logger handler ([root logger]): handler level = INFO
May 13, 2009 10:46:53 AM SimpleLoggerTest logMessages
WARNING: SimpleLoggerTest: warning message
May 13, 2009 10:46:53 AM SimpleLoggerTest logMessages
INFO: SimpleLoggerTest: info message
May 13, 2009 10:46:53 AM java.util.logging.LogManager$RootLogger log
WARNING: [root logger]: warning message
May 13, 2009 10:46:53 AM SimpleLoggerTest listHandlerLevels
INFO: parent logger handler ([root logger]): handler level = FINE
May 13, 2009 10:46:53 AM SimpleLoggerTest logMessages
WARNING: SimpleLoggerTest: warning message
May 13, 2009 10:46:53 AM SimpleLoggerTest logMessages
INFO: SimpleLoggerTest: info message
May 13, 2009 10:46:53 AM SimpleLoggerTest logMessages
FINE: SimpleLoggerTest: fine message
May 13, 2009 10:46:53 AM java.util.logging.LogManager$RootLogger log
WARNING: [root logger]: warning message

这就是我试图在我的另一篇回答中表达的内容。


我已使用您的测试程序并使用另一个记录器“theirLogger”进行了扩展,并调用了logMessages()以记录它。一切似乎都按预期工作。我将不得不重新思考我的java.util.logging理解,显然。 - Gerco Dries
1
好的...我想通了。我得出结论,那些认为处理程序神奇地“复制”到所有子记录器并使它们的级别比记录器级别更重要的人要么是疯了,要么是试图让其他人疯掉!无论如何,现在它可以工作了。我要做的第一件事就是找一个对普通人有意义的不同日志框架。 - Gerco Dries

2

是的,JDK自带的日志框架有时确实很麻烦。正如您正确注意到的那样,根记录器处理程序的日志级别是问题所在。您在问题中提出的解决方案几乎是一个好方法:

Logger logger = Logger.getLogger("com.mycompany.myapp");
Handler handler = new ConsoleHandler(); /* or whatever you like. */
handler.setLevel(Level.ALL);
logger.addHandler(handler);
logger.setLevel(Level.ALL);
logger.setUseParentHandlers(false); /* <- important. */

当你创建一个名为“com.mycompany.myapp.foo.Foo”的记录器时,它将仅记录到你创建的记录器中。该记录器的父记录器(根记录器)将不会从你的应用程序接收任何日志消息,但将接收来自JDK其他部分(如Swing或AWT)的消息。


这似乎工作得非常好!我唯一剩下的问题是,我的文件处理程序附加到了自己的“根”记录器,并且每当另一个记录器有关键消息时,它将仅记录到控制台而不是我的日志文件中。我想我可以接受这个问题,因为这种情况极为罕见。 - Gerco Dries
我相信这个代码是可行的,但是如果你需要调整日志级别,就需要重新编译代码,这会让调整变得困难。 - Kris
Gerco,你可以尝试将文件处理程序附加到根记录器上。或者你可以从你的shell中捕获应用程序的stdout。 - Bombe
Kris,你到底为什么不能通过迭代所有日志记录器并根据某个配置文件设置它们的级别?请注意,我上面建议的只是基本设置,以将应用程序和JDK日志记录分开。你仍然可以使用Logging API执行各种有趣的技巧。 - Bombe

2
每个记录器都有一个处理程序,具有自己的日志级别
你的记录器的处理程序可能没有设置为ALL,而是忽略了这些消息吗?(这很混乱,我知道)。
这里是O'Reilly关于Java日志记录的2002年文章

当我将我的记录器的日志级别设置为ALL并保留根记录器时,什么都没有发生。如果我将根记录器和处理程序的级别也设置为ALL,我将从比我的更多的记录器中接收到大量消息。 - Gerco Dries
1
没错,我只是建议您将日志级别设置为ALL,应用于与您的记录器相关的处理程序。这些处理程序可能将其级别设置为INFO或更高级别,并忽略您的CONFIG或FINE消息。我不知道这是否能解决您的问题,但这是一个很容易尝试的方法。您可以使用Logger.getHandlers()访问处理程序。 - Vinnie
我的自定义日志记录器没有任何关联的处理程序。所有输出都通过根记录器上的控制台处理程序完成。我应该删除该处理程序并向我的“根”记录器(com.mycompany.myapp)添加新的处理程序吗?如果其中任何一个记录器有严重或警告级别的报告,会发生什么? - Gerco Dries
只有一个评论...如果根记录器设置为类似WARN的级别,但它的处理程序设置为ALL,而您的Logger设置为ALL,我认为这应该可以运行...您将获得所有消息以及其他包中高于或等于WARN级别的消息。 - Vinnie
谢谢您的建议,但是它并没有按预期工作。根记录器只是丢弃日志事件,因为它们低于WARN级别。完全删除根记录器处理程序,并将处理程序添加到自己的“根”记录器中,这样就可以正常工作了。现在我不再从任何其他包中收到错误或警告,所以将来可能仍然存在问题。也许我会添加一个-vv模式来重新启用那些记录器。 - Gerco Dries

0

设置属性

com.mycompany.myapp.level = ALL

应该做你想要的事情。

但是所有记录器都需要使用相关的类名正确地命名!例如:

package com.mycompany.myapp;

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

另一个可能的解释是您的配置没有正确传递到日志框架。尝试提供更多信息。
LogManager.getLogManager().readConfiguration(InputStream);

在启动时配置您的日志记录。这就是我所做的,对我来说很有效。


1
这对于名为com.mycompany.myapp.foo.Foo的类也适用吗? - willcodejavaforfood
抱歉,但这不起作用。根记录器忽略消息,因为它的级别(也是处理程序级别)设置为INFO。当我将它们也设置为ALL时,我会收到所有记录器的所有消息,而不仅仅是我的。 - Gerco Dries

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