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

Java Custom Logger无法正确打印指定类名问题咨询

问题根因

  1. Logger实例被static修饰导致全局覆盖:代码中的log是类静态变量,每次实例化MyLogger都会覆盖该变量,多实例场景下所有日志都会输出到最后一次初始化的Logger下,无法正确对应传入的name参数。
  2. JUL自动推断调用栈错位:java.util.logging的SimpleFormatter中%2$s占位符默认取的是直接调用Logger.log()方法的类,代码中是在MyLogger的封装方法内调用的log.log(),所以JUL推断出的调用类永远是MyLogger,不会直接使用Logger的name字段,也无法识别外层业务调用类。
  3. 配置重复加载问题:配置读取逻辑放在构造方法中,每次创建MyLogger实例都会重新加载全局日志配置,会引发并发异常、配置覆盖等问题。

修复方案

第一步:修复基础逻辑问题

  • 去掉log变量的static修饰,改为实例私有常量,保证每个MyLogger实例持有独立的Logger对象
  • 将配置加载逻辑移到静态代码块,全局仅执行一次,避免重复加载

第二步:二选一解决类名打印问题

方案1:使用logp方法显式指定源类名

JUL提供的logp方法支持手动传入源类名、源方法名,跳过自动调用栈推断,直接将构造时传入的name作为源类名传入即可。
修复后的MyLogger代码示例:

import java.io.FileInputStream;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

public class MyLogger {
    // 改为实例持有,移除static修饰
    private final Logger log;
    
    // 配置全局仅加载一次
    static {
        try {
            LogManager.getLogManager().readConfiguration(new FileInputStream("./logging.properties"));
        } catch (IOException | SecurityException ex) {
            Logger.getLogger(MyLogger.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    public static MyLogger getLogger(String name) {
        return new MyLogger(name);
    }

    private MyLogger(String name) {
        log = Logger.getLogger(name);
    }

    public void log(Level level, String logMessage, Object param1) {
        // 取外层调用的堆栈元素,获取实际业务调用的方法名
        StackTraceElement caller = Thread.currentThread().getStackTrace()[2];
        // logp参数依次为:日志级别、源类名、源方法名、日志内容、参数
        log.logp(level, log.getName(), caller.getMethodName(), logMessage, param1);
    }

    public void log(Level level, String logMessage, Throwable e) {
        StackTraceElement caller = Thread.currentThread().getStackTrace()[2];
        log.logp(level, log.getName(), caller.getMethodName(), logMessage, e);
    }

    public void log(Level level, String logMessage) {
        StackTraceElement caller = Thread.currentThread().getStackTrace()[2];
        log.logp(level, log.getName(), caller.getMethodName(), logMessage);
    }
}
方案2:自定义Formatter直接取Logger的name作为输出类名

重写SimpleFormatter,直接读取LogRecord.getLoggerName()作为类名字段输出,从根源避免调用栈推断误差。
自定义Formatter代码示例:

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.LogRecord;
import java.util.logging.SimpleFormatter;

public class CustomLogFormatter extends SimpleFormatter {
    private static final String LOG_FORMAT = "%1$tF %1$tT %4$s %2$s %5$s%6$s%n";
    
    @Override
    public String format(LogRecord record) {
        // 直接使用Logger的name作为类名输出,替代默认的调用栈推断结果
        String sourceClassName = record.getLoggerName();
        // 处理异常栈
        String throwableInfo = "";
        if (record.getThrown() != null) {
            StringWriter sw = new StringWriter();
            record.getThrown().printStackTrace(new PrintWriter(sw));
            throwableInfo = sw.toString();
        }
        return String.format(LOG_FORMAT,
                record.getMillis(),
                sourceClassName,
                record.getLoggerName(),
                record.getLevel().getLocalizedName(),
                formatMessage(record),
                throwableInfo
        );
    }
}

修改logging.properties中的Formatter配置即可生效:

# 替换为你自定义Formatter的全类名
java.util.logging.FileHandler.formatter = com.yourpackage.CustomLogFormatter

内容的提问来源于stack exchange,提问作者AKPuvvada

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 17:39:05