打印优质日志的 10 条军规



前言

去年双十一大促,我面对监控大屏上疯狂跳动的红色指标,颤抖着打开服务器日志,看到的却是这样的画面:

用户登录失败  
订单创建出错 null  
ERROR 非法参数

那一刻我突然顿悟:写不好日志的程序员,就像不会写病历的医生

这篇文章跟大家一起聊聊打印优质日志的10条军规,希望对你会有所帮助。

👉 欢迎加入小哈的星球,你将获得: 专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 收尾中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17..., 点击查看项目介绍;演示地址:http://116.62.199.48:7070/

  • 《从零手撸:前后端分离博客项目(全栈开发)》 2期已完结,演示链接:http://116.62.199.48/;

  • 专栏阅读地址:https://www.quanxiaoha.com/column

截止目前,累计输出 90w+ 字,讲解图 3713+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,Spring Cloud Alibaba 等等,戳我加入学习,解锁全部项目,已有3200+小伙伴加入

图片

第1条:格式统一

反例(管理看到会扣钱)

log.info("start process");
log.error("error happen");

无时间戳,无上下文。

正解代码

<!-- logback.xml核心配置 -->
<pattern>
    %d{yy-MM-dd HH:mm:ss.SSS} 
    |%X{traceId:-NO_ID} 
    |%thread 
    |%-5level 
    |%logger{36} 
    |%msg%n
</pattern>

在logback.xml中统一配置了日志的时间格式、tradeId,线程、等级、日志详情都信息。

日志的格式统一了,更方便点位问题。

第2条:异常必带堆栈

反例(同事看了想打人)

try {
    processOrder();
} catch (Exception e) {
    log.error("处理失败"); 
}

出现异常了,日志中没打印任何的异常堆栈信息。

相当于自己把异常吃掉了。

非常不好排查问题。

正确姿势

log.error("订单处理异常 orderId={}", orderId, e); // e必须存在!

日志中记录了出现异常的订单号orderId和异常的堆栈信息e。

第3条:级别合理

反面教材

log.debug("用户余额不足 userId={}", userId); // 业务异常应属WARN
log.error("接口响应稍慢"); // 普通超时属INFO

接口响应稍慢,打印了error级别的日志,显然不太合理。

正常情况下,普通超时属INFO级别。

级别定义表

级别

正确使用场景

FATAL

系统即将崩溃(OOM、磁盘爆满)

ERROR

核心业务失败(支付失败、订单创建异常)

WARN

可恢复异常(重试成功、降级触发)

INFO

关键流程节点(订单状态变更)

DEBUG

调试信息(参数流水、中间结果)

第4条:参数完整

反例(让运维骂娘)

log.info("用户登录失败");

上面这个日志只打印了“用户登录失败”这个文案。

谁在哪登录失败?

侦探式日志

log.warn("用户登录失败 username={}, clientIP={}, failReason={}", 
    username, clientIP, "密码错误次数超限");

登录失败的业务场景,需要记录哪个用户,ip是多少,在什么时间,登录失败了,失败的原因是什么。

时间在logback.xml中统一配置了格式。

这样才方便快速定位问题:

第5条:数据脱敏

血泪案例
某同事打印日志泄露用户手机号被投诉。

我在记录的日志中,需要对一下用户的个人敏感数据做脱敏处理。

例如下面这样:

// 脱敏工具类
public class LogMasker {
    public static String maskMobile(String mobile) {
        return mobile.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
    }
}

// 使用示例
log.info("用户注册 mobile={}", LogMasker.maskMobile("13812345678"));

第6条:异步保性能

问题复现
某次秒杀活动中直接同步写日志,导致大量线程阻塞:

log.info("秒杀请求 userId={}, itemId={}", userId, itemId);

高并发下IO阻塞。

致命伤害分析:

  1. 同步写日志导致线程上下文切换频繁

  2. 磁盘IO成为系统瓶颈

  3. 高峰期日志打印耗时占总RT的25%

正确示范(三步配置法)

步骤1:logback.xml配置异步通道

<!-- 异步Appender核心配置 -->  
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">  
    <!-- 不丢失日志的阈值:当队列剩余容量<此值时,TRACE/DEBUG级别日志将被丢弃 -->  
    <discardingThreshold>0</discardingThreshold>  
    <!-- 队列深度:建议设为 (最大并发线程数 × 2) -->  
    <queueSize>4096</queueSize>  
    <!-- 关联真实Appender -->  
    <appender-ref ref="FILE"/>  
</appender>

步骤2:日志输出优化代码

// 无需前置判断,框架自动处理  
log.debug("接收到MQ消息:{}", msg.toSimpleString()); // 自动异步写入队列  

// 不应做复杂计算后再打印(异步前仍在业务线程执行)  
// 错误做法:  
log.debug("详细内容:{}", computeExpensiveLog());

流程图如下:

步骤3:性能关键参数公式

最大内存占用 ≈ 队列长度 × 平均单条日志大小  
推荐队列深度 = 峰值TPS × 容忍最大延迟(秒)  
例如:10000 TPS × 0.5s容忍 ⇒ 5000队列大小

风险规避策略

  1. 防队列堆积:监控队列使用率,达80%触发告警

  2. 防OOM:严格约束大对象toString()的调用

  3. 紧急逃生:预设JMX接口用于快速切换同步模式

第7条:链路追踪

混沌场景
跨服务调用无法关联日志。

我们需要有链路追踪方案。

全链路方案

// 拦截器注入traceId
MDC.put("traceId", UUID.randomUUID().toString().substring(0,8));

// 日志格式包含traceId
<pattern>%d{HH:mm:ss} |%X{traceId}| %msg%n</pattern>

可以在MDC中设置traceId。

后面可以通过traceId全链路追踪日志。

流程图如下:

第8条:动态调参

半夜重启的痛
线上问题需要临时开DEBUG日志,比如:查询用户的某次异常操作的日志。

热更新方案

@GetMapping("/logLevel")
public String changeLogLevel(
    @RequestParam String loggerName, 
    @RequestParam String level) {
    
    Logger logger = (Logger) LoggerFactory.getLogger(loggerName);
    logger.setLevel(Level.valueOf(level)); // 立即生效
    return "OK";
}

有时候我们需要临时打印DEBUG日志,这就需要有个动态参数控制了。

否则每次调整打印日志级别都需要重启服务,可能会影响用户的正常使用。

journey
    title 日志级别动态调整
    section 旧模式
        发现问题 --> 修改配置 --> 重启应用 --> 丢失现场
    section 新模式
        发现问题 --> 动态调整 --> 立即生效 --> 保持现场

第9条:结构化存储

混沌日志

用户购买了苹果手机 订单号1001 金额8999

上面的日志拼接成了一个字符串,虽说中间有空格分隔了,但哪些字段对应了哪些值,看起来不是很清楚。

我们在存储日志的时候,需要做结构化存储,方便快速的查询和搜索。

机器友好式日志

{
  "event": "ORDER_CREATE",
  "orderId": 1001,
  "amount": 8999,
  "products": [{"name":"iPhone", "sku": "A123"}]
}

这里使用了json格式存储日志。

日志中的数据一目了然。

第10条:智能监控

最失败案例
某次用户开通会员操作,错误日志堆积3天才被发现,黄花菜都凉了。

我们需要在项目中引入智能监控。

ELK监控方案

报警规则示例

ERROR日志连续5分钟 > 100条 → 电话告警  
WARN日志持续1小时 → 邮件通知

总结

研发人员的三大境界

  1. 青铜System.out.println("error!")

  2. 钻石:标准化日志 + ELK监控

  3. 王者

  • 日志驱动代码优化

  • 异常预测系统

  • 根因分析AI模型

最后的灵魂拷问
下次线上故障时,你的日志能让新人5分钟定位问题吗?

👉 欢迎加入小哈的星球,你将获得: 专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 收尾中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17..., 点击查看项目介绍;演示地址:http://116.62.199.48:7070/

  • 《从零手撸:前后端分离博客项目(全栈开发)》 2期已完结,演示链接:http://116.62.199.48/;

  • 专栏阅读地址:https://www.quanxiaoha.com/column

截止目前,累计输出 90w+ 字,讲解图 3713+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,Spring Cloud Alibaba 等等,戳我加入学习,解锁全部项目,已有3200+小伙伴加入

图片

图片

图片

1. 我的私密学习小圈子,从0到1手撸企业实战项目~
2. 别再用雪花算法生成 ID 了!试试这个吧
3. 谈谈负载均衡
4. SpringBoot 接口防抖(防重复提交)的一些实现方案
最近面试BAT,整理一份面试资料《Java面试BATJ通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:点“在看”,关注公众号并回复 Java 领取,更多内容陆续奉上。
PS:因公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。
点“在看”支持小哈呀,谢谢啦
内容概要:本文档《DeepSeek本地部署教程(非ollama)》详细介绍了DeepSeek大语言模型的本地部署流程。首先明确了环境要求,包括Python 3.8以上版本、CUDA 11.7(针对GPU用户)、至少16GB RAM以及推荐的操作系统。接着阐述了安装步骤,如克隆代码仓库、创建虚拟环境、安装依赖等。随后讲解了模型下载方式,支持从Hugging Face平台下载不同版本的DeepSeek模型,如DeepSeek-7B、DeepSeek-67B和DeepSeek-Coder。文档还提供了两种运行模型的方式:命令行运行和使用API服务。此外,针对常见的问题,如CUDA相关错误、内存不足和模型加载失败等,给出了详细的解决方案。最后,文档提出了性能优化建议,如使用量化技术减少内存占用、启用CUDA优化等,并强调了安全注意事项,包括定期更新模型和依赖包、注意API访问权限控制等方面。; 适合人群:对大语言模型感兴趣的研究人员、开发者,特别是希望在本地环境中部署和测试DeepSeek模型的技术人员。; 使用场景及目标:①帮助用户在本地环境中成功部署DeepSeek大语言模型;②解决部署过程中可能遇到的问题,如环境配置、模型下载和运行时的常见错误;③提供性能优化建议,确保模型在不同硬件件下的最佳表现;④指导用户进行安全配置,保障模型和数据的安全性。; 阅读建议:在阅读本教程时,建议按照文档的步骤顺序逐步操作,同时结合实际情况调整环境配置和参数设置。对于遇到的问题,可以参考常见问题解决部分提供的解决方案。此外,性能优化部分的内容有助于提高模型的运行效率,值得深入研究。
Java基于SpringCloud架构的简易版个人网上银行系统源码(高分项目),该项目是个人大作业项目,答辩评审分达到98分,代码都经过调试测试,确保可以运行!欢迎下载使用,可用于小白学习、进阶。该资源主要针对计算机、通信、人工智能、自动化等相关专业的学生、老师或从业者下载使用,亦可作为期末课程设计、课程大作业、毕业设计等。项目整体具有较高的学习借鉴价值!基础能力强的可以在此基础上修改调整,以实现不同的功能。 Java基于SpringCloud架构的简易版个人网上银行系统源码(高分项目)Java基于SpringCloud架构的简易版个人网上银行系统源码(高分项目)Java基于SpringCloud架构的简易版个人网上银行系统源码(高分项目)Java基于SpringCloud架构的简易版个人网上银行系统源码(高分项目)Java基于SpringCloud架构的简易版个人网上银行系统源码(高分项目)Java基于SpringCloud架构的简易版个人网上银行系统源码(高分项目)Java基于SpringCloud架构的简易版个人网上银行系统源码(高分项目)Java基于SpringCloud架构的简易版个人网上银行系统源码(高分项目)Java基于SpringCloud架构的简易版个人网上银行系统源码(高分项目)Java基于SpringCloud架构的简易版个人网上银行系统源码(高分项目)Java基于SpringCloud架构的简易版个人网上银行系统源码(高分项目)Java基于SpringCloud架构的简易版个人网上银行系统源码(高分项目)Java基于SpringCloud架构的简易版个人网上银行系统源码(高分项目)Java基于SpringCloud架构的简易版个人网上银行系统源码(高分项目)Java基于SpringCloud架构的简易版个人网上银行系统源码(高分项目)Java基于S
内容概要:本文档详细介绍了基于JavaScript的俄罗斯方块游戏课程设计,旨在通过开发完整的俄罗斯方块游戏帮助学生掌握前端开发技术。课程设计分为课程背景与目标、项目意义、预期成果、需求分析、系统设计、详细设计、界面设计、实现方案、测试方案、项目进度安排以及总结与展望几个部分。系统设计采用模块化思想,包括游戏核心逻辑、界面渲染、用户交互和游戏状态管理四个主要模块。详细设计中定义了方块类、游戏类、渲染类和控制器类,明确了各组件的功能和交互方式。实现方案提供了HTML、CSS和JavaScript的具体代码示例,确保游戏在不同浏览器和设备上的兼容性。测试方案涵盖功能测试、边界测试、用户界面测试和兼容性测试,以保证游戏的质量。项目进度安排分为需求分析、编码实现、测试调试、文档编写和项目验收五个阶段,时间跨度约为11周。 适合人群:具备一定编程基础,特别是对JavaScript有一定了解的学生或初学者。 使用场景及目标:①巩固JavaScript基础知识,包括变量、函数、对象、数组、循环等;②理解并掌握DOM操作方法;③学习如何处理用户事件和实现交互效果;④掌握动画原理和实现方式;⑤培养解决实际问题的能力和逻辑思维。 其他说明:此课程设计不仅注重代码编写,还强调需求分析和方案设计,建议学习者在实践中结合这些内容,调试代码并不断优化游戏体验。此外,文档还提出了未来的改进方向,如添加更多游戏模式、实现多人对战、增加音效和动画效果等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值