`

间接调用Log4j的日志功能导致类名输出错误解决方案

 
阅读更多

在使用Log4j的时候,一般都是在每个类中定义一个Logger对象,通过该对象输出日志,此方法需要重复编写创建Logger对象的代码;

考虑编写一个公共Log类,对外提供静态日志输出方法,在该方法内部再调用Logger的方法进行日志输出;此方法下有一个弊端:

当日志中需要输出调用日志请求的类名、方法名、代码行数时,输出的是公共类(Log)中的相关信息,这不符合实际要求,通过分析Log4j的源码发现Log4j的调用堆栈结构如下:

在这里有个概念需要弄清楚,Log4j打出日志方法调用者的类名等信息是通过Java提供的堆栈跟踪信息实现的:

 

Throwable t = new Throwable();
StackTraceElement[] ste = t.getStackTrace();

从上图知道Log4j的调用堆栈结构如下:

 

Caller-->Category-->LoggingEvent-->LocationInfo,因为info等方法在Category中,故堆栈中不包含Logger

 

在Category创建LoggingEvent对象的时候会把FQCN传递过去,FQCN信息如下:

 

 private static final String FQCN = Category.class.getName();

 接下来看下Log4j是怎么在LocationInfo中获取调用者(调用日志的对象)的类名等信息的,以下是LocationInfo构造器中的主要代码

 

 

Object[] noArgs = null;
              Object[] elements =  (Object[]) getStackTraceMethod.invoke(t, noArgs);
              String prevClass = NA;
              for(int i = elements.length - 1; i >= 0; i--) {
                  String thisClass = (String) getClassNameMethod.invoke(elements[i], noArgs);
                  if(fqnOfCallingClass.equals(thisClass)) {
                      int caller = i + 1;
                      if (caller < elements.length) {
                          className = prevClass;
                          methodName = (String) getMethodNameMethod.invoke(elements[caller], noArgs);
                          fileName = (String) getFileNameMethod.invoke(elements[caller], noArgs);
                          if (fileName == null) {
                              fileName = NA;
                          }
                          int line = ((Integer) getLineNumberMethod.invoke(elements[caller], noArgs)).intValue();
                          if (line < 0) {
                              lineNumber = NA;
                          } else {
                              lineNumber = String.valueOf(line);
                          }
                          StringBuffer buf = new StringBuffer();
                          buf.append(className);
                          buf.append(".");
                          buf.append(methodName);
                          buf.append("(");
                          buf.append(fileName);
                          buf.append(":");
                          buf.append(lineNumber);
                          buf.append(")");
                          this.fullInfo = buf.toString();
                      }
                      return;
                  }
                  prevClass = thisClass;
              }

代码中fqnOfCallingClass即Category中的FQCN,可参考以下代码:

 

Category类:
public
  void info(Object message) {
    if(repository.isDisabled(Level.INFO_INT))
      return;
    if(Level.INFO.isGreaterOrEqual(this.getEffectiveLevel()))
      forcedLog(FQCN, Level.INFO, message, null);
  }
protected
  void forcedLog(String fqcn, Priority level, Object message, Throwable t) {
    callAppenders(new LoggingEvent(fqcn, this, level, message, t));
  }
LoggingEvent类:
public LoggingEvent(String fqnOfCategoryClass, Category logger,
		      Priority level, Object message, Throwable throwable) {
    this.fqnOfCategoryClass = fqnOfCategoryClass;
    this.logger = logger;
    this.categoryName = logger.getName();
    this.level = level;
    this.message = message;
    if(throwable != null) {
      this.throwableInfo = new ThrowableInformation(throwable, logger);
    }
    timeStamp = System.currentTimeMillis();
  }public LocationInfo getLocationInformation() {
    if(locationInfo == null) {
      locationInfo = new LocationInfo(new Throwable(), fqnOfCategoryClass);
    }
    return locationInfo;
  }

 

 从以上两段代码分析可知,LocationInfo在遍历调用堆栈的时候,匹配到Catetory类后,再往上取一层——即Caller,以此来得到调用者的信息。

 

综上所述,只要把创建LoggingEvent对象是的fqnOfCategoryClass换成公用类(Log)的名称即可,这样LocationInfo在遍历调用堆栈的时候,在匹配到公用类(Log)后再往上取一层——即得到真正需要调用日志的对象的类信息。

 

实际上Category中所有日志相关方法(info、debug、error等)都是通过forcedLog方法创建LoggingEvent对象的——从上述代码可见,并且forcedLog方法是protected的,因此只要覆写该方法,改变fqnOfCategoryClass参数为公用类(Log)即可,实现的结构如下:

 

 

 

 

因为要使用自定义的MyLogger,因此需要自定义工厂类,在Log中按如下方式使用MyLogger

private final static Logger log = MyLogger.getLogger(Log.class.getName(),new MyLoggerFactory());
	
	public static void debug(String msg){
		log.debug(msg);
	}
	public static void debug(String msg,Throwable t){
		log.debug(msg,t);
	}
	
	public static void info(String msg){
		log.info(msg);
	}
	public static void info(String msg,Throwable t){
		log.info(msg,t);
	}
}

 

这样在其他地方直接调用Log的静态日志方法也能正确的输出调用者信息!

 附件附上代码~~

  • 大小: 25.5 KB
  • 大小: 36.3 KB
  • log.zip (1.3 KB)
  • 下载次数: 70
分享到:
评论
1 楼 我的最爱JJ 2013-05-29  
mark

相关推荐

    很好用的LOG封装,可同时输出类名,方法名,行数,可控制输出不输出

    很好用的LOG封装,可同时输出类名,方法名,行数,可控制输出不输出

    Log4j日志管理系统简单使用说明

    Log4j有三个主要的组件:Loggers,Appenders和Layouts,这里可简单理解为日志类别,日志要输出的地方和日志以何种形式输出。综合使用这三个组件可以轻松的记录信息的类型和级别,并可以在运行时控制日志输出的样式和...

    log4Qt 支持函数名,类名

    log4Qt 支持函数名,类名,可以自己随意修改代码,本想免费分享,但最小是1分

    深入学习log4j

    Loggers组件的主要功能是提供相应API,根据不同配置的loggers将不同级别的log输入到控制台或文件,类似于java中经常用到的System.out.println,但是log4j封装后的loggers组件能够输出更丰富的信息,包括时间,线程,...

    打印日志等异常处理,使用Log4j的配置

    #如果一条日志信息的级别大于等于配置文件的级别,就记录配置输出源所对应的辅助类:log4j.appender.输出源名称=类名,如果输出到文件就写FileAppender #指定文件名 Tomcat的根目录: #指定布局方式(消息放入文件...

    log4j使用实战

    log4j.rootLogger=INFO,CONSOLE log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender INFO是日志记录的最低等级,必须写,它和比它高的等级会进入日志,如WARN、ERROR、FATAL、OFF。 关于8个日志等级,参考:...

    log4j多文件目标输出

    log4j多文件目标输出

    log4j 工具类 多个日志文件

    NULL 博文链接:https://zw7534313.iteye.com/blog/2221183

    超实用的android自定义log日志输出工具类

    android自定义log日志输出工具,该工具类具有以下优点: 1 在LogUtlis方法的第一个参数中填this可以输出当前类的名称,特别是在匿名内部类使用也可以输出当前类名。 如 : LogUtils.i(this,”这是一个实用的日志...

    log4j的配置使用

    log4j配置文件,及使用,配置相应包 private static final Logger logger = Logger.getLogger(类名.class.getName()); 在每个类前加该语句,就可以输出相应日志

    log4jToJDBCAppender.zip

    本工程用于研究log4j日志输出目的地org.apache.log4j.jdbc.JDBCAppender的使用方法 本工程编码方式:UTF-8 本工程开发工具:MyEclipse 本工程需要执行的SQL语句: CREATE DATABASE `test`; CREATE TABLE `...

    类名查看器类名查看器

    类名查看器类名查看器类名查看器类名查看器类名查看器类名查看器

    android调用第三方程序,需要包名、类名

    android自己调用第三方程序,需要包名、类名。

    qt日志管理类 log4qt

    log4qt库包括以下几种类供用户组合使用: 1)Logger,用于供要记录log的类使用,向log4qt系统加入信息,比如刚才的那个...3)Layout,指定Log输出时的格式,比如是否要带有当前日期时间,是否带有当前的类名,等等。

    易语言动态调用类模块

    易语言动态调用类模块源码,动态调用类模块,释放动态链接库_,载入动态链接库_,动态调用子程序_,动态调用子程序_类型,转换到逻辑型,取字节型指针_,取字节集指针_,取短整数指针_,取整数型指针_,取小数型指针_,取长整数...

    VC修改窗口类名

    VC修改窗口类名

    打印调试日志

    日志系统,可以打印调试日志。有兴趣的可以下载。

    OpenCascade类名及功能分析

    帮助查看Opencascade源码下头文件和类的使用查找,引导编写和学习Opencascade

    易语言修改窗口类名

    易语言修改窗口类名易语言源码,能用,易语言修改窗口类名.e

Global site tag (gtag.js) - Google Analytics