`

间接调用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封装"是一个专门针对日志记录功能的自定义实现,旨在提供更高效、更便捷的日志输出方式,同时具备控制日志是否输出的能力。下面我们将详细讨论这个LOG封装的一些关键知识点。 首先,"MyLog.java"是这个...

    log4Qt 支持函数名,类名

    `Log4Qt`是一款基于Qt框架的日志记录库,它源自于Java中的著名日志框架`log4j`,并针对Qt进行了移植和优化,为Qt应用程序提供了强大的日志处理能力。`Log4Qt`允许开发者自定义日志输出的方式、级别、格式以及目的地...

    AndroidStudio 使用log4j记录日志,按照大小定期滚动日志Demo

    本文将详细介绍如何在Android Studio中使用log4j库来记录日志,并实现按照日志文件大小自动滚动的功能,以及如何进行日志内容的格式化输出。 首先,log4j是一款广泛使用的日志记录框架,它提供了灵活的日志配置和...

    log4J日志.zip

    在“log4j日志.zip”压缩包中,包含的可能是Log4j的配置文件(如log4j.properties或log4j.xml)。这个文件定义了日志的级别、输出位置和格式。例如: ```properties # log4j.properties 示例 log4j.rootLogger=...

    tomcat8更换log4j记录日志

    此压缩包提供的资源是针对Tomcat8及其以下版本的日志管理解决方案,主要涉及Log4j这个流行的Java日志框架。下面将详细介绍如何在Tomcat8中替换或更新Log4j来记录日志。 首先,让我们了解一下Log4j。Log4j是Apache...

    打log4j日志-ibatis的sql输出

    Log4j的配置文件通常是`log4j.properties`或`log4j.xml`,在这里你可以设置不同级别的日志输出,例如DEBUG、INFO、WARN、ERROR等。 对于Ibatis,这是一个轻量级的持久层框架,它将SQL语句与Java代码分离,提供了...

    log4j-1.2.17的jar包以及依赖包,还有一份log4j的配置文件,输出到控制台和文件夹两种配置

    总结来说,Log4j-1.2.17提供了灵活的日志管理功能,通过配置文件可以定制化日志输出的方式和内容,对于开发、调试和维护Java应用具有极大的帮助。尽管有更新的版本如Log4j2可用,但Log4j 1.x仍然在许多项目中得到...

    log4j+slf4j实现 log4j测试代码,log4j+slf4j实现 log4j测试代码

    Log4j和SLF4J(Simple Logging Facade for Java)是两个广泛使用的日志框架,它们各有优势并常被一起使用以提供更灵活的日志解决方案。本文将详细探讨如何通过SLF4J接口来使用Log4j进行日志记录,并展示一个测试代码...

    log4j日志输出格式化和日志文件名格式化

    Log4j作为Java平台上广泛使用的日志框架,提供了强大的日志输出和管理功能。本文将深入探讨如何利用Log4j进行日志输出格式化以及设置日志文件名格式化,以满足公司的标准化需求。 首先,我们来看日志输出格式化。...

    log4j 写多个日志文件,按照日期每天都记

    本文将深入探讨如何利用`log4j`实现按照日期每天写入不同日志文件的功能,以及涉及到的相关知识点。 首先,我们要理解`log4j.properties`配置文件的作用。它是`log4j`的配置中心,通过这个文件我们可以定制日志输出...

    log4j的eclipse工程,输出到文件的方式配置log4j

    标题"log4j的eclipse工程,输出到文件的方式配置log4j"表明我们关注的是在Eclipse开发环境中如何利用log4j框架将日志信息输出到文件。Log4j是Apache的一个开源项目,它提供了一个灵活的日志系统,允许开发者在运行时...

    .net log4jdemo log4j日志

    总的来说,Log4jDemo为.NET开发者提供了一种灵活的日志记录解决方案,使得在各种复杂情况下都能有效地管理和分析日志数据。通过熟悉和掌握Log4j的使用,开发者可以提升项目的可维护性,同时也能更好地应对调试和故障...

    Log4j2简介及与Log4j效率对比

    Log4j2是Apache软件基金会推出的日志框架,它是Log4j 1.x的重构版本,旨在提供更为高效且灵活的日志解决方案。与Log4j 1.x相比,Log4j2在设计上进行了重大改进,并解决了Logback等其他日志框架中存在的某些体系结构...

    简单java操作log4j+Mysql存储日志信息

    它为各种日志框架提供了统一的API,而SLF4J-log4j12-1.5.0.jar和slf4j-api-1.5.0.jar正是SLF4J与Log4j之间的桥梁,使得我们可以通过SLF4J接口调用Log4j的功能。 SLF4J API提供了`org.slf4j.Logger`接口,我们可以在...

    log4j 日志打印jar

    - Log4j是Apache软件基金会的开源项目,提供了一个灵活的日志系统,支持多种输出格式(如控制台、文件、数据库等)和多个日志级别(如DEBUG、INFO、WARN、ERROR、FATAL)。 - 它的核心组件包括:Logger(日志器)...

    如何使用Log4j如何使用Log4j

    无论是Windows还是Linux系统下的GUI应用,或者是网络追踪、Windows NT/2000事件记录以及UNIX Syslog等场景,Log4j都能够提供统一的日志记录解决方案。 #### 二、Log4j的安装与配置 在开始使用Log4j之前,我们需要...

    log4j日志框架

    Log4j的核心配置文件通常是`log4j.properties`或`log4j.xml`,它们定义了日志的输出方式、目的地和格式。例如,可以设置日志输出到控制台、文件,甚至是网络服务器。通过配置,可以指定不同的日志级别对不同类或包...

    Log4j生成日志保存

    Log4j是一款广泛应用于Java平台的日志记录框架,它的主要任务是帮助开发者记录应用程序运行过程中的各种信息,包括错误、警告、调试信息等。在Android平台上,虽然原生的`Log`类已经提供了基本的日志功能,但Log4j...

    Log4j实现日志操作

    Log4j是Java领域广泛应用的日志框架,因其强大的功能和灵活性而受到广大开发者的青睐。这篇博客将深入探讨如何使用Log4j进行日志操作。** 首先,我们需要理解Log4j的基本组件。Log4j由三部分组成:配置器...

Global site tag (gtag.js) - Google Analytics