isInfoEnabled究竟多有用?

前段时间,公司里组织了一次代码检查,其中有一条检查项让我有些费解:

所有INFO和DEBUG级别的日志,必须加上isInfoEnabled和isDebugEnabled的判断。

理由是大量的不输出的日志对性能会有影响(日志中存在字符串拼接)。如果说只是DEBUG的加上,我也就认了,可是在系统中写成INFO的日志如果不输出,那还写它干嘛,我就是想看到关键路径的日志。而且在大多数日志上加上这么一个判断真的很难看。。。

 

所谓上有政策,下有对策,于是有人开始写一些包装了判断的辅助类,当中用这样的代码(截取):

public class LogUtil {

    public static void info(Logger logger, String message) {
        if (logger.isInfoEnabled()) {
            logger.info(message);
        }
    }

    public static void debug(Logger logger, String message) {
        if (logger.isDebugEnabled()) {
            logger.debug(message);
        }
    }

    public static void debug(Logger logger, String message, Throwable throwable) {
        if (logger.isDebugEnabled()) {
            logger.debug(message, throwable);
        }
    }
}

但这样的代码真的就能解决问题了吗?答案是“不能”!字符串拼接还存在。

 

那让我们分两个部分来看一下这个问题:

  1. 日志中的字符串拼接真的对性能影响很大吗
  2. 加不加isInfoEnabled的性能差距究竟有多少

关于第一个问题,String的拼接是不修改原来的字符串的,而是创建一个新的String对象,道理上是这么说的,我们也该这么理解。但Java编译器并不傻,实际情况是怎么样的呢,它会做些优化。请看如下代码:

public class StringAddDemo {
    public static void main(String[] args) {
        String a = "abc";
        String b = a + "def";
        System.out.println(b + "ghi");
    }
}

在编译后,通过javap -c StringAddDemo看看结果:

javap结果
Compiled from "StringAddDemo.java"
public class StringAddDemo extends java.lang.Object{
public StringAddDemo();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   ldc     #2; //String abc
   2:   astore_1
   3:   new     #3; //class java/lang/StringBuilder
   6:   dup
   7:   invokespecial   #4; //Method java/lang/StringBuilder."<init>":()V
   10:  aload_1
   11:  invokevirtual   #5; //Method java/lang/StringBuilder.append:(Ljava/lang/
String;)Ljava/lang/StringBuilder;
   14:  ldc     #6; //String def
   16:  invokevirtual   #5; //Method java/lang/StringBuilder.append:(Ljava/lang/
String;)Ljava/lang/StringBuilder;
   19:  invokevirtual   #7; //Method java/lang/StringBuilder.toString:()Ljava/la
ng/String;
   22:  astore_2
   23:  getstatic       #8; //Field java/lang/System.out:Ljava/io/PrintStream;
   26:  new     #3; //class java/lang/StringBuilder
   29:  dup
   30:  invokespecial   #4; //Method java/lang/StringBuilder."<init>":()V
   33:  aload_2
   34:  invokevirtual   #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   37:  ldc     #9; //String ghi
   39:  invokevirtual   #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   42:  invokevirtual   #7; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   45:  invokevirtual   #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   48:  return

}

实际这里的拼接用的是StringBuilder.append。情况没想的这么糟糕,而且1000次字符串拼接的开销都比不上一次远程调用的开销大,与其想着从这里挤性能,还不如去优化远程调用和数据库访问。

 

第二点,同样用实验来做说明,这里对比了使用辅助类,使用isInfoEnabled判断和不使用判断的情况,日志级别为INFO,另外再加了使用辅助类,使用isDebugEnabled判断和不使用判断的情况,都是循环输出20万次,每次的拼接在最后都加了些运算:

public class LogDemo {
    private static final Logger logger = LoggerFactory.getLogger(LogDemo.class);

