为什么级别为FINE的日志信息没有显示?

131

Java.util.logging.Level的JavaDocs中提到:


按降序排列的级别依次为:

  • SEVERE(最高值)
  • WARNING
  • INFO
  • CONFIG
  • FINE
  • FINER
  • FINEST(最低值)

来源

import java.util.logging.*;

class LoggingLevelsBlunder {

    public static void main(String[] args) {
        Logger logger = Logger.getAnonymousLogger();
        logger.setLevel(Level.FINER);
        System.out.println("Logging level is: " + logger.getLevel());
        for (int ii=0; ii<3; ii++) {
            logger.log(Level.FINE, ii + " " + (ii*ii));
            logger.log(Level.INFO, ii + " " + (ii*ii));
        }
    }
}

输出

Logging level is: FINER
Jun 11, 2011 9:39:23 PM LoggingLevelsBlunder main
INFO: 0 0
Jun 11, 2011 9:39:24 PM LoggingLevelsBlunder main
INFO: 1 1
Jun 11, 2011 9:39:24 PM LoggingLevelsBlunder main
INFO: 2 4
Press any key to continue . . .

问题陈述

我的示例将Level设置为FINER,所以我期望每次循环都会看到2条消息。但实际上,每次循环只显示一条消息(Level.FINE的消息消失了)。

问题

需要进行哪些更改才能看到FINE(或FINERFINEST)输出?

更新(解决方案)

感谢Vineet Reynolds的答案,这个版本符合我的预期。它显示3 x INFO消息和3 x FINE消息。

import java.util.logging.*;

class LoggingLevelsBlunder {

    public static void main(String[] args) {
        Logger logger = Logger.getAnonymousLogger();
        // LOG this level to the log
        logger.setLevel(Level.FINER);

        ConsoleHandler handler = new ConsoleHandler();
        // PUBLISH this level
        handler.setLevel(Level.FINER);
        logger.addHandler(handler);

        System.out.println("Logging level is: " + logger.getLevel());
        for (int ii=0; ii<3; ii++) {
            logger.log(Level.FINE, ii + " " + (ii*ii));
            logger.log(Level.INFO, ii + " " + (ii*ii));
        }
    }
}

12
在我的看法中,您将会看到在控制台上打印INFO级别及以上消息两次:首先是由匿名日志记录器打印,然后是由其父级全局日志记录器打印,该日志记录器默认设置为INFO级别并带有一个ConsoleHandler。如果要禁用全局日志记录器,您需要添加以下代码行:logger.setUseParentHandlers(false); - mins
我只是想确认一下关于doubles的mins评论。除非您使用.setUseParentHandlers(false),否则您将获得两个输出。 - xpagesbeast
在您的解决方案中,logger 有两个处理程序(一个默认为 INFO,另一个添加为 FINE),因此 logger.info("something") 将打印两次。 - limestreetlab
添加日志属性文件并设置配置的目的是什么?最终不都由根记录器控制吗?@andrew-thompson - Mehul Parmar
8个回答

140

日志记录器(Loggers)仅记录消息,即它们创建日志记录(或者称为日志请求)。它们不会将消息发布到目标位置,这由处理器(Handlers)来处理。设置日志记录器的级别只会使其创建匹配该级别或更高级别的日志记录。

你可能正在使用一个ConsoleHandler(我无法推断你的输出是System.err还是文件,但我假设它是前者),其默认发布级别为Level.INFO的日志记录。你需要配置此处理器,以发布级别为Level.FINER及更高级别的日志记录,以获得所需的结果。

我建议阅读Java Logging Overview指南,以了解底层设计。该指南介绍了日志记录器和处理器的概念之间的区别。

编辑处理器的级别

1. 使用配置文件

可以修改java.util.logging属性文件(默认情况下,在JRE_HOME/lib中的logging.properties文件中)来更改ConsoleHandler的默认级别:

java.util.logging.ConsoleHandler.level = FINER

