Java 业务开发常见错误 100 例
朱晔
贝壳金服资深架构师
51940 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 48 讲
代码篇 (23讲)
Java 业务开发常见错误 100 例
15
15
1.0x
00:00/00:00
登录|注册

13 | 日志:日志记录真没你想象的那么简单

你好,我是朱晔。今天,我和你分享的是,记录日志可能会踩的坑。
一些同学可能要说了,记录日志还不简单,无非是几个常用的 API 方法,比如 debug、info、warn、error;但我就见过不少坑都是记录日志引起的,容易出错主要在于三个方面:
日志框架众多,不同的类库可能会使用不同的日志框架,如何兼容是一个问题。
配置复杂且容易出错。日志配置文件通常很复杂,因此有些开发同学会从其他项目或者网络上复制一份配置文件,但却不知道如何修改,甚至是胡乱修改,造成很多问题。比如,重复记录日志的问题、同步日志的性能问题、异步记录的错误配置问题。
日志记录本身就有些误区,比如没考虑到日志内容获取的代价、胡乱使用日志级别等。
Logback、Log4j、Log4j2、commons-logging、JDK 自带的 java.util.logging 等,都是 Java 体系的日志框架,确实非常多。而不同的类库,还可能选择使用不同的日志框架。这样一来,日志的统一管理就变得非常困难。为了解决这个问题,就有了 SLF4J(Simple Logging Facade For Java),如下图所示:
SLF4J 实现了三种功能:
一是提供了统一的日志门面 API,即图中紫色部分,实现了中立的日志记录 API。
二是桥接功能,即图中蓝色部分,用来把各种日志框架的 API(图中绿色部分)桥接到 SLF4J API。这样一来,即便你的程序中使用了各种日志 API 记录日志,最终都可以桥接到 SLF4J 门面 API。
三是适配功能,即图中红色部分,可以实现 SLF4J API 和实际日志框架(图中灰色部分)的绑定。SLF4J 只是日志标准,我们还是需要一个实际的日志框架。日志框架本身没有实现 SLF4J API,所以需要有一个前置转换。Logback 就是按照 SLF4J API 标准实现的,因此不需要绑定模块做转换。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 业务开发常见错误 100 例》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(32)

  • 最新
  • 精选
  • Geek_58afe9
    置顶
    "其实,我们只是换成了 Log4j2 API,真正的日志记录还是走的 Logback 框架。没错,这就是 SLF4J 适配的一个好处。". Log4j2 和 LogBack 不是同质化产品吗, Log4j2 api 怎么会走到Logback?

    作者回复: log4j2 API,并不是log4j 你在ch.qos.logback.classic.Logger.buildLoggingEventAndAppend设一个断点看一下整个过程就知道了,或者直接访问 https://github.com/JosephZhu1983/java-common-mistakes/blob/master/src/main/java/org/geekbang/time/commonmistakes/logging/placeholder/log4j2api_to_slf4j_to_logack.jpg 查看

    6
  • Darren
    我们在线上的日志基本遇到的问题也不多,最多就是日志消费不及时问题,目前通过filebeat采集写入kafka,strom消费,写入es聚合,然后前端展示;现在有延迟问题,正在切flink。 回答下问题: 第一个问题采用了表达式; 第二个问题主要是SizeAndTimeBasedRollingPolicy的MaxHistory,MaxFileSize,totalSizeCap属性等,文件在github上,请老师指点 https://github.com/y645194203/geektime-java-100/blob/master/logback.xml

    作者回复: 这个实现可以,我也更新了一个自定义Filter的例子 https://github.com/JosephZhu1983/java-common-mistakes/blob/master/src/main/java/org/geekbang/time/commonmistakes/logging/duplicate/multiplelevelsfilter.xml

    6
    13
  • Husiun
    老师还有一点我补充一下,springboot默认使用starter日志依赖logback的时候,日志配置文件名应以-spring结尾,才会默认加入其上下文环境中。

    作者回复: 是的,相关信息参考 https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-logback-extensions

    10
  • Demon.Lee
    扎心,然后就是瑟瑟发抖,我一直以为:“SLF4J 的{}占位符语法,到真正记录日志时才会获取实际参数,因此解决了日志数据获取的性能问题” 是对的,也没有去验证,所以代码都是这么写的,虽然把函数作为入参的情况很少,但写了那么多用SLF4J 的{}占位符打印日志的代码,突然很恐慌。哎,小伙伴们,再没时间也要把每个例子动手去实验一下。

    作者回复: :)

    3
    7
  • 殿小二
    老师,你好 问一下: 1.在生产环境中需要info.warning,error分开文件记录吗? 2.如果日志分开,在后期查看调用过程时不借助第三方工具,需要按时间进行匹配感觉会很累,有必要额外单独一个文件记录所以日志吗? 3.我们项目一般会记录请求和响应信息到单独的一个文件中,这个日志级别设置为info和debug都无所谓吧?

    作者回复: 1. 取决于你们是否希望针对不同的日志级别有不同的归档策略等等 2. 一般都会用ELK搜索,不会有这个问题 3. 你认为请求和响应属于开发用的调试信息那么放入debug,生产不开启debug,如果认为是用于审计等,那么放入info,我不好判断你的需求是什么

    2
    7
  • Monday
    @Log4j2 @Slf4j 两个注解使用的区别是什么,前者使用了Log4j的框架记录日志,后者使用了默认的Logback框架吗?

    作者回复: @Log4j Creates private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class); @Log4j2 Creates private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class); @Slf4j Creates private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class); @Log4j2和@Slf4j只是日志记录API,和实际日志记录框架没有关系,可以再看看文章一开始说的那段有关SLF4J结构的内容

    7
  • alex
    老师,一个类只在指定类配置下才打印日志这个配置怎么做,比如系统有定时器调用DataSourceTransactionManager的情况下, 在DEBUG下会有大量日志不间断打出,基本没法看,但是又想看别的类调用DataSourceTransactionManager下的日志输出, 现在做法是定义filter,判断进程名 if (event != null && event.getLevel().equals(Level.DEBUG_INTEGER) && Objects.equals("flowable-bpmn-acquire-async-jobs", event.getThreadName())) { return FilterReply.DENY; } 不知道有没有更好的做法

    作者回复: 嗯,对于这种需求,自定义Filter或直接使用GEventEvaluator/JaninoEventEvaluator都是比较好的解决方案。

    3
  • boyxie
    日志格式化取代日志级别的判断,本地测试发现一个特列,如果只传对象(并重写了toString方法),是有日志级别判断的功能的。这对于大部分打印入参出参的日志还是合适的吧 log.debug("debug0:{}", user); // 有效果 log.debug("debug0:{}", user.toString()); // 无效果

    作者回复: 传入的是Object,那么log.debug传入的是Object,只有输出日志的时候才会toString,如果你在toString里面打印了东西来判断是否有效果,就是这样的

    3
  • J.Smile
    有一点 不是很明白,即便是“参数会延迟到真正需要记录日志时再获取“,但又不是异步的,凭什么说能提高性能呢

    作者回复: 因为是info级别的时候就无需浪费时间去获取debug日志需要的参数了

    2
    2
  • 天天向上
    虽然我们可以使用 log4j-over-slf4j 来实现 Log4j 桥接到 SLF4J,也可以使用 slf4j-log4j12 实现 SLF4J 适配到 Log4j,也把它们画到了一列,但是它不能同时使用它们,否则就会产生死循环。jcl 和 jul 也是同样的道理。 老师,这句话的意思是,对于那个图来讲,就是,直上直下(属于一列)不可以,但是可以拐弯。是这个意思吗?

    作者回复: 是

    2
    2
收起评论
显示
设置
留言
32
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部