Log信息獲取調用類和調用方法名的實現原理

剛好看到關於log的討論。想起之前調查的一個問題。整理出來,但願對你們能有所幫助。

Sun JDK 源代碼下載 http://wwws.sun.com/software/communitysource/
先註冊並登陸到「Sun Community Source Licensing」,而後下載J2SE(幾十兆)或者J2EE(幾百兆)。

Log可以把代碼運行時間,類名,方法名,還有信息,所有都打印出來。
一個直觀的例子,每次啓動Tomcat(缺省配置)的時候。通常能夠看到
Jul 9, 2004 11:22:29 AM org.apache.struts.util.PropertyMessageResources <init>
INFO: Initializing, config='org.apache.webapp.admin.ApplicationResources', returnNull=true
Jul 9, 2004 11:22:41 AM org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on port 8080

時間,類名,方法名,信息都打印了出來。
那麼,log是如何獲取調用自身的這個類和這個方法名的呢?

後面給出的代碼是JDK1.4的源代碼,和Log4J的源代碼。說明其實現原理。
得到調用類,和方法名,就是須要得到當前運行棧的結構。
new Throwable().getStackTrace() 會返回當前運行棧的結構層次。
利用這種原理,能夠得到整個運行棧的調用關係。

JDK1.4的java.util.logging包, 經過Throwable.getStackTrace()方法實現的。
// Get the stack trace.
StackTraceElement stack[] = (new Throwable()).getStackTrace();

完整的代碼在JDK1.4的源代碼裏面,java.util.logging.LogRecord類的inferCaller方法。
java

// Private method to infer the caller's class and method names
private void inferCaller() {
needToInferCaller = false;
// Get the stack trace.
StackTraceElement stack[] = (new Throwable()).getStackTrace();
// First, search back to a method in the Logger class.
int ix = 0;
while (ix < stack.length) {
StackTraceElement frame = stack[ix];
String cname = frame.getClassName();
if (cname.equals("java.util.logging.Logger")) {
break;
}
ix++;
}
// Now search for the first frame before the "Logger" class.
while (ix < stack.length) {
StackTraceElement frame = stack[ix];
String cname = frame.getClassName();
if (!cname.equals("java.util.logging.Logger")) {
// We've found the relevant frame.
setSourceClassName(cname);
setSourceMethodName(frame.getMethodName());
return;
}
ix++;
}
// We haven't found a suitable frame, so just punt. This is
// OK as we are only commited to making a "best effort" here.
}



Log4j有殊途同歸之妙。
org.apache.log4j.spi.LocationInfo類。
先用Throwable.printStackTrace()方法把Exception信息打印到一個字符串裏。
而後按行分析這個字符串。抽出調用類和方法。參見LocationInfo類的構造函數。

web

/**
Instantiate location information based on a Throwable. We
expect the Throwable <code>t</code>, to be in the format

<pre>
java.lang.Throwable
...
at org.apache.log4j.PatternLayout.format(PatternLayout.java:413)
at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183)
at org.apache.log4j.Category.callAppenders(Category.java:131)
at org.apache.log4j.Category.log(Category.java:512)
at callers.fully.qualified.className.methodName(FileName.java:74)
...
</pre>

<p>However, we can also deal with JIT compilers that "lose" the
location information, especially between the parentheses.

*/
public LocationInfo(Throwable t, String fqnOfCallingClass)



e.printStackTrace()把Exception發生當時的整個運行棧結構展開,打印出來。
Log4J就是分析這個打印結果,得到全部的調用層次。

關於直接獲取調用類名的方法。
咱們來看sun.reflect.Reflection的getCallerClass()方法的說明。

apache

/** Returns the class of the method <code>realFramesToSkip</code>
frames up the stack (zero-based), ignoring frames associated
with java.lang.reflect.Method.invoke() and its implementation.
The first frame is that associated with this method, so
<code>getCallerClass(0)</code> returns the Class object for
sun.reflect.Reflection. Frames associated with
java.lang.reflect.Method.invoke() and its implementation are
completely ignored and do not count toward the number of "real"
frames skipped. */
public static native Class getCallerClass(int realFramesToSkip);



這是一個native方法。原理也是根據StackFrame(運行棧)獲取相應類的信息。這個方法直接返回一個Class名字,直接有效。參數realFramesToSkip用來選取你所須要的Stack層次,因此,你能夠用這個方法得到任何層次的上的調用類名。

Throwable.getStackTrace()也是一個native方法。原理也是根據StackFrame(運行棧)獲取相應類的信息。返回一個StackTraceElement[]。
StackTraceElement類在JDK1.4的java.lang的包裏面。裏面包含豐富的信息,很是適合Debug。
StackTraceElement類有以下方法:
getFileName(),getLineNumber(), getClassName(), getMethodName()。app

相關文章
相關標籤/搜索