Java业务开发常见错误100例
朱晔
贝壳金服资深架构师
立即订阅
5474 人已学习
课程目录
已更新 36 讲 / 共 38 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 业务代码真的会有这么多坑?
免费
代码篇 (20讲)
01 | 使用了并发工具类库,线程安全就高枕无忧了吗?
02 | 代码加锁:不要让“锁”事成为烦心事
03 | 线程池:业务代码最常用也最容易犯错的组件
04 | 连接池:别让连接池帮了倒忙
05 | HTTP调用:你考虑到超时、重试、并发了吗?
06 | 20%的业务代码的Spring声明式事务,可能都没处理正确
07 | 数据库索引:索引并不是万能药
08 | 判等问题:程序里如何确定你就是你?
09 | 数值计算:注意精度、舍入和溢出问题
10 | 集合类:坑满地的List列表操作
11 | 空值处理:分不清楚的null和恼人的空指针
12 | 异常处理:别让自己在出问题的时候变为瞎子
13 | 日志:日志记录真没你想象的那么简单
14 | 文件IO:实现高效正确的文件读写并非易事
15 | 序列化:一来一回你还是原来的你吗?
16 | 用好Java 8的日期时间类,少踩一些“老三样”的坑
17 | 别以为“自动挡”就不可能出现OOM
18 | 当反射、注解和泛型遇到OOP时,会有哪些坑?
19 | Spring框架:IoC和AOP是扩展的核心
20 | Spring框架:框架帮我们做了很多工作也带来了复杂度
设计篇 (6讲)
21 | 代码重复:搞定代码重复的三个绝招
22 | 接口设计:系统间对话的语言,一定要统一
23 | 缓存设计:缓存可以锦上添花也可以落井下石
24 | 业务代码写完,就意味着生产就绪了?
25 | 异步处理好用,但非常容易用错
26 | 数据存储:NoSQL与RDBMS如何取长补短、相辅相成?
安全篇 (3讲)
27 | 数据源头:任何客户端的东西都不可信任
28 | 安全兜底:涉及钱时,必须考虑防刷、限量和防重
29 | 数据和代码:数据就是数据,代码就是代码
不定期加餐 (6讲)
加餐1 | 带你吃透课程中Java 8的那些重要知识点(上)
加餐2 | 带你吃透课程中Java 8的那些重要知识点(下)
加餐3 | 定位应用问题,排错套路很重要
加餐4 | 分析定位Java问题,一定要用好这些工具(一)
加餐5 | 分析定位Java问题,一定要用好这些工具(二)
加餐6 | 这15年来,我是如何在工作中学习技术和英语的?
Java业务开发常见错误100例
15
15
1.0x
00:00/00:00
登录|注册

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

朱晔 2020-04-07
你好,我是朱晔。今天,我和你分享的是,记录日志可能会踩的坑。
一些同学可能要说了,记录日志还不简单,无非是几个常用的 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/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java业务开发常见错误100例》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(20)

  • 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 查看

    2020-04-13
    1
  • jiarupc
    对于我们这些小公司,日志的性能不是问题,日志也没踩过坑。

    最大的问题是,日志记录不合理。我工作过两家公司。

    一家公司日志记录很随意,想起来才记录,日志根本没啥用,全凭经验找问题。

    另一家公司,日志全在一个文件,也没分级,一大堆信息,不知道怎么查问题,最后还是得靠经验。
    2020-04-07
    8
  • 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

    2020-04-08
    4
    7
  • hellojd
    赞,日志是性能的隐形杀手,学问也不小。佩服老师的工匠精神。
    2020-04-07
    6
  • 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结构的内容

    2020-04-07
    4
  • Husiun
    课后问题1.过滤级别可以多个用逗号隔开2.日志归档,通过设置不同的滚动policy策略将日志存档,当然logback比log4j更好的是可以自动删除过期日志;还望老师指点--
    2020-04-07
    4
  • Husiun
    老师还有一点我补充一下,springboot默认使用starter日志依赖logback的时候,日志配置文件名应以-spring结尾,才会默认加入其上下文环境中。

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

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

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

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

    作者回复: :)

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

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

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

    作者回复: 是

    2020-05-20
  • 天天向上
    学到新技能StopWatch 。
    2020-05-19
  • 天天向上
    配置文件如下,由于只能输入2000字,只是截取了info和error的。
    _info.log中还是有warn和error的日志,但是_warn.log和_error.log中却只有自己级别的日志。不知道什么原因<appendername="INFO_FILE"class="ch.qos.logback.core.rolling.RollingFileAppender">
    <filterclass="com.edianzu.saas.dps.filter.CustomLogFilter"/>
    <file>${log.path}/log_info.log</file>
    <encoder>
    <pattern>%d{yyyy-MM-ddHH:mm:ss.SSS}[%thread]%-5level%logger{50}-%msg%n</pattern>
    <charset>UTF-8</charset>
    </encoder>
    <rollingPolicyclass="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
    <timeBasedFileNamingAndTriggeringPolicyclass="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
    <maxFileSize>100MB</maxFileSize>
    </timeBasedFileNamingAndTriggeringPolicy>
    <maxHistory>15</maxHistory>
    </rollingPolicy>
    <filterclass="ch.qos.logback.classic.filter.LevelFilter">
    <level>info</level>
    <onMatch>ACCEPT</onMatch>
    <onMismatch>DENY</onMismatch>
    </filter>
    </appender>
    <appendername="ERROR_FILE"class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${log.path}/log_error.log</file>
    <encoder>
    <pattern>%d{yyyy-MM-ddHH:mm:ss.SSS}[%thread]%-5level%logger{50}-%msg%n</pattern>
    <charset>UTF-8</charset><!--此处设置字符集-->
    </encoder>
    <rollingPolicyclass="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
    <timeBasedFileNamingAndTriggeringPolicyclass="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
    <maxFileSize>100MB</maxFileSize>
    </timeBasedFileNamingAndTriggeringPolicy>
    <maxHistory>15</maxHistory>
    </rollingPolicy>
    <filterclass="ch.qos.logback.classic.filter.LevelFilter">
    <level>ERROR</level>
    <onMatch>ACCEPT</onMatch>
    <onMismatch>DENY</onMismatch>
    </filter>
    </appender>
    <rootlevel="info">
    <appender-refref="INFO_FILE"/>
    <appender-refref="WARN_FILE"/>
    <appender-refref="ERROR_FILE"/>
    </root>

    作者回复: 可能和CustomLogFilter有关系

    2020-05-18
    1
  • 天天向上
    我们系统里就是这样配置的
    <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>INFO</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter>,
    warn和error类似。
    但是_info.log中还是有warn和error的日志,但是_warn.log和_error.log中却只有自己级别的日志。不知道什么原因

    作者回复: 贴下完整配置

    2020-05-17
  • 天天向上
    定义的 <logger>会自动继承<root>吗?
    2020-05-17
  • Eclipse
    课程好评!希望老师能够将每小节的pom依赖和配置等都分开;将每小节内容分成Modules;这样真的会方便好多
    2020-05-15
  • jjn0703
    请问老师演示的jar包依赖树是怎么打印的呀

    作者回复: mvn dependency:tree

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

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

    2020-04-28
  • David Mo
    工作的公司,日志是十几年前的log4j1.2.8。发现系统无法通过增加线程提高吞吐量。后来查看jvm发现阻塞在logger appender, 升级log4j2异步,问题解决

    作者回复: 😀

    2020-04-24
收起评论
20
返回
顶部