当前位置: 首页 > news >正文

LogUtil日志工具类你真的封装对了么?

问题背景

在Java项目中使用SLF4J框架时,经常会将Logger封装到工具类LogUtil中以提高代码复用性。然而这种做法会导致日志丢失原始的调用类名和行号信息,给调试和问题排查带来困难。

解决方案

以下是一个经过优化的LogUtil实现,它能够正确显示实际调用日志的类名和行号:

package com.lzc.log;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.spi.LocationAwareLogger;import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;public class LogUtil1 {// 工具类的完全限定名(关键)private static final String FQCN = LogUtil1.class.getName();// 使用双重缓存:类名 -> Logger 的映射private static final Map<String, Logger> LOGGER_CACHE = new ConcurrentHashMap<>();/*** 高性能获取当前调用者的Logger** @return*/private static Logger getCallerLogger() {// 1. 获取当前调用栈StackTraceElement[] stack = new Throwable().getStackTrace();// 2. 跳过工具类自身和日志框架的栈帧for (int i = 0; i < stack.length; i++) {String className = stack[i].getClassName();// 找到第一个非工具类、非日志框架的调用者if (!className.equals(FQCN) && !className.startsWith("org.slf4j") && !className.startsWith("java.lang")) {// 3. 使用缓存避免重复创建Loggerreturn LOGGER_CACHE.computeIfAbsent(className, LoggerFactory::getLogger);}}// 回退:使用工具类自身的Loggerreturn LoggerFactory.getLogger(LogUtil1.class);}// 其他日志级别方法...public static void debug(String format, Object... args) {final Logger logger = getCallerLogger();if (logger.isDebugEnabled()) {if (logger instanceof LocationAwareLogger) {((LocationAwareLogger) logger).log(null, FQCN, LocationAwareLogger.DEBUG_INT, format, args, null);} else {logger.debug(format, args);}}}// 核心:带行号记录的日志方法public static void info(String format, Object... args) {final Logger logger = getCallerLogger();// 使用LocationAwareLogger获取精确位置if (logger instanceof LocationAwareLogger) {LocationAwareLogger lal = (LocationAwareLogger) logger;// 传递FQCN确保日志框架跳过工具类lal.log(null, FQCN, LocationAwareLogger.INFO_INT, format, args, null);} else {// 回退方案logger.info(format, args);}}public static void warn(String msg, Throwable t) {final Logger logger = getCallerLogger();if (logger instanceof LocationAwareLogger) {LocationAwareLogger lal = (LocationAwareLogger) logger;lal.log(null, FQCN, LocationAwareLogger.WARN_INT, msg, null, t);} else {logger.error(msg, t);}}// 错误日志(带异常)public static void error(String msg, Throwable t) {final Logger logger = getCallerLogger();if (logger instanceof LocationAwareLogger) {LocationAwareLogger lal = (LocationAwareLogger) logger;lal.log(null, FQCN, LocationAwareLogger.ERROR_INT, msg, null, t);} else {logger.error(msg, t);}}// 性能优化:清除缓存(通常在类加载器卸载时调用)public static void clearCache() {LOGGER_CACHE.clear();}}

使用示例

业务代码


public class BusinessService {public void process() {// 普通日志LogUtil.debug("开始处理订单,订单ID: {}", orderId);try {// 业务逻辑...} catch (Exception e) {// 带异常的日志LogUtil.error("订单处理失败", e);}// 格式化日志LogUtil.info("订单{}处理完成,总计金额: {.2f}", orderId, totalAmount);}
} 

日志效果


2023-08-15 14:30:25 [DEBUG] com.example.BusinessService:56 - 开始处理订单,订单ID: 1001
2023-08-15 14:30:26 [INFO] com.example.BusinessService:62 - 订单1001处理完成,总计金额: 299.00
2023-08-15 14:30:27 [ERROR] com.example.BusinessService:59 - 订单处理失败
java.lang.NullPointerException: Cannot read field "amount"at com.example.BusinessService.process(BusinessService.java:45)

此解决方案在保证正确获取类名和行号的同时,通过多级缓存和性能优化措施,将日志工具的性能开销降至最低,适合高并发生产环境使用。

进一步优化思路

上方提供的LogUtil1类中已经可以正确获取类名和行号,下方是我优化了一版之后的代码,优化的几点:
● 限制堆栈遍历深度
● 避免重复的类型检查
● 添加Lambda支持
● 统一日志处理接口
● 优化缓存结构

package com.lzc.log;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.spi.LocationAwareLogger;import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;/*** 限制堆栈遍历深度* 避免重复的类型检查* 添加Lambda支持* 统一日志处理接口* 优化缓存结构*/
public class LogUtil2 {// 工具类的完全限定名private static final String FQCN = LogUtil2.class.getName();// 最大堆栈遍历深度private static final int MAX_STACK_DEPTH = 15;// Logger缓存private static final Map<String, CachedLogger> LOGGER_CACHE = new ConcurrentHashMap<>();// 日志处理器接口private interface LogHandler {void log(String fqcn, int level, String format, Object[] args, Throwable t);}// 缓存Logger及其处理器private static class CachedLogger {final Logger logger;final LogHandler handler;CachedLogger(Logger logger) {this.logger = logger;if (logger instanceof LocationAwareLogger) {LocationAwareLogger lal = (LocationAwareLogger) logger;this.handler = (fqcn, level, format, args, t) ->lal.log(null, fqcn, level, format, args, t);} else {this.handler = (fqcn, level, format, args, t) -> {switch (level) {case LocationAwareLogger.TRACE_INT:logger.trace(format, args);break;case LocationAwareLogger.DEBUG_INT:logger.debug(format, args);break;case LocationAwareLogger.INFO_INT:logger.info(format, args);break;case LocationAwareLogger.WARN_INT:logger.warn(format, args);break;case LocationAwareLogger.ERROR_INT:logger.error(format, args, t);break;}};}}}/*** 高性能获取当前调用者的Logger*/private static CachedLogger getCallerLogger() {// 1. 获取当前调用栈StackTraceElement[] stack = new Throwable().getStackTrace();int depth = Math.min(stack.length, MAX_STACK_DEPTH);// 2. 跳过工具类自身和日志框架的栈帧for (int i = 0; i < depth; i++) {String className = stack[i].getClassName();// 找到第一个非工具类、非日志框架的调用者if (!className.equals(FQCN) &&!className.startsWith("org.slf4j") &&!className.startsWith("java.lang")) {// 3. 使用缓存避免重复创建Loggerreturn LOGGER_CACHE.computeIfAbsent(className, cn -> {Logger logger = LoggerFactory.getLogger(cn);return new CachedLogger(logger);});}}// 回退:使用工具类自身的Loggerreturn LOGGER_CACHE.computeIfAbsent(FQCN, cn ->new CachedLogger(LoggerFactory.getLogger(LogUtil2.class)));}// ============ TRACE级别 ============public static void trace(String format, Object... args) {CachedLogger cached = getCallerLogger();if (cached.logger.isTraceEnabled()) {cached.handler.log(FQCN, LocationAwareLogger.TRACE_INT, format, args, null);}}public static void trace(Supplier<String> messageSupplier) {CachedLogger cached = getCallerLogger();if (cached.logger.isTraceEnabled()) {trace(messageSupplier.get());}}// ============ DEBUG级别 ============public static void debug(String format, Object... args) {CachedLogger cached = getCallerLogger();if (cached.logger.isDebugEnabled()) {cached.handler.log(FQCN, LocationAwareLogger.DEBUG_INT, format, args, null);}}public static void debug(Supplier<String> messageSupplier) {CachedLogger cached = getCallerLogger();if (cached.logger.isDebugEnabled()) {debug(messageSupplier.get());}}// ============ INFO级别 ============public static void info(String format, Object... args) {CachedLogger cached = getCallerLogger();if (cached.logger.isInfoEnabled()) {cached.handler.log(FQCN, LocationAwareLogger.INFO_INT, format, args, null);}}public static void info(Supplier<String> messageSupplier) {CachedLogger cached = getCallerLogger();if (cached.logger.isInfoEnabled()) {info(messageSupplier.get());}}// ============ WARN级别 ============public static void warn(String format, Object... args) {CachedLogger cached = getCallerLogger();cached.handler.log(FQCN, LocationAwareLogger.WARN_INT, format, args, null);}public static void warn(String msg, Throwable t) {CachedLogger cached = getCallerLogger();cached.handler.log(FQCN, LocationAwareLogger.WARN_INT, msg, null, t);}public static void warn(Supplier<String> messageSupplier) {CachedLogger cached = getCallerLogger();warn(messageSupplier.get());}// ============ ERROR级别 ============public static void error(String format, Object... args) {CachedLogger cached = getCallerLogger();cached.handler.log(FQCN, LocationAwareLogger.ERROR_INT, format, args, null);}public static void error(String msg, Throwable t) {CachedLogger cached = getCallerLogger();cached.handler.log(FQCN, LocationAwareLogger.ERROR_INT, msg, null, t);}public static void error(Supplier<String> messageSupplier, Throwable t) {CachedLogger cached = getCallerLogger();error(messageSupplier.get(), t);}// 性能优化:清除缓存public static void clearCache() {LOGGER_CACHE.clear();}
}
http://www.lryc.cn/news/588509.html

相关文章:

  • 19.数据增强技术
  • LeetCode 692题解 | 前K个高频单词
  • JAVA进阶--JVM
  • C#——数据与变量
  • Brooks 低温泵On-Board Cryopump 安装和维护手法Installation and Maintenance Manual
  • 文献查找任务及其方法
  • 信息学奥赛一本通 1549:最大数 | 洛谷 P1198 [JSOI2008] 最大数
  • 7.14练习案例总结
  • YOLOv11开发流程
  • 数字化红头文件生成工具:提升群聊与团队管理效率的创新方案
  • H264的帧内编码和帧间编码
  • 每日mysql
  • RAG索引流程中的文档解析:工业级实践方案与最佳实践
  • 【Linux网络】:HTTP(应用层协议)
  • 学习软件测试的第十五天
  • 【DOCKER】-6 docker的资源限制与监控
  • Linux操作系统之信号:信号的产生
  • 深入学习前端 Proxy 和 Reflect:现代 JavaScript 元编程核心
  • Modbus 开发工具实战:ModScan32 与 Wireshark 抓包分析(二)
  • Swift 解 LeetCode 326:两种方法判断是否是 3 的幂,含循环与数学技巧
  • [硬件电路-21]:模拟信号处理运算与数字信号处理运算的详细比较
  • 无人机迫降模式模块运行方式概述!
  • ICMP隧道工具完全指南:原理、实战与防御策略
  • Datawhale AI夏令营大模型 task2.1
  • 【科研绘图系列】R语言绘制世界地图
  • 硬盘爆满不够用?这个免费神器帮你找回50GB硬盘空间
  • 【React Natve】NetworkError 和 TouchableOpacity 组件
  • 网络编程(TCP连接)
  • 代理模式详解:代理、策略与模板方法模式
  • 暑期自学嵌入式——Day02(C语言阶段)