`
flyfoxs
  • 浏览: 298102 次
  • 性别: Icon_minigender_1
  • 来自: 合肥
社区版块
存档分类
最新评论

Log4j是如何取得行号的(有源码分析)

 
阅读更多

Log4J是如何打印出行号的呢,之前一直以为是通过JAVA的反射.今天特意去查了一下,JAVA的方式好像没有提供这样的接口.于是研究了一下Log4J的代码,现在分享出来.

 

  • 开门见山:

直接开门见山的讲重点, 其实要获得JAVA的行号,Log4j就是先抛出异常,然后在异常堆栈中找到调用函数所在位置. 下面根据下面的一个异常堆栈,来分析如何通过2歩找到找位置的办法.

1)找到org.apache.commons.logging.impl.Log4JLogger所在行

2)接下来,去掉下一行的1个空格,一个"at",就可以得到 "log.Main.main(Main.java:22)"

 

 

 

java.lang.Throwable
	at org.apache.log4j.spi.LoggingEvent.getLocationInformation(LoggingEvent.java:191)
	at org.apache.log4j.helpers.PatternParser$LocationPatternConverter.convert(PatternParser.java:483)
	at org.apache.log4j.helpers.PatternConverter.format(PatternConverter.java:64)
	at org.apache.log4j.PatternLayout.format(PatternLayout.java:503)
	at org.apache.log4j.WriterAppender.subAppend(WriterAppender.java:301)
	at org.apache.log4j.WriterAppender.append(WriterAppender.java:159)
	at org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:230)
	at org.apache.log4j.helpers.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:65)
	at org.apache.log4j.Category.callAppenders(Category.java:203)
	at org.apache.log4j.Category.forcedLog(Category.java:388)
	at org.apache.log4j.Category.log(Category.java:853)
	at org.apache.commons.logging.impl.Log4JLogger.debug(Log4JLogger.java:171)
	at log.Main.main(Main.java:22)

 

 

  • 关键代码分析:

Log4j打印出行号,关键就在LocationInfo. 这个Class就是通过对异常堆栈和org.apache.commons.logging.Log的实例(比如:org.apache.commons.logging.impl.Log4JLogger)的处理,来得到fullInfo(比如:log.Main.main(Main.java:22)). 接下来基于fullInfo再得到行号,类名,包名就手到擒来了.

 

下面是LocationInfo的构造函数的分析:
    public LocationInfo(Throwable t, String fqnOfCallingClass) {
      if(t == null)
	return;

      String s;
      // Protect against multiple access to sw.
      synchronized(sw) {
	t.printStackTrace(pw);
	s = sw.toString();
	sw.getBuffer().setLength(0);
      }
      //System.out.println("s is ["+s+"].");
      int ibegin, iend;

      // Given the current structure of the package, the line
      // containing "org.apache.log4j.Category." should be printed just
      // before the caller.

      // This method of searching may not be fastest but it's safer
      // than counting the stack depth which is not guaranteed to be
      // constant across JVM implementations.
	  //找到Log4JLogger对应的行的行头
      ibegin = s.lastIndexOf(fqnOfCallingClass);
      if(ibegin == -1)
	return;

	   //找到Log4JLogger对应的行的行尾
      ibegin = s.indexOf(Layout.LINE_SEP, ibegin);
      if(ibegin == -1)
	return;
	  //后移到下一行的行头,也就是真正需要信息所在行 
      ibegin+= Layout.LINE_SEP_LEN;

      // determine end of line
	  //找到真正需要信息所在行的行尾
      iend = s.indexOf(Layout.LINE_SEP, ibegin);
      if(iend == -1)
	return;

      // VA has a different stack trace format which doesn't
      // need to skip the inital 'at'
      if(!inVisualAge) {
	// back up to first blank character
	//判断,真正信息说在行是否有"at "这个关键字
	ibegin = s.lastIndexOf("at ", iend);
	if(ibegin == -1)
	  return;
	// Add 3 to skip "at ";
	//如果有"at "这样的关键字,就过滤掉.
	ibegin += 3;
      }
      // everything between is the requested stack item
      this.fullInfo = s.substring(ibegin, iend);
    }

 

 

  • 来龙去脉:

为了分析Log4j行号的来龙去脉,我写了下面的测试类,

package log;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class Main {
	/**
	 * Logger for this class
	 */
	private static final Log logger = LogFactory.getLog(Main.class);

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		if (logger.isDebugEnabled()) {
			logger.debug("main(String[]) - start"); //$NON-NLS-1$
		}
		 

		if (logger.isDebugEnabled()) {
			logger.debug("main(String[]) - end"); //$NON-NLS-1$
		}
	}

}

 

 

然后基于上面的代码,跟踪到了如下的调用栈,getLocationInformation就是上面分析得到行号的关键地方.

Thread [main] (Suspended)	
	owns: ConsoleAppender  (id=53)	
	owns: RootLogger  (id=49)	
	LocationInfo.<init>(Throwable, String) line: 146	
	LoggingEvent.getLocationInformation() line: 191	
	PatternParser$LocationPatternConverter.convert(LoggingEvent) line: 483	
	PatternParser$LocationPatternConverter(PatternConverter).format(StringBuffer, LoggingEvent) line: 64	
	PatternLayout.format(LoggingEvent) line: 503	
	ConsoleAppender(WriterAppender).subAppend(LoggingEvent) line: 301	
	ConsoleAppender(WriterAppender).append(LoggingEvent) line: 159	
	ConsoleAppender(AppenderSkeleton).doAppend(LoggingEvent) line: 230	
	AppenderAttachableImpl.appendLoopOnAppenders(LoggingEvent) line: 65	
	Logger(Category).callAppenders(LoggingEvent) line: 203	
	Logger(Category).forcedLog(String, Priority, Object, Throwable) line: 388	
	Logger(Category).log(String, Priority, Object, Throwable) line: 853	
	Log4JLogger.debug(Object) line: 171	
	Main.main(String[]) line: 22	

	

 

 

 

3
1
分享到:
评论
6 楼 flyfoxs 2014-07-11  
@fainfy
谢谢你的回复.

1) 下面的代码是 Thread.currentThread().getStackTrace() 对应的源码,可以很清楚的看到是和异常抛不开关系,特别是高亮处.

public StackTraceElement[] getStackTrace() {
        if (this != Thread.currentThread()) {
            // check for getStackTrace permission
            SecurityManager security = System.getSecurityManager();
            if (security != null) {
                security.checkPermission(
                    SecurityConstants.GET_STACK_TRACE_PERMISSION);
            }
    if (!isAlive()) {
return EMPTY_STACK_TRACE;//返回的一个空数组,没有任何内容
    }
    return dumpThreads(new Thread[] {this})[0]; //本地Native方法,机制不了解了.
        } else {
    // Don't need JVM help for current thread
    return (new Exception()).getStackTrace();
}
    }

2)你可以看看StackTraceElement对应的JavaDoc很清楚.

3)Log4j就是这样得到行号的,上面代码都贴出来了,你可以看看,如果我分析错了你可纠正.

4)你说的方法是获取行号的办法,但是是需要自己写代码去实现的,如果你来写Log4j怎么来实现,需要对原有办法没有侵入,我是没想到好的办法,除非JDK有反射来实现.
5 楼 fainfy 2014-07-11  
flyfoxs 写道
fainfy 写道
得到行号就是JDK提供的内置方法,没有你讲的这么复杂.
java.lang.StackTraceElement 你应该去查阅该类型的API说明.


有2点需要说明一下.
1)StackTraceElement 这个好像是和异常绑定的,你可以查看这个Class的Javadoc
2)Log4j取得行号,底层还是基于StackTraceElement.
你说的这个是提供了行号,但是这个行号是针对整个异常的,你看这个异常的每一行最后都有一个行号,就是StackTraceElement 来得到的.
但是如何得到业务逻辑对应的行号,而不是log4j自己代码的行号,还是需要一些处理的.


java.lang.Throwable
at org.apache.log4j.spi.LoggingEvent.getLocationInformation(LoggingEvent.java:191)
at org.apache.log4j.helpers.PatternParser$LocationPatternConverter.convert(PatternParser.java:483)
at org.apache.log4j.helpers.PatternConverter.format(PatternConverter.java:64)
at org.apache.log4j.PatternLayout.format(PatternLayout.java:503)
at org.apache.log4j.WriterAppender.subAppend(WriterAppender.java:301)
at org.apache.log4j.WriterAppender.append(WriterAppender.java:159)
at org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:230)
at org.apache.log4j.helpers.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:65)
at org.apache.log4j.Category.callAppenders(Category.java:203)
at org.apache.log4j.Category.forcedLog(Category.java:388)
at org.apache.log4j.Category.log(Category.java:853)
at org.apache.commons.logging.impl.Log4JLogger.debug(Log4JLogger.java:171)
at log.Main.main(Main.java:22)


第1 StackTraceElement它并不是专门为Exception服务的, 至少我可以通过下面的方式来获取当前的上下文信息.
Thread.currentThread().getStackTrace()
第2 行号是由JDK编译的DEBUG信息. log4j获取行号的唯一途径就是通过JDK提供的Api, 至于你说的处理, 只是一个渲染问题, 这跟所谓的获取行号本身没有直接的联系.
4 楼 flyfoxs 2014-07-11  
fainfy 写道
得到行号就是JDK提供的内置方法,没有你讲的这么复杂.
java.lang.StackTraceElement 你应该去查阅该类型的API说明.


有2点需要说明一下.
1)StackTraceElement 这个好像是和异常绑定的,你可以查看这个Class的Javadoc
2)Log4j取得行号,底层还是基于StackTraceElement.
你说的这个是提供了行号,但是这个行号是针对整个异常的,你看这个异常的每一行最后都有一个行号,就是StackTraceElement 来得到的.
但是如何得到业务逻辑对应的行号,而不是log4j自己代码的行号,还是需要一些处理的.


java.lang.Throwable
at org.apache.log4j.spi.LoggingEvent.getLocationInformation(LoggingEvent.java:191)
at org.apache.log4j.helpers.PatternParser$LocationPatternConverter.convert(PatternParser.java:483)
at org.apache.log4j.helpers.PatternConverter.format(PatternConverter.java:64)
at org.apache.log4j.PatternLayout.format(PatternLayout.java:503)
at org.apache.log4j.WriterAppender.subAppend(WriterAppender.java:301)
at org.apache.log4j.WriterAppender.append(WriterAppender.java:159)
at org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:230)
at org.apache.log4j.helpers.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:65)
at org.apache.log4j.Category.callAppenders(Category.java:203)
at org.apache.log4j.Category.forcedLog(Category.java:388)
at org.apache.log4j.Category.log(Category.java:853)
at org.apache.commons.logging.impl.Log4JLogger.debug(Log4JLogger.java:171)
at log.Main.main(Main.java:22)
3 楼 fainfy 2014-07-10  
得到行号就是JDK提供的内置方法,没有你讲的这么复杂.
java.lang.StackTraceElement 你应该去查阅该类型的API说明.
2 楼 flyfoxs 2014-07-10  
hamber 写道
这样很坑爹吧,每次都整个异常出来,很影响性能的样子吧。


你说的对,最初我也有这样的问题,其实也就是如果行号对你不是很重要不打印行号就避免了抛出异常的开销.

下面是2种打印日志的方法(注意高亮的地方):方法一就不需要抛出异常,也就不坑爹了. 但是方法二对查找问题很有帮助.如果性能问题不是很关键,可以考虑试用方法二,或者测试时使用方法二.

log4j.appender.stdout.layout.ConversionPattern=%d{MM-dd HH:mm:ss.SSS} %-4r  %-5p [%t]  %37c %3x - %m%n
07-10 09:41:41.578 1     DEBUG [main]                               log.Main     - main(String[]) - start
07-10 09:41:44.015 2438  DEBUG [main]                               log.Main     - main(String[]) - end


log4j.appender.stdout.layout.ConversionPattern=%d{MM-dd HH:mm:ss.SSS} %-4r  %-5p [%t]  %37l %3x - %m%n
07-10 09:46:10.542 1     DEBUG [main]            log.Main.main(Main.java:17)     - main(String[]) - start
07-10 09:46:21.616 11075  DEBUG [main]            log.Main.main(Main.java:22)     - main(String[]) - end
1 楼 hamber 2014-07-10  
这样很坑爹吧,每次都整个异常出来,很影响性能的样子吧。

相关推荐

    Spring项目中怎么配置log4j

    在Spring项目中配置log4j是一项基础且重要的工作,它能帮助我们记录应用程序的运行日志,便于调试、排查问题和性能分析。Log4j是一个广泛使用的Java日志框架,提供灵活的日志记录功能。接下来,我们将详细讲解如何在...

    log4j 实例

    **标题:“log4j 实例”** 在Java开发中,日志记录是不可或缺的一部分,而Log4j作为一款广泛使用的开源日志框架,为开发者提供了强大的日志处理能力。本实例将深入探讨如何在实际项目中应用Log4j,帮助你理解和掌握...

    log4j-1.2.14.jar.zip 335k

    《深入理解Log4j:基于1.2.14版本的分析》 在Java开发领域,日志管理是一项至关重要的任务,它可以帮助开发者追踪程序运行状态,定位问题,以及进行性能监控。Log4j作为Apache组织开发的一个开源日志框架,因其强大...

    log4j的配置示例

    工具方面,Log4j提供了强大的日志管理和分析能力,可以帮助开发者定位问题、监控系统状态,同时,通过不同的Appender和布局,可以灵活地定制日志输出的格式和存储方式。 **压缩包子文件的文件名称列表:Log4jDemo**...

    log4j.properties的简单运用

    《log4j.properties的简单运用》 在Java开发中,日志记录是一项至关重要的任务,它可以帮助开发者追踪程序运行过程中的错误、调试信息以及其他关键事件。Log4j是一款广泛使用的日志框架,它允许我们自定义日志级别...

    打log4j日志-ibatis的sql输出

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

    log4j的源码

    源码分析可以帮助我们深入理解其工作原理,从而更好地利用它或进行定制化开发。 首先,我们来看看`logging-log4j-1.2.14`这个版本。这是Log4j的一个较旧但广泛使用的版本。尽管现在已经有更新的Log4j 2.x系列,但...

    log4j 日志配置

    1. **配置文件**:Log4j的配置通常在`log4j.properties`或`log4j.xml`文件中完成。在这个文件中,你可以定义日志的级别(如DEBUG、INFO、WARN、ERROR、FATAL),设置日志输出目的地(控制台、文件、数据库等),以及...

    log4j.properties 全部应用

    《log4j.properties全应用详解》 在Java开发中,日志记录是不可或缺的一部分,它帮助开发者追踪程序运行状态,定位错误,优化性能。本文将深入探讨log4j.properties配置文件的使用,以及如何在实际项目中充分利用其...

    一个Log4j配置文件,感觉还不错

    如`log4j.appender.FILE.layout=org.apache.log4j.PatternLayout`并指定模式`log4j.appender.FILE.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n`,定义了日期、优先级、类名、行号和消息等信息。...

    log4jdemo包

    **标题与描述解析** 标题"Log4jDemo包"指的是一个使用...同时,结合`src`目录下的Java源码,我们可以深入理解Log4j的使用和配置。这个项目对于初学者来说,是一个很好的起点,帮助他们掌握Java日志处理的关键概念。

    log4j配置文件

    此外,Log4j与其他开源工具,如IDE插件、构建工具(Maven、Gradle)等有很好的集成,方便在项目中快速配置和使用。 总结,`log4j.properties`是Log4j的核心配置文件,通过它可以精细化控制日志的输出。熟练掌握这个...

    log4j配置祥解

    **标题:“log4j配置详解”** 在Java开发中,日志记录是不可或缺的一部分,它可以帮助开发者追踪程序运行状态,定位错误,以及进行性能分析。Log4j是一款广泛使用的日志框架,提供了灵活且高效的日志记录功能。这篇...

    log4j之小试牛刀

    此外,Log4j2提供了更丰富的功能,如异步日志记录,性能上有显著提升;支持JSON、XML等结构化日志格式,便于后期数据处理;并且有更强大的配置方式,比如通过Java API动态调整日志配置。 总的来说,Log4j作为一个...

    log4j.properties

    通过熟练掌握`log4j.properties`配置,开发者可以构建出符合项目需求的日志管理系统,实现日志的高效记录、查询和分析。了解并充分利用这些特性,将有助于提升软件的可维护性和稳定性,同时降低故障排查的成本。 在...

    一个配置比较全的log4j.xml文件,配置的正确性已在项目中验证。

    《全面解析Log4j.xml配置文件》 在Java开发中,日志管理是不可或缺的一部分,它可以帮助我们跟踪程序运行状态,排查错误,优化性能。Log4j作为一款广泛应用的日志框架,其强大的功能和灵活的配置深受开发者喜爱。...

    log4c完整例子源代码

    log4c 是一个基于 C 语言的日志记录框架,它借鉴了 Java 中的 log4j 设计理念,旨在为 C 语言开发者提供一个功能强大、灵活且易于使用的日志系统。这个压缩包包含了 log4c 的一个完整实例,适合初学者学习和参考。 ...

    logging-log4cxx-0.11.0.zip

    通过对log4cxx源码的分析,我们可以看到其设计的精巧之处,以及如何通过配置实现高效、灵活的日志管理。掌握log4cxx的使用和原理,对于提升C++项目的可维护性和稳定性具有重要意义。在实际开发中,理解并利用这些...

    一个简单的获取异常信息的例子,包括行号

    在实际应用中,我们可以通过日志框架(如Log4j、SLF4J)记录这些异常信息,以便于后期分析。同时,为了不影响正常的`Assert`功能,我们可以在特定的测试环境中使用这个自定义版本,而在生产环境中保持原版`Assert`。...

    liferay里的日志管理

    `portal-log4j-ext.xml`是Liferay日志配置的核心文件,用于定制日志行为。这篇文章将深入探讨Liferay的日志管理系统及其与`log4j`框架的集成。 Liferay日志系统基于Apache Log4j,一个广泛使用的开源日志记录库。...

Global site tag (gtag.js) - Google Analytics