2. 在运行时创建处理程序

这并不是推荐的方法,因为它会覆盖全局配置。在整个代码库中使用此方法可能会导致难以管理的日志记录器配置。

Handler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.FINER);
Logger.getAnonymousLogger().addHandler(consoleHandler);

2
谢谢。那解决了问题。我现在意识到为什么一开始会如此困惑。我之前使用过日志级别,但当时我的实现只是将每个记录的消息放入一个列表中,并显示出来,而没有考虑处理程序。 - Andrew Thompson
3
不客气。如果一个人一直以来都只是将字符串输出到文件、控制台等记录器中,那么设计确实会影响到你。 - Vineet Reynolds
11
更改全局 JRE 库中的 logging.properties 可以管理吗? - James Anderson
3
请注意,记录器本身的级别必须比处理程序的级别宽松。在记录 Java 日志之前,Java会检查记录器和处理程序之间的最大级别。因此,如果您只设置了handler.setLevel(Level.FINER); 您将看不到任何日志信息,因为默认的记录器级别是Level.INFO。您必须将其设置为FINER、FINEST 或 ALL。例如,logger.setLevel(Level.ALL); - Jeff_Alieffson
1
我猜这个一行代码就可以解决问题,即使你在默认环境下使用匿名和命名的日志记录器: Logger.getGlobal().getParent().getHandlers()[0].setLevel(Level.FINER); - Mark Jeronimus

32

原因

java.util.logging有一个根记录器,默认为Level.INFO,并附加了一个ConsoleHandler,该处理程序也默认为Level.INFOFINE低于INFO,因此默认情况下不会显示详细信息。


解决方案1

为整个应用程序创建记录器,例如使用您的包名称或使用Logger.getGlobal(),并钩接您自己的ConsoleLogger。 然后,要么要求根记录器保持安静(以避免更高级别消息的重复输出),要么要求您的记录器不将日志转发到根记录器。

public static final Logger applog = Logger.getGlobal();
...

// Create and set handler
Handler systemOut = new ConsoleHandler();
systemOut.setLevel( Level.ALL );
applog.addHandler( systemOut );
applog.setLevel( Level.ALL );

// Prevent logs from processed by default Console handler.
applog.setUseParentHandlers( false ); // Solution 1
Logger.getLogger("").setLevel( Level.OFF ); // Solution 2

解决方案2

另外一种方法是,您可以降低根日志记录器的门槛。

您可以通过代码进行设置:


Logger rootLog = Logger.getLogger("");
rootLog.setLevel( Level.FINE );
rootLog.getHandlers()[0].setLevel( Level.FINE ); // Default console handler

如果您使用日志配置文件,则可以:

.level = FINE
java.util.logging.ConsoleHandler.level = FINE

通过降低全局级别,您可能会开始看到来自核心库的消息,例如来自某些Swing或JavaFX组件。 在这种情况下,您可以在根记录器上设置过滤器以过滤掉不来自您的程序的消息。


applog.setUseParentHandlers(false) 这段代码帮了我很多,非常感谢。 - aolphn

11

为什么

正如@Sheepy所述,它不起作用的原因是java.util.logging.Logger具有默认为Level.INFO的根记录器,并且连接到该根记录器的ConsoleHandler也默认为Level.INFO。因此,为了查看FINEFINERFINEST)输出,您需要将根记录器及其ConsoleHandler的默认值设置为Level.FINE,如下所示:

Logger.getLogger("").setLevel(Level.FINE);
Logger.getLogger("").getHandlers()[0].setLevel(Level.FINE);


更新问题的解决方案

如@mins所述,对于INFO及以上级别,您将在控制台上打印两次消息:首先是匿名记录器,然后是其父记录器,即根记录器,它还默认设置为ConsoleHandlerINFO. 要禁用根记录器,您需要添加这行代码:logger.setUseParentHandlers(false);

还有其他方法可以防止日志被默认控制台处理程序处理,例如@Sheepy提到的:

Logger.getLogger("").getHandlers()[0].setLevel( Level.OFF );

但是 Logger.getLogger("").setLevel( Level.OFF ); 不起作用,因为它只会阻止直接传递给根记录器的信息,而不会阻止来自子记录器的信息。为了说明 Logger 层次结构 的工作原理,我画了下面的图表:

enter image description here

public void setLevel(Level newLevel) 设置日志级别,指定将由此记录器记录的消息级别。低于此值的消息级别将被丢弃。级别值 Level.OFF 可用于关闭日志记录。如果新级别为 null,则表示该节点应从具有特定(非空)级别值的最近祖先继承其级别。


4

为什么我的Java日志记录不起作用

提供一个jar文件,它将帮助您确定为何您的日志记录未按预期工作。 它会为您提供完整的日志记录器和处理程序安装以及设置的级别以及它们在日志记录层次结构中所处的级别的转储。


1
这是一条注释,而不是答案。 - hfontanez

1
尝试了其他变体,这可能是正确的。
    Logger logger = Logger.getLogger(MyClass.class.getName());        
    Level level = Level.ALL;
    for(Handler h : java.util.logging.Logger.getLogger("").getHandlers())    
        h.setLevel(level);
    logger.setLevel(level);
// this must be shown
    logger.fine("fine");
    logger.info("info");

本答案和Joe Yichong的指出,记录器首先获取日志消息,然后将消息传递到处理程序。这很令人困惑,但在实践中运行良好。总之,大多数处理程序实际上都在根级别,因此操作员可以更容易地控制它们。当我需要控制程序中的日志记录时(通常不应该这样做),我会使用获取根记录器并设置其所有处理程序的技术。 - markspace

0

对我来说,这个解决方案在可维护性和设计变更方面看起来更好:

  1. 在资源项目文件夹中创建日志属性文件,以嵌入到jar文件中:

    # 日志
    handlers = java.util.logging.ConsoleHandler
    .level = ALL
    
    # 控制台日志
    java.util.logging.ConsoleHandler.level = ALL
    
  2. 从代码中加载属性文件:

    public static java.net.URL retrieveURLOfJarResource(String resourceName) {
       return Thread.currentThread().getContextClassLoader().getResource(resourceName);
    }
    
    public synchronized void initializeLogger() {
       try (InputStream is = retrieveURLOfJarResource("logging.properties").openStream()) {
          LogManager.getLogManager().readConfiguration(is);
       } catch (IOException e) {
          // ...
       }
    }
    

0

我找到了实际问题,而且没有在任何答案中提到:我的一些单元测试导致日志初始化代码在同一个测试套件内运行多次,从而破坏了后续测试的日志记录。


0

更改Logcat级别:

adb shell setprop log.tag.<YOUR_LOG_TAG> <LEVEL>

例如:

C:\Users\my_name\AppData\Local\Android\Sdk\platform-tools\adb.exe shell setprop log.tag.com.mycompany.myapp VERBOSE

以下是为什么这对Logger有效的原因:

根记录器是您记录器的父记录器,它使用一个内部日志处理程序com.android.internal.logging.AndroidHandler。在其publish方法中,此处理程序检查Log.isLoggable并写入日志。

关于<YOUR_LOG_TAG>的注意事项:

基本上,这是您的Logger的名称。对于全局Logger,它是Logger.GLOBAL_LOGGER_NAME。对于匿名记录器,它是"null"。标签限制为23个字符。
AndroidHandler将记录器名称转换为标记:

    private static String loggerNameToTag(String loggerName) {
        // Anonymous logger.
        if (loggerName == null) {
            return "null";
        }

        int length = loggerName.length();
        if (length <= 23) {
            return loggerName;
        }

        int lastPeriod = loggerName.lastIndexOf(".");
        return length - (lastPeriod + 1) <= 23
                ? loggerName.substring(lastPeriod + 1)
                : loggerName.substring(loggerName.length() - 23);
    }

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