45 | 日志打印对象用 toString,生产环境可能引发雪崩
摘要:
log.info("订单:{}", order)会触发order.toString(),如果toString()里拼接了大量字段或有循环引用,生产环境高并发下会引发性能雪崩。
一、问题现象
// ❌ 危险:直接打印整个对象log.info("处理订单:{}",order);// order.toString() 可能是:publicStringtoString(){return"Order{id="+id+", userId="+userId+", items="+items+", logs="+logs+...}";// 如果 items 有 1000 条,toString() 极其耗时}现象:生产环境日志量暴增,CPU 占用升高,接口响应变慢。
二、踩坑现场
场景 1:toString() 里有集合字段
// ❌ 危险:toString() 里打印了 List@ToStringclassOrder{privateLongid;privateList<OrderItem>items;// 可能 1000 条privateList<OrderLog>logs;// 可能 500 条}log.info("订单:{}", order)→ 触发order.toString()→ 拼接 1500 条子对象的toString()→ 一次日志耗时 500ms。
场景 2:敏感信息打印到日志
// ❌ 危险:用户密码、Token 打印到日志log.info("用户登录:{}",user);// user.toString() 包含 password三、正确写法
3.1 只打印关键字段
// ✅ 正确:只打印关键字段log.info("处理订单:orderId={}, userId={}",order.getId(),order.getUserId());3.2 用 Lombok @ToString 排除大字段
// ✅ 正确:排除大字段@ToString(exclude={"items","logs"})classOrder{privateLongid;@ToString.Exclude// Lombok 新版本privateList<OrderItem>items;}3.3 敏感字段不打印
// ✅ 正确:敏感字段用 @ToString.Exclude 排除@ToStringclassUser{privateLongid;@ToString.Exclude// 排除 passwordprivateStringpassword;privateStringname;}3.4 JSON 打印(需控制深度)
// ✅ 需要打印对象时,用 JSON 且排除敏感字段log.info("订单:{}",JsonUtils.toJson(order,"password","token"));四、最佳实践
✅ 日志打印的 5 条铁律
- 不要直接打印整个对象,只打印关键字段
@ToString排除大字段和集合字段- 敏感字段(
password、token)必须排除 - 日志里不要用字符串拼接(
+),用占位符({}) - 生产环境关闭 DEBUG 日志(只开 INFO/WARN)
🔍 Lombok @ToString 配置
@ToString(exclude={"items","logs"},// 排除字段callSuper=false,// 不打印父类字段includeFieldNames=true// 打印字段名)classOrder{...}五、小结
log.info("{}", obj)会触发obj.toString(),如果toString()拼接大字段,高并发下性能雪崩- 只打印关键字段,不要直接打印整个对象
@ToString排除大字段和集合字段- 敏感字段必须排除,防止泄露
- 生产环境只开 INFO/WARN,关闭 DEBUG
下一篇预告:用 switch 忘了写 break?Java 14 之后有更好的写法—— 穿透问题(fall-through)是 C 语言遗留的设计,Java 14 的 switch 表达式彻底解决了它。
更多资料:一线大厂Java面试题合集