Java Custom Logger无法正确打印指定类名问题咨询
问题根因
- Logger实例被static修饰导致全局覆盖:代码中的
log是类静态变量,每次实例化MyLogger都会覆盖该变量,多实例场景下所有日志都会输出到最后一次初始化的Logger下,无法正确对应传入的name参数。 - JUL自动推断调用栈错位:
java.util.logging的SimpleFormatter中%2$s占位符默认取的是直接调用Logger.log()方法的类,代码中是在MyLogger的封装方法内调用的log.log(),所以JUL推断出的调用类永远是MyLogger,不会直接使用Logger的name字段,也无法识别外层业务调用类。 - 配置重复加载问题:配置读取逻辑放在构造方法中,每次创建
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
相关产品推荐
相关产品推荐