    public static void main(String[] args) {
        Profiler.start();

        Profiler.enter("Ignore");
        for (int i = 0; i < 200000; i++) {
            LogUtil.info(logger, "test" + "test" + i + i*2);
        }
        Profiler.release();
        
        Profiler.enter("InfoUtil");
        for (int i = 0; i < 200000; i++) {
            LogUtil.info(logger, "test1" + "test2" + i + i*2);
        }
        Profiler.release();

        Profiler.enter("isInfoEnabled");
        for (int i = 0; i < 200000; i++) {
            if (logger.isInfoEnabled()) {
                logger.info("test3" + "test4" + i + i*2);
            }
        }
        Profiler.release();

        Profiler.enter("info");
        for (int i = 0; i < 200000; i++) {
            logger.info("test5" + "test6" + i + i*2);
        }
        Profiler.release();
        
        Profiler.enter("DebugUtil");
        for (int i = 0; i < 200000; i++) {
            LogUtil.debug(logger, "test7" + "test8" + i + i*2);
        }
        Profiler.release();

        Profiler.enter("isDebugEnabled");
        for (int i = 0; i < 200000; i++) {
            if (logger.isDebugEnabled()) {
                logger.debug("test9" + "test0" + i + i*2);
            }
        }
        Profiler.release();

        Profiler.enter("debug");
        for (int i = 0; i < 200000; i++) {
            logger.debug("tes1" + "tes2" + i + i*2);
        }
        Profiler.release();

        Profiler.release();
        System.out.println(Profiler.dump());
    }
}

( 说明:这里的Profiler类是个工具类,作用是记录调用的时间;代码有截取;第一个循环主要是预热一下,不在统计范围内)

 

经过了几次测试,结果如下:

实验结果
+---14,766 [15,171ms, 26%, 26%] - InfoUtil
+---29,937 [14,187ms, 24%, 24%] - isInfoEnabled
+---44,124 [13,938ms, 24%, 24%] - info
+---58,062 [31ms, 0%, 0%] - DebugUtil
+---58,093 [16ms, 0%, 0%] - isDebugEnabled
`---58,109 [46ms, 0%, 0%] - debug

+---14,968 [14,656ms, 25%, 25%] - InfoUtil
+---29,624 [14,141ms, 24%, 24%] - isInfoEnabled
+---43,765 [14,265ms, 25%, 25%] - info
+---58,030 [47ms, 0%, 0%] - DebugUtil
+---58,077 [0ms] - isDebugEnabled
`---58,077 [47ms, 0%, 0%] - debug

+---14,999 [14,922ms, 26%, 26%] - InfoUtil
+---29,921 [14,093ms, 24%, 24%] - isInfoEnabled
+---44,014 [14,219ms, 24%, 24%] - info
+---58,233 [47ms, 0%, 0%] - DebugUtil
+---58,280 [0ms] - isDebugEnabled
`---58,280 [47ms, 0%, 0%] - debug

+---14,656 [14,812ms, 26%, 26%] - InfoUtil
+---29,468 [14,344ms, 25%, 25%] - isInfoEnabled
+---43,812 [13,890ms, 24%, 24%] - info
+---57,702 [31ms, 0%, 0%] - DebugUtil
+---57,733 [16ms, 0%, 0%] - isDebugEnabled
`---57,749 [47ms, 0%, 0%] - debug

+---16,219 [16,719ms, 25%, 25%] - InfoUtil
+---32,938 [17,703ms, 27%, 27%] - isInfoEnabled
+---50,641 [15,157ms, 23%, 23%] - info
+---65,798 [47ms, 0%, 0%] - DebugUtil
+---65,845 [0ms] - isDebugEnabled
`---65,845 [47ms, 0%, 0%] - debug

(数据说明:第一列为开始计时的时间点,[]内为时间及统计,15,171ms为该阶段的具体耗时,后面的百分比是该阶段耗时在这个统计内所占的百分比)

 

 对上述数据分析后,可以得到这样的结论:

  1. 日志输出前的判断确实有效,但效果甚微
  2. 在日志级别较低,不会输出的情况下,日志的耗时基本可以忽略 ,20万次的调用耗时在50ms内
  3. 在日志级别满足输出要求时,3种方法差别不大

针对第3条再做些补充说明,日志输出大的开销应该在IO上,计算应该不会很多,也不该很多,如果存在大量的运算请自己考虑下是不是有问题;既然是确认要输出的日志,那增加判断其实是种浪费,虽然这种判断的开销可以忽略。

 

综上所述,个人建议在日常系统中无需对日志增加isInfoEnabled判断,想通过这种处理来优化效果的作用不会很明显,还是把精力从日志移到数据库和远程调用上效果更好些。 (特殊情况下,如果在日志中有复杂的操作,可以酌情考虑,但个人不倾向于复杂的日志)

 

一文搞懂 JavaScript 模块化规范:CommonJS、AMD、ES6 Module 在现代 JavaScript 开发中,模块化是提升代码组织性和可维护性的关键。本文深入解析三种主流的模块化规范——CommonJS、AMD 和 ES6 Module。 阅读详情

相关推荐

Virtualbox “Kernel driver not installed (rc=-1908)”问题全面解析与修复指南

本文全面解析了Virtualbox出现'Kernel driver not installed (rc=-1908)'错误的原因及修复方法。详细介绍了如何通过重新安装virtualbox-dkms、处理Secure Boot特殊情况等步骤解决该问题,并提供了预防措施与最佳实践,帮助用户避免类似错误再次发生。

weixin_30352645的博客 530

logger.isDebugEnabled和logger.isInfoEnabled的作用

1.看下apache的官方的document,在Performance下那块(From: http://logging.apache.org/log4j/1.2/manual.html) For example, for some logger cat, writing, logger.debug("Entry number: " + i + " is " +

鹏然心动 5797

【训练】Qwen2.5VL 多机多卡 Grounding Box定位

之前的相关文章:【深度学习】LLaMA-Factory微调sft Qwen2-VL进行印章识别使用LLaMA-Factory微调sft Qwen2-VL-7B-Instruct构建最新的LLaMA-Factory镜像。

q742971636的博客 2583

如何设置C# log4net 日志级别和isInfoEnable()的理解

在C#中使用log4net设置日志级别主要通过配置文件完成,通常是app.config或web.config,或者是单独的log4net配置文件。下面是如何在不同类型的配置文件中设置日志级别的步骤。

精通搬砖,资深技术砖家,在软件开发,人工智能,设备开发等领域都有研究。商业合作&交流学习可私信联系。 1292

瑞芯微RK3576开发板实战:6TOPS NPU如何加速你的AIoT项目(附性能测试)

本文以瑞芯微RK3576开发板为核心,深入探讨其6TOPS NPU在AIoT项目中的实战应用。通过详细的性能测试对比,展示了NPU如何将AI模型推理速度提升15倍以上,并大幅降低CPU负载。文章涵盖从环境搭建、模型转换优化到多屏异显与系统级调优的全流程,为开发者提供了将硬件算力转化为实际项目加速效果的完整指南。

oauth7security的博客 820

2022 七校联合NewStarCTF 公开赛赛道 WEEK5|MISC

2022 七校联合NewStarCTF 公开赛赛道 WEEK5|MISC

zerorzeror的博客 3118

log4j中isDebugEnabled(), log.isInfoEnabled()和log.isTraceEnabled的封装

1. log4j中log.isDebugEnabled(), log.isInfoEnabled()和log.isTraceEnabled()作用项目在应用log4j打印Debug,Info和Trace级别的log时需要加上对应的三个方法进行过滤,代码如下:if (log.isDebugEnabled()) {    log.debug(" From: " + req.getFrom(

1万+

LangChain4j + Milvus 从0-1实现会话管理与RAG检索的AIChat超详细教程

本教程将介绍如何使用LangChain4j和Milvus向量数据库构建一个具备流式输出、会话管理、会话隔离和基于文档RAG检索能力的AI机器人。我们将使用阿里百炼模型作为LLM后端,并通过Docker部署Milvus向量数据库。创建Assistant接口,定义聊天服务:LangChain4j 中文文档 | LangChain4j 中文文档wiringMode :这个属性一般两种,一种是自动装配,我们这里选择手动配置。

2301_80014343的博客 1435

log4j-logger.isDebugEnabled和logger.isInfoEnabled的作用

简单来说,两者主要是为了提升性能。 直接使用logger.info(...)来输出log,在进入log.info函数之产有,括号内的表达式已经通过运算拼接成了一个字符串,而如果事先使用if(logger.isInfoEnabled())进行断断,那么当log级别为debug及以上时,就省去上述的字符串操作,在高并发和复杂的log信息拼接的情况下,使用这种标准的方法输出log能够省去不小的开销。 ...

金溪的博客 4972

PID调参避坑指南:为什么你的电机总是震荡?从比例带积分到微分实战解析

本文针对电机控制中常见的震荡问题,提供了一份PID调参实战指南。文章深入解析了比例(P)、积分(I)、微分(D)三个参数对系统稳定性的影响,揭示了比例增益过大是导致震荡的关键原因之一,并给出了从纯比例调试到引入积分、微分的系统化调参流程与避坑要点,帮助工程师快速实现电机平稳、精准的控制。

weixin_28223453的博客 457

计算机视觉安防监控升级:人脸识别与行为异常检测

计算机视觉安防监控升级:人脸识别与行为异常检测 ,人工智能,计算机视觉,大模型,AI,本文将深入探讨人脸识别与行为异常检测技术在安防监控中的应用,包括技术原理、算法实现、应用场景以及面临的挑战与解决方案。通过实际案例和代码示例,帮助读者更好地理解和掌握这些技术,为推动安防监控系统的智能化升级提供参考。

不踩坑,不知道坑有多深;不总结,踩过的坑白踩。 23万+

isDebugEnabled 、isInfoEnabled 为什么要使用

以前一直不明白为什么在输出日志前必须使用: if (log.isDebugEnabled())??? 在看下面的解释之前,我先说明下,对于打印的信息没有字符拼接或者对象(说白了没有tostring的方法调用),如果打印的信息是固定字符串的话,就加上面的判断和不加其实意义是一样的(更说白了,就是节省log.debug(***)中***部分向字符串的装换。下面是一位网友写的,他说明了比较纤细,我摘...

iteye_17650的博客 1719

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

打印日志时为什么要使用isDebugEnabled 、isInfoEnabled

isDebugEnabled 、isInfoEnabled 为什么要使用 以前一直不明白为什么在输出日志前必须使用: if (log.isDebugEnabled())??? 在看下面的解释之前,我先说明下,对于打印的信息没有字符拼接或者对象(说白了没有tostring的方法调用),如果打印的信息是固定字符串的话,就加上面的判断和不加其实意义是一样的(更说白了,就是节省log.debug(*

手写白Tech 1万+

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

log4net loginfo.IsInfoEnabled=false问题解决方法

log4net使用中loginfo.IsInfoEnabled=false问题解决方法 引用了其他项目中的日志组件,调试时发现IsInfoEnabled属性一直为false 找了很多的解决办法,都不行 program.cs里面也不用添加 log4net.Config.XmlConfigurator.Configure(); 只需要在AssemblyInfo.cs中加上完整的 [assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4ne

u013934107的博客 1433

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

log4net使用中loginfo.IsInfoEnabled=false问题解决方法

log4net使用中loginfo.IsInfoEnabled=false问题解决方法

weixin_45866907的博客 142

使用日志类库log4net.dll出现代码loginfo.IsInfoEnabled等于false的解决办法

使用日志类库log4net.dll出现代码loginfo.IsInfoEnabled等于false的解决办法

LYQ 2523

为什么使用logger.isInfoEnabled()

很多东西当真都是要用到的时候,才会拿来仔细研究的。记得当时学的log4j,也就简单了解点,会用他输出下日志好了。搭建日志环境:http://hi.baidu.com/zhangna_307/blog/item/508df78aa02fec719f2fb4f2.html 然后碰到isDebugEnabled方法的时候,就网上好好查了下。 1.看下apache的官方的document,在P

gongjds的博客 2万+

org.slf4j.Logger中isTraceEnabled(),isDebugEnabled(),isInfoEnabled(),isWarnEnabled(),isErrorEnabled()

log4j中log.isDebugEnabled(), log.isInfoEnabled()和log.isTraceEnabled()作用 项目在应用log4j打印Debug,Info和Trace级别的log时需要加上对应的三个方法进行过滤,代码如下: if (log.isDebugEnabled()) { log.debug(" From: " + req.getFrom().toSt...

闻道有先后 术业有专攻 4519

isDebugEnabled 、isInfoEnabled 为啥要使用

isDebugEnabled 、isInfoEnabled 为什么要使用    以前一直不明白为什么在输出日志前必须使用: if (log.isDebugEnabled())??? 在看下面的解释之前,我先说明下,对于打印的信息没有字符拼接或者对象(说白了没有tostring的方法调用),如果打印的信息是固定字符串的话,就加上面的判断和不加其实意义是一样的(更说白了,就是节省log.debu

放逐的菜鸟的博客 2940
上一篇: 初试Sonar
下一篇: 我的2010
dingx
博客等级 码龄24年 67粉丝 112原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值