如何封装java.util.Logger日志方法且不丢失调用方方法名称
可行解决思路
思路1:采用组合而非继承实现自定义Logger(推荐)
原生java.util.Logger的实例创建、缓存逻辑和JDK模块系统强绑定,强行继承很容易触发类加载、实例转换异常,组合是更稳妥的方案。
核心解决调用方信息错误的方法:使用JUL原生提供的logp方法,该方法支持直接传入调用方类名、方法名,JUL会直接使用你传入的信息记录日志,不会再自行推导调用方。
可通过StackWalker(Java9+)或遍历栈轨迹(Java8及以下)获取正确的业务调用方信息,完整实现示例如下:
public class CustomLogger { private final Logger delegate; // Java9+ 栈轨迹查询工具,Java8及以下可替换为Thread.currentThread().getStackTrace()实现 private static final StackWalker STACK_WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); // 私有构造,禁止外部直接实例化 private CustomLogger(Logger delegate) { this.delegate = delegate; } // 对外提供的获取实例方法,完全避开原生Logger的继承限制 public static CustomLogger getLogger(String name) { return new CustomLogger(Logger.getLogger(name)); } public static CustomLogger getLogger(Class<?> clazz) { return new CustomLogger(Logger.getLogger(clazz.getName())); } // 内部通用日志打印方法,处理调用方信息 private void logSevere(String msg, Exception e) { // 跳过CustomLogger自身的栈帧,找到业务调用方 StackWalker.StackFrame callerFrame = STACK_WALKER.walk(stream -> stream.dropWhile(frame -> frame.getDeclaringClass() == CustomLogger.class) .findFirst() .orElse(null) ); if (callerFrame != null) { // 调用logp传入调用方类名、方法名 if (e != null) { delegate.logp(Level.SEVERE, callerFrame.getClassName(), callerFrame.getMethodName(), msg, e); } else { delegate.logp(Level.SEVERE, callerFrame.getClassName(), callerFrame.getMethodName(), msg); } } else { // 兜底逻辑,找不到调用方时用普通打印 if (e != null) { delegate.log(Level.SEVERE, msg, e); } else { delegate.log(Level.SEVERE, msg); } } } // 你自定义的便捷方法 public void severeException(Exception e, String msg) { String fullMsg = String.join(System.lineSeparator(), msg, e.getClass().getCanonicalName(), e.getMessage() ); logSevere(fullMsg, e); } public void severeLoadingFile(Exception e, String filePath) { String msg = "Could not load file '" + filePath + "'"; String fullMsg = String.join(System.lineSeparator(), msg, e.getClass().getCanonicalName(), e.getMessage() ); logSevere(fullMsg, e); } public void severeLoadingFile(String filePath) { logSevere("Could not load file '" + filePath + "'", null); } }
该实现完全不需要继承原生Logger,不会有类加载、模块系统冲突问题,所有日志的调用方信息都是业务层的实际调用方法,不会显示为CustomLogger的封装方法,你之前定义的所有便捷方法的语义完全保留,业务代码不需要修改调用逻辑。
思路2:自定义LogRecord手动设置调用方信息
如果需要更灵活的日志定制,可以手动构造LogRecord实例,主动设置sourceClassName和sourceMethodName属性,再调用原生Logger的log(LogRecord)方法提交日志,效果和logp完全一致。
可选优化:直接使用日志门面框架
如果没有强制要求必须使用原生JUL,可以引入SLF4J等门面框架,其API本身就提供了大量便捷的异常日志打印方法,且天然解决调用方信息记录的问题,不需要自行封装基础逻辑。
内容的提问来源于stack exchange,提问作者coterobarros
相关产品推荐
相关产品推荐

