You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何封装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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 19:54:01