You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

为Java日志File Handler的Formatter获取调用类名

获取包装Logger后实际调用日志的类

好问题!当你对Logger做了包装后,LogRecord.getSourceClassName()确实会返回包装类的名称,而不是实际调用log方法的业务类——这是因为默认的LogRecordLogger.log被调用时,会把当前执行的类(也就是你的包装类)填充为源类名,所以这个方法自然就失效了。

不过我们可以通过线程栈轨迹来绕过这个限制,直接找到实际发起日志调用的类。下面是具体的实现思路和代码示例:

实现步骤

  • 在自定义Formatterformat方法中,获取当前线程的栈轨迹数组。
  • 遍历栈轨迹,过滤掉java.util.logging包下的类、你的日志包装类,找到第一个符合条件的栈元素。
  • 从这个栈元素中提取实际调用类的名称。

代码示例

假设你的日志包装类叫WrappedLogger,自定义Formatter可以这么写:

import java.util.logging.Formatter;
import java.util.logging.LogRecord;

public class CustomFormatter extends Formatter {
    // 定义需要排除的类前缀
    private static final String[] EXCLUDED_PREFIXES = {
        "java.util.logging.",
        "com.yourpackage.WrappedLogger" // 替换成你的包装类全限定名
    };

    @Override
    public String format(LogRecord record) {
        // 获取实际调用类的名称
        String actualClassName = getActualCallingClassName();
        
        // 组装日志内容,这里可以根据你的需求自定义格式
        return String.format("[%s] [%s] %s%n",
                actualClassName,
                record.getLevel().getName(),
                record.getMessage());
    }

    private String getActualCallingClassName() {
        // 获取当前线程的栈轨迹
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        
        // 遍历栈元素,跳过不需要的类
        for (StackTraceElement element : stackTrace) {
            String className = element.getClassName();
            boolean isExcluded = false;
            
            // 检查是否在排除列表中
            for (String prefix : EXCLUDED_PREFIXES) {
                if (className.startsWith(prefix)) {
                    isExcluded = true;
                    break;
                }
            }
            
            if (!isExcluded) {
                return className;
            }
        }
        
        // 如果找不到,返回默认值
        return "UnknownClass";
    }
}

注意事项

  • 性能影响:获取线程栈轨迹是相对耗时的操作,如果你的系统日志量极大,可能会有一定性能开销。如果是普通业务场景,这个影响可以忽略不计。
  • 精确性调整:如果你的日志包装类有多层,或者有其他中间类需要排除,可以在EXCLUDED_PREFIXES中添加对应的前缀或全类名。
  • JVM优化:某些JVM优化可能会影响栈轨迹的准确性,比如方法内联。如果遇到这种情况,可以考虑在包装类的log方法中手动填充LogRecord的源类名。

更高效的可选方案:包装时手动传递调用类

如果你觉得遍历栈轨迹太耗时,也可以在包装类的log方法中,提前获取调用类的信息,然后设置到LogRecord中:

import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.LogRecord;

public class WrappedLogger {
    private final Logger delegate;

    public WrappedLogger(Logger delegate) {
        this.delegate = delegate;
    }

    public void log(Level level, String msg) {
        // 获取调用类的名称(跳过当前类和Thread类的栈元素)
        String callingClassName = Thread.currentThread().getStackTrace()[2].getClassName();
        LogRecord record = new LogRecord(level, msg);
        record.setSourceClassName(callingClassName);
        delegate.log(record);
    }
}

这种方式只在包装类的log方法中获取一次栈轨迹,比在Formatter中每次都获取更高效,而且Formatter可以直接使用record.getSourceClassName()拿到正确的类名。

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

火山引擎 最新活动