用Java处理调试输出的正确方法是什么?


32

随着我目前的Java项目变得越来越大,我感到同样需要在代码的多点插入调试输出。

为了适当地启用或禁用此功能,具体取决于测试会话的打开或关闭,我通常private static final boolean DEBUG = false在测试要检查的类的开头放置a ,然后以这种方式简单地使用它(例如):

public MyClass {
  private static final boolean DEBUG = false;

  ... some code ...

  public void myMethod(String s) {
    if (DEBUG) {
      System.out.println(s);
    }
  }
}

等等。

但这并不能使我感到高兴,因为它当然有效,但是如果您不仅仅盯着其中的几个,可能会有太多的类将DEBUG设置为true。

相反,我(就像-我认为-许多其他人一样)不希望将整个应用程序置于调试模式,因为输出的文本数量可能很庞大。

那么,有没有一种正确的方法来处理这种情况,或者最正确的方法是使用DEBUG类成员?


14
在Java中,正确的方法是不要使用自制代码进行日志记录。选择一个已建立的框架,不要重新发明轮子
gna

出于与您所述相同的原因,我在一些更复杂的类中使用了布尔型DEBUG。我通常不希望调试整个应用程序,而只是给我麻烦的类。这种习惯来自我的COBOL时代,其中DISPLAY语句是唯一可用的调试形式。
吉尔伯特·勒布朗克

1
我还建议在可能的情况下更多地依赖调试器,并且不要在调试语句中乱扔代码。
Andrew T Finnell

1
您是否在单元测试中练习测试驱动开发(TDD)?一旦开始这样做,我注意到“调试代码”的大量减少。
2012年

Answers:


52

您想查看一个日志记录框架,也许还要查看一个日志记录外观框架。

那里有多个日志记录框架,通常具有重叠的功能,以至于随着时间的流逝,许多日志记录框架演变为依赖于公共API,或者已经通过立面框架使用以抽象化它们的使用并允许将它们交换到位如果需要的话。

构架

一些记录框架

一些伐木立面

用法

基本范例

这些框架中的大多数将允许您编写某种形式的东西(在此处使用slf4j-apilogback-core):

package chapters.introduction;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// copied from: http://www.slf4j.org/manual.html
public class HelloWorld {

  public static void main(String[] args) {
    final Logger logger = LoggerFactory.getLogger(HelloWorld.class);

    logger.debug("Hello world, I'm a DEBUG level message");
    logger.info("Hello world, I'm an INFO level message");
    logger.warn("Hello world, I'm a WARNING level message");
    logger.error("Hello world, I'm an ERROR level message");
  }
}

请注意,使用当前类来创建专用的记录器,这将允许SLF4J / LogBack格式化输出并指示记录消息来自何处。

SLF4J手册中所述,类中的典型使用模式通常是:

import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  

public class MyClass {

    final Logger logger = LoggerFactory.getLogger(MyCLASS.class);

    public void doSomething() {
        // some code here
        logger.debug("this is useful");

        if (isSomeConditionTrue()) {
            logger.info("I entered by conditional block!");
        }
    }
}

但实际上,使用以下形式声明记录器甚至更常见:

private static final Logger LOGGER = LoggerFactory.getLogger(MyClass.class);

这也允许在静态方法中使用记录器,并且在类的所有实例之间共享记录器。这很可能是您的首选表格。但是,正如Brendan Long在评论中指出的那样,您希望确保一定要理解其中的含义并做出相应的决定(这适用于遵循这些习惯用法的所有日志记录框架)。

还有其他实例化记录器的方法,例如,使用字符串参数创建命名记录器:

Logger logger = LoggerFactory.getLogger("MyModuleName");

调试级别

调试级别因一个框架而异,但常见的是(按关键程度排列,从良性到严重损坏,从可能非常普遍到希望非常罕见):

  • TRACE 非常详细的信息。应该只写入日志。仅用于在检查点跟踪程序的流程。

  • DEBUG 详细资料。应该只写入日志。

  • INFO 值得注意的运行时事件。应该立即在控制台上可见,因此请谨慎使用。

  • WARNING 运行时异常和可恢复的错误。

  • ERROR 其他运行时错误或意外情况。

  • FATAL 严重错误导致过早终止。

街区和守卫

现在,假设您有一个代码部分,您将在其中编写许多调试语句。这可能会很快影响您的性能,这既是因为日志本身的影响,也可能是您可能传递给日志方法的任何参数的生成所致。

为了避免此类问题,您通常需要编写以下形式的内容:

if (LOGGER.isDebugEnabled()) {
   // lots of debug logging here, or even code that
   // is only used in a debugging context.
   LOGGER.debug(" result: " + heavyComputation());
}

如果您在调试语句块之前没有使用过此防护程序,即使可能未输出消息(例如,如果您的记录器当前配置为仅打印INFO级别以上的内容),则该heavyComputation()方法仍将执行。

组态

配置完全取决于您的日志记录框架,但是它们为此提供了大多数相同的技术:

  • 编程配置(在运行时,通过API- 允许更改运行时),
  • 静态声明式配置(在启动时,通常是通过XML或属性文件- 可能一开始就是您所需要的)。

它们还提供大多数相同的功能:

  • 输出消息格式的配置(时间戳,标记等),
  • 输出电平的配置
  • 配置细粒度的过滤器(例如,包含/排除包或类),
  • 配置附加程序以确定日志记录的位置(控制台,文件,Web服务...),以及可能处理较旧的日志(例如,使用自动滚动文件)的方法。

这是使用logback.xml文件的声明式配置的常见示例。

<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

如前所述,这取决于您的框架,并且可能还有其他选择(例如,LogBack还允许使用Groovy脚本)。XML配置格式也可能因一种实现方式而异。

有关更多配置示例,请(除其他外)参考:

一些历史的乐趣

请注意,Log4J的是看到此刻的一次重大更新,从版本过渡的1.x2.x的。您可能希望同时查看两者,以获得更多的历史乐趣或困惑,如果选择Log4J,则可能更喜欢使用2.x版本。

正如Mike Partridge在评论中提到的那样,值得注意的是LogBack是由Log4J的前团队成员创建的。创建它是为了解决Java Logging框架的缺点。而且即将推出的主要Log4J 2.x版本本身现在已经集成了LogBack的一些功能。

建议

最重要的是,尽量保持解耦,尝试一些,然后看看最适合您的方法。最后,它只是一个日志记录框架。除非您有非常具体的原因,除了易于使用和个人喜好之外,这些方法中的任何一个都可以,所以没有必要将其挂在身上。它们中的大多数也可以扩展到您的需求。

不过,如果我今天必须选择一个组合,我会选择LogBack + SLF4J。但是,如果几年后您问我,我会建议将Log4J与Apache Commons Logging一起使用,因此请密切注意您的依赖关系并与之一起发展。


1
SLF4J和LogBack由最初编写Log4J的人编写。
Mike Partridge

4
对于那些可能担心日志记录的性能影响的人:slf4j.org/faq.html#logging_performance
Mike Partridge

2
值得一提的是,是否应该制作记录器并不太明确static,因为它可以节省少量内存,但是在某些情况下会引起问题:slf4j.org/faq.html#declared_static
恢复Monica

1
@MikePartridge:我知道链接的内容,但是它仍然不会阻止参数评估。Byt参数化日志记录性能更高的原因是因为不会发生对日志消息的处理(字符串特别是串联)。但是,如果将任何方法调用作为参数传递,则将执行该方法调用。因此,根据您的用例,块可能很有用。就像文章中提到的那样,它们也可以对您有用,仅在启用DEBUG级别时将其他与调试相关的活动分组(不仅是日志记录)。
haylem 2012年

1
@haylem-是的,我的错。
迈克·帕特里奇

2

使用日志框架

大多数时候有一种静态工厂方法

private static final Logger logger = Logger.create("classname");

那么您可以输出不同级别的日志记录代码:

logger.warning("error message");
logger.info("informational message");
logger.trace("detailed message");

那么将只有一个文件,您可以在其中定义每个类的哪些消息应写入日志输出(文件或stderr)



0

日志记录框架绝对是必经之路。但是,您还必须具有良好的测试套件。良好的测试覆盖范围通常可以消除对调试输出的需求。


如果您使用日志记录框架并提供了调试日志记录,那么将有一段时间使您免于经历非常糟糕的一天。
Fortyrunner

1
我不是说你不应该记录日志。我说过您需要先进行测试。
迪马2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.