找回密码
 立即注册
首页 业界区 业界 打印高质量日志的10条军规

打印高质量日志的10条军规

炳裘垦 2025-6-2 23:47:20
前言

去年双十一大促,我面对监控大屏上疯狂跳动的红色指标,颤抖着打开服务器日志,看到的却是这样的画面:
  1. 用户登录失败  
  2. 订单创建出错 null  
  3. ERROR 非法参数
复制代码
那一刻我突然顿悟:写不好日志的程序员,就像不会写病历的医生
这篇文章跟大家一起聊聊打印优质日志的10条军规,希望对你会有所帮助。
1.webp

第1条:格式统一

反例(管理看到会扣钱)
  1. log.info("start process");
  2. log.error("error happen");
复制代码
无时间戳,无上下文。
正解代码
  1. <pattern>
  2.     %d{yy-MM-dd HH:mm:ss.SSS}
  3.     |%X{traceId:-NO_ID}
  4.     |%thread
  5.     |%-5level
  6.     |%logger{36}
  7.     |%msg%n
  8. </pattern>
复制代码
在logback.xml中统一配置了日志的时间格式、tradeId,线程、等级、日志详情都信息。
日志的格式统一了,更方便点位问题。
2.webp

第2条:异常必带堆栈

反例(同事看了想打人)
  1. try {
  2.     processOrder();
  3. } catch (Exception e) {
  4.     log.error("处理失败");
  5. }
复制代码
出现异常了,日志中没打印任何的异常堆栈信息。
相当于自己把异常吃掉了。
非常不好排查问题。
正确姿势
  1. log.error("订单处理异常 orderId={}", orderId, e); // e必须存在!
复制代码
日志中记录了出现异常的订单号orderId和异常的堆栈信息e。
第3条:级别合理

反面教材
  1. log.debug("用户余额不足 userId={}", userId); // 业务异常应属WARN
  2. log.error("接口响应稍慢"); // 普通超时属INFO
复制代码
接口响应稍慢,打印了error级别的日志,显然不太合理。
正常情况下,普通超时属INFO级别。
级别定义表
级别正确使用场景FATAL系统即将崩溃(OOM、磁盘爆满)ERROR核心业务失败(支付失败、订单创建异常)WARN可恢复异常(重试成功、降级触发)INFO关键流程节点(订单状态变更)DEBUG调试信息(参数流水、中间结果)第4条:参数完整

反例(让运维骂娘)
  1. log.info("用户登录失败");
复制代码
上面这个日志只打印了“用户登录失败”这个文案。
谁在哪登录失败?
侦探式日志
  1. log.warn("用户登录失败 username={}, clientIP={}, failReason={}",
  2.     username, clientIP, "密码错误次数超限");
复制代码
登录失败的业务场景,需要记录哪个用户,ip是多少,在什么时间,登录失败了,失败的原因是什么。
时间在logback.xml中统一配置了格式。
这样才方便快速定位问题:
3.webp

第5条:数据脱敏

血泪案例
某同事打印日志泄露用户手机号被投诉。
我在记录的日志中,需要对一下用户的个人敏感数据做脱敏处理。
例如下面这样:
  1. // 脱敏工具类
  2. public class LogMasker {
  3.     public static String maskMobile(String mobile) {
  4.         return mobile.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
  5.     }
  6. }
  7. // 使用示例
  8. log.info("用户注册 mobile={}", LogMasker.maskMobile("13812345678"));
复制代码
第6条:异步保性能

问题复现
某次秒杀活动中直接同步写日志,导致大量线程阻塞:
  1. log.info("秒杀请求 userId={}, itemId={}", userId, itemId);
复制代码
高并发下IO阻塞。
致命伤害分析:

  • 同步写日志导致线程上下文切换频繁
  • 磁盘IO成为系统瓶颈
  • 高峰期日志打印耗时占总RT的25%
正确示范(三步配置法)
步骤1:logback.xml配置异步通道
  1.   
  2.   
  3.       
  4.     <discardingThreshold>0</discardingThreshold>  
  5.       
  6.     <queueSize>4096</queueSize>  
  7.       
  8.       
  9. </appender>  
复制代码
步骤2:日志输出优化代码
  1. // 无需前置判断,框架自动处理  
  2. log.debug("接收到MQ消息:{}", msg.toSimpleString()); // 自动异步写入队列  
  3. // 不应做复杂计算后再打印(异步前仍在业务线程执行)  
  4. // 错误做法:  
  5. log.debug("详细内容:{}", computeExpensiveLog());  
复制代码
流程图如下:
4.webp

步骤3:性能关键参数公式
  1. 最大内存占用 ≈ 队列长度 × 平均单条日志大小  
  2. 推荐队列深度 = 峰值TPS × 容忍最大延迟(秒)  
  3. 例如:10000 TPS × 0.5s容忍 ⇒ 5000队列大小  
复制代码
风险规避策略

  • 防队列堆积:监控队列使用率,达80%触发告警
  • 防OOM:严格约束大对象toString()的调用
  • 紧急逃生:预设JMX接口用于快速切换同步模式
第7条:链路追踪

混沌场景
跨服务调用无法关联日志。
我们需要有链路追踪方案。
全链路方案
  1. // 拦截器注入traceId
  2. MDC.put("traceId", UUID.randomUUID().toString().substring(0,8));
  3. // 日志格式包含traceId
  4. <pattern>%d{HH:mm:ss} |%X{traceId}| %msg%n</pattern>
复制代码
可以在MDC中设置traceId。
后面可以通过traceId全链路追踪日志。
流程图如下:
5.webp

第8条:动态调参

半夜重启的痛
线上问题需要临时开DEBUG日志,比如:查询用户的某次异常操作的日志。
热更新方案
  1. @GetMapping("/logLevel")
  2. public String changeLogLevel(
  3.     @RequestParam String loggerName,
  4.     @RequestParam String level) {
  5.    
  6.     Logger logger = (Logger) LoggerFactory.getLogger(loggerName);
  7.     logger.setLevel(Level.valueOf(level)); // 立即生效
  8.     return "OK";
  9. }
复制代码
有时候我们需要临时打印DEBUG日志,这就需要有个动态参数控制了。
否则每次调整打印日志级别都需要重启服务,可能会影响用户的正常使用。
  1. journey
  2.     title 日志级别动态调整
  3.     section 旧模式
  4.         发现问题 --> 修改配置 --> 重启应用 --> 丢失现场
  5.     section 新模式
  6.         发现问题 --> 动态调整 --> 立即生效 --> 保持现场
复制代码
第9条:结构化存储

混沌日志
  1. 用户购买了苹果手机 订单号1001 金额8999
复制代码
上面的日志拼接成了一个字符串,虽说中间有空格分隔了,但哪些字段对应了哪些值,看起来不是很清楚。
我们在存储日志的时候,需要做结构化存储,方便快速的查询和搜索。
机器友好式日志
  1. {
  2.   "event": "ORDER_CREATE",
  3.   "orderId": 1001,
  4.   "amount": 8999,
  5.   "products": [{"name":"iPhone", "sku": "A123"}]
  6. }
复制代码
这里使用了json格式存储日志。
日志中的数据一目了然。
第10条:智能监控

最失败案例
某次用户开通会员操作,错误日志堆积3天才被发现,黄花菜都凉了。
我们需要在项目中引入智能监控。
ELK监控方案
6.webp

报警规则示例
  1. ERROR日志连续5分钟 > 100条 → 电话告警  
  2. WARN日志持续1小时 → 邮件通知
复制代码
总结

研发人员的三大境界

  • 青铜:System.out.println("error!")
  • 钻石:标准化日志 + ELK监控
  • 王者

    • 日志驱动代码优化
    • 异常预测系统
    • 根因分析AI模型

最后的灵魂拷问
下次线上故障时,你的日志能让新人5分钟定位问题吗?
最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,我的所有文章都会在公众号上首发,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。
本文收录于我的技术网站:http://www.susan.net.cn

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册