最佳实践 - 日志记录
基本原则
- ❌ 避免记录重复、无意义的日志,确保每一条日志都有助于调试
- 🔄 避免在循环中使用日志,以免产生大量冗余数据
- 📘 只记录必要的参数,而不是完整的对象结构
风险防范
🌐 将英文作为日志语言,以预防编码问题
🔨 不滥用Error级别的日志,减轻紧急响应负担
🚫 不要在日志记录过程中阻断业务流程
错误方式
不要用可能会发生空指针异常的对象或方法
javapublic void createShop(Shop shop){ log.info("create and print log: {}", shop.getName().toLowerCase()); }
性能保障
💡 使用专用日志库,而非标准输出,来提高性能并便于管理
错误方式
javapublic void love() { System.out.println("i love java..."); // 业务逻辑 ... }
原因:println 内部实现是带锁的
javapublic void println(boolean x) { synchronized (this) { print(x); newLine(); } }
正确方式
javapublic void love() { log.info("i love java..."); // 业务逻辑 ... }
🔒 统一日志框架减少依赖耦合,提升维护便利性
禁用
Log4j
或LogBack
的API输出日志,而是用Slf4j
,防止代码和日志强耦合java// 直接使用 @Slf4j public class Test {} // 或者直接引入 import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger log = LoggerFactory.getLogger(xxx.class);
⚙️ 根据日志级别控制输出,防止不必要的资源耗费
错误方式
javapublic void hello(String name) { log.trace("trace hello" + name); log.debug("debug hello" + name); log.info("info hello" + name); // 业务逻辑 ... }
正确方式
强烈建议使用占位符来输出内容,防止执行字符串拼接操作,浪费系统资源
javapublic void hello() { if (log.isTraceEnabled()) { log.trace("trace hello {}", name); } if (log.isDebugEnabled()) { log.debug("debug hello {}", name); } if (log.isInfoEnabled()) { log.info("info hello {}", name); } // 业务逻辑 ... }
👾 错误地使用
e.printStackTrace()
,可能导致严重的资源耗尽错误方式
javapublic void hello() { try { // 业务逻辑 ... } catch (Exception e) { e.printStackTrace(); } }
原因:
e.printStackTrace()
会将产生的字符串存放到字符串常量池,降低可用内存空间 javapublic void printStackTrace() { printStackTrace(System.err); }
正确方式
javapublic void hello() { try { // 业务逻辑 ... } catch (Exception e) { log.error("execute failed", e); } }
🐛 在日志中禁用复杂对象序列化以防潜在风险
错误方式
禁止使用JSON序列化工具,这些工具是通过调用对象的get方法将对象进行序列化的,如果对象的get方法被重写则可能会面临抛出异常的风险
javapublic void hello(Object data) { log.info("print log, data={}", JSON.toJSONString(data)); // 业务逻辑 ... }
正确方式
推荐自定义toString 方法
javapublic void hello() { log.info("hello and print log, data={}", data); }
开发简化
🕵️♂️ 提供详细异常信息有助于问题追踪
错误方式
javapublic void hello() { try { // 业务逻辑 ... } catch (Exception e) { // 没有打印异常 e,无法定位出现什么类型的异常 log.error("execute failed"); // 没有记录详细的堆栈异常信息,只记录错误基本描述信息,不利于排查问题 log.error("execute failed", e.getMessage()); } }
正确方式
javapublic void hello() { try { // 业务逻辑 ... } catch (Exception e) { log.error("execute failed", e); } }
💼 记录关键函数的输入/输出,增强排错能力
javapublic String doSth(String id, String type) { log.info("start: {}, {}", id, type); // 入口处记录初始值 String res = process(id, type); log.info("end: {}, {}, {}", id, type, res); // 出口处记录结果 }
🤝 利用条件语句前后的日志,简化程序跟踪
javapublic void doSth() { ... if (user.isVip()) { log.info("vip member, Id: {}, start vip", userId()); // 会员逻辑 } else { log.info("该用户是非会员, Id: {}, 开始处理非会员逻辑", userId()); // 非会员逻辑 } ... }
📍 添加Trace ID用于关联相关事件流,串联一次性的日志链路
javaimport javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.UUID; @Component public class MDCCFilter implements Filter { private static final String TRACE_ID = "traceId"; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, javax.servlet.ServletException { try { // 为每个请求生成唯一的 Trace ID String traceId = UUID.randomUUID().toString(); MDC.put(TRACE_ID, traceId); // 如果需要,可以从请求中提取更多上下文信息,例如用户 ID if (request instanceof HttpServletRequest) { HttpServletRequest httpRequest = (HttpServletRequest) request; String userId = httpRequest.getHeader("X-User-Id"); // 假设用户 ID 在请求头中 if (userId != null) { MDC.put("userId", userId); } } // 执行后续的 Filter 链 chain.doFilter(request, response); } finally { // 清理 MDC,避免线程数据污染 MDC.clear(); } } } // 日志格式 %d{yyyy-MM-dd HH:mm:ss} [%thread] [%X{traceId}] %logger{36} - %msg%n ...