【问题标题】:what's log4j actually doing when we turn on or off some log places?当我们打开或关闭一些日志位置时,log4j 实际上在做什么?
【发布时间】:2010-02-09 15:29:47
【问题描述】:

我们知道我们可以通过其属性/配置文件配置 log4j 以关闭特定位置(Java 中的类或包)的日志。我的问题如下:

  1. log4j 实际上对这些标志做了什么?
  2. log4j 中的日志语句是否仍然被调用,但由于该标志而没有被写入文件或控制台?那么还有性能影响吗?
  3. 是不是像 C++ 中的#ifdef 在编译时生效然后可以限制性能影响?

谢谢,

【问题讨论】:

    标签: java log4j


    【解决方案1】:

    是的,日志语句仍将被执行。这就是为什么首先检查日志级别是一个很好的模式:类似于

    if (log.isInfoEnabled()) {
        log.info("My big long info string: " + someMessage);
    }
    

    这是为了避免在日志级别不支持INFO 语句时为信息String 重新分配空间。

    它不像 #ifdef - #ifdef 是编译器指令,而 Log4J 配置是在运行时处理的。

    编辑:我讨厌因为无知而被降级,所以这里有一篇文章支持我的回答。

    来自http://surguy.net/articles/removing-log-messages.xml:

    在 Log4J 中,如果您在 DEBUG 级别和当前 Appender 设置为仅记录 INFO 的消息 级别及以上,则消息将 不显示。表现 调用 log 方法的惩罚 本身是最小的 - 几纳秒。 但是,可能需要更长的时间 评估日志的参数 方法。例如:

    logger.debug("大对象是 "+largeObject.toString());

    评估 largeObject.toString() 可能 慢一点,它在之前被评估过 对记录器的调用,所以记录器 不能阻止它被评估, 即使它不会被使用。

    编辑 2:来自 log4j 手册本身 (http://logging.apache.org/log4j/1.2/manual.html):

    用户应注意以下性能问题。

    1. 关闭日志记录时的日志记录性能。 当日志完全关闭或仅关闭一组级别时,日志请求的成本包括方法调用和整数比较。在 233 MHz Pentium II 机器上,此成本通常在 5 到 50 纳秒范围内。

      但是,方法调用涉及参数构造的“隐藏”成本。

      比如对于一些logger cat,写,

       logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
      

      会产生构造消息参数的成本,即将整数 i 和 entry[i] 转换为字符串,并连接中间字符串,无论是否记录消息。参数构造的成本可能相当高,并且取决于所涉及参数的大小。

      为了避免参数构造成本写:

      if(logger.isDebugEnabled() {
        logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
      }
      

      如果禁用调试,则不会产生参数构造成本。另一方面,如果记录器启用了调试,则评估记录器是否启用会产生两倍的成本:一次在 debugEnabled 中,一次在调试中。这是一个微不足道的开销,因为评估记录器大约需要实际记录所需时间的 1%。

    【讨论】:

    • 实际上,将所有日志语句包装到 if 语句中并不是一个好主意。日志记录应该尽可能少的工作,这就是为什么你有一个日志记录框架。此外,如果日志语句(意外)有副作用,您将使调试与发布不同。只有当你这样做一百万次时,你才应该添加 if。也有可能虚拟机已经优化了这个从未使用过的代码。 (顺便说一句,它被评估的评论当然是正确的)
    • 抱歉,您完全不了解情况。虚拟机不可能优化这一点,因为语句每次都被执行。您不需要“一百万”个日志语句来降低性能,您只需要一个循环中的日志语句。在这种情况下,“日志记录应该尽可能少的工作”是一个毫无意义的陈述——尤其是考虑到 OP 专门询问性能。
    • 我同意danben,在某些情况下。如果您想连续记录 10 行包含大字符串(或复杂的 toString())调用的行,将其包装在 if 中是个好主意。我认为不应该投反对票。
    • downmod 有点太苛刻了,所以我会删除它。但是,对我来说,您的帖子似乎建议在记录之前始终检查日志级别。我还是强烈反对,也许你应该补充一点,只有当日志语句是性能问题时才应该这样做。请注意,Java 1.6 中添加了许多关于方法调用的优化(例如自动内联)。我建议这可能是其中之一(它是对一个什么都不做的函数的调用,其中参数的评估没有副作用)
    • 我最喜欢的“优化高尔夫”技术是从紧密循环中删除调试日志语句。 1 项任务的开销似乎很小,但“无害”代码的净效应会以更高的数量级快速累积。
    【解决方案2】:

    我运行了一个简单的基准测试。

        for (int j = 0; j < 5; j++) {
            long t1 = System.nanoTime() / 1000000;
            int iterations = 1000000;
            for (int i = 0; i < iterations; i++) {
                int test = i % 10;
                log.debug("Test " + i + " has value " + test);
            }
            long t2 = System.nanoTime() / 1000000;
            log.info("elapsed time: " + (t2 - t1));
    
            long t3 = System.nanoTime() / 1000000;
            for (int i = 0; i < iterations; i++) {
                int test = i % 10;
                if (log.isDebugEnabled()) {
                    log.debug("Test " + i + " has value " + test);
                }
            }
            long t4 = System.nanoTime() / 1000000;
            log.info("elapsed time 2: " + (t4 - t3));
        }
    
    elapsed time: 539
    elapsed time 2: 17
    elapsed time: 450
    elapsed time 2: 18
    elapsed time: 454
    elapsed time 2: 19
    elapsed time: 454
    elapsed time 2: 17
    elapsed time: 450
    elapsed time 2: 19
    

    对于 1.6.0_18,这让我感到惊讶,因为我原以为内联会阻止这种情况发生。也许带有逃逸分析的 Java 7 会。

    但是我仍然不会将调试语句包装在 if 子句中,除非半微秒级的时间改进变得很重要!

    【讨论】:

      【解决方案3】:
      1. log4j 将处理日志语句,并检查特定记录器是否在特定日志记录级别启用。如果不是,则不会记录该语句。

      2. 这些检查比实际写入磁盘(或控制台)的成本要低得多,但它们仍然会产生影响。

      3. 不,java 没有 #ifdef 这样的概念(反正开箱即用,有 java 预编译器)

      【讨论】:

        猜你喜欢
        • 2015-03-21
        • 2016-02-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-12-05
        • 2015-08-15
        • 1970-01-01
        • 2021-04-07
        相关资源
        最近更新 更多