关键字: 企业应用
恰好看到关于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方法。
代码
1. // Private method to infer the caller's class and method names
2. private void inferCaller() {
3. needToInferCaller = false;
4. // Get the stack trace.
5. StackTraceElement stack[] = (new Throwable()).getStackTrace();
6. // First, search back to a method in the Logger class.
7. int ix = 0;
8. while (ix < stack.length) {
9. StackTraceElement frame = stack[ix];
10. String cname = frame.getClassName();
11. if (cname.equals("java.util.logging.Logger")) {
12. break;
13. }
14. ix++;
15. }
16. // Now search for the first frame before the "Logger" class.
17. while (ix < stack.length) {
18. StackTraceElement frame = stack[ix];
19. String cname = frame.getClassName();
20. if (!cname.equals("java.util.logging.Logger")) {
21. // We've found the relevant frame.
22. setSourceClassName(cname);
23. setSourceMethodName(frame.getMethodName());
24. return;
25. }
26. ix++;
27. }
28. // We haven't found a suitable frame, so just punt. This is
29. // OK as we are only commited to making a "best effort" here.
30. }
Log4j有异曲同工之妙。
org.apache.log4j.spi.LocationInfo类。
先用Throwable.printStackTrace()方法把Exception信息打印到一个字符串里。
然后按行分析这个字符串。抽出调用类和方法。参见LocationInfo类的构造函数。
代码
1. /**
2. Instantiate location information based on a Throwable. We
3. expect the Throwable <code>t</code>, to be in the format
4.
5. <pre>
6. java.lang.Throwable
7. ...
8. at org.apache.log4j.PatternLayout.format(PatternLayout.java:413)
9. at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183)
10. at org.apache.log4j.Category.callAppenders(Category.java:131)
11. at org.apache.log4j.Category.log(Category.java:512)
12. at callers.fully.qualified.className.methodName(FileName.java:74)
13. ...
14. </pre>
15.
16. <p>However, we can also deal with JIT compilers that "lose" the
17. location information, especially between the parentheses.
18.
19. */
20. public LocationInfo(Throwable t, String fqnOfCallingClass)
e.printStackTrace()把Exception发生当时的整个运行栈结构展开,打印出来。
Log4J就是分析这个打印结果,获得所有的调用层次。
关于直接获取调用类名的方法。
我们来看sun.reflect.Reflection的getCallerClass()方法的说明。
代码
1. /** Returns the class of the method <code>realFramesToSkip</code>
2. frames up the stack (zero-based), ignoring frames associated
3. with java.lang.reflect.Method.invoke() and its implementation.
4. The first frame is that associated with this method, so
5. <code>getCallerClass(0)</code> returns the Class object for
6. sun.reflect.Reflection. Frames associated with
7. java.lang.reflect.Method.invoke() and its implementation are
8. completely ignored and do not count toward the number of "real"
9. frames skipped. */
10. 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()。
18:36 | 永久链接 | 浏览 (3445) | 评论 (3) | my blog | 进入论坛 |
永久链接
http://buaawhl.javaeye.com/blog/6450
评论 共 3 条
agilejava 2004-07-27 10:05
非常好:)
lllyq 2004-07-27 10:51
以前作unit test时,我也写过一个类似,主要就是getTrueMethod() 这个方法,
其中还包括jdk1.3的方法
可以直接使用。只要在类初始化时MethodTrace methodTrace = new MethodTrace();
,然后在方法中methodTrace.getTrueMethod()
代码
1. /*
2. * Created on Feb 27, 2003 12:00:04 PM
3. *
4. */
5. package com.bba96.util;
6.
7. import java.io.PrintWriter;
8. import java.io.StringWriter;
9.
10. /**
11. * @author Leon
12. */
13. public class MethodTrace {
14. // for jdk 1.4
15. private StackTraceElement elements[] ;
16.
17. StringWriter sw = new StringWriter();
18. PrintWriter out = new PrintWriter(sw);
19. private String trueMethodName = null;
20. private String methodName = null;
21. private String methodNameExTest = null;
22. private String e = null;
23. private int a[] = new int[2];
24. private int b;
25. private String cutStr = "test";
26. private int cutLength = cutStr.length();
27. private char[] line = null;
28. private StringBuffer buf = new StringBuffer();
29. public MethodTrace() {
30. }
31. private String cutTestStr(String str) {
32. if (str == null) {
33. return null;
34. }
35. String strLowerCase = str.toLowerCase();
36. int i = strLowerCase.indexOf(cutStr);
37. int j;
38. if (i != -1) {
39. line = str.toCharArray();
40. buf.setLength(str.length());
41. buf.append(line, 0, i);
42. i += cutLength;
43. j = i;
44. i = strLowerCase.indexOf(cutStr, i);
45. while (i != -1) {
46. buf.append(line, j, i - j);
47. i += cutLength;
48. j = i;
49. i = strLowerCase.indexOf(cutStr, i);
50. }
51. buf.append(line, j, line.length - j);
52. str = buf.toString();
53. buf.setLength(0);
54. }
55. return str;
56. }
57. public String getTrueMethod() {
58. trueMethodName = null;
59. try {
60. b = a[4];
61. } catch (ArrayIndexOutOfBoundsException aioobe) {
62. // jdk 1.3
63. /*
64. aioobe.printStackTrace(out);
65. out.flush();
66. e = sw.toString();
67. try {
68. out.close();
69. sw.close();
70. } catch (IOException e) {
71. // Do nothing, this should not happen as it is StringWriter.
72. }
73. out = null;
74. sw = null;
75. boolean logError = false;
76. int pos = e.indexOf("at ");
77. for (int i = 0; i < 3; i++) {
78. if (pos == -1) {
79. logError = true;
80. break;
81. }
82. e = e.substring(pos + 3, e.length());
83. pos = e.indexOf("at ");
84. }
85. if (logError) {
86. trueMethodName = "UnknownClass.unknownMethod()";
87. } else {
88. trueMethodName = e.substring(0, e.indexOf('('));
89. }
90. */
91.
92. // for jdk 1.4
93.
94. elements = aioobe.getStackTrace();
95. if (elements.length < 3) {
96. return "UnknownClass.unknownMethod()";
97. }
98. trueMethodName = elements[2].getClassName() + "." + elements[2].getMethodName();
99.
100. }
101. return trueMethodName;
102. }
103. public String getMethod() {
104. return methodName;
105. }
106. public String getMethodExTest() {
107. methodNameExTest = cutTestStr(getTrueMethod());
108. int index = methodNameExTest.lastIndexOf('.');
109. methodNameExTest =
110. methodNameExTest.substring(0, index + 1)
111. + methodNameExTest.substring(index + 1, index + 2).toLowerCase()
112. + methodNameExTest.substring(index + 2, methodNameExTest.length());
113. return methodNameExTest;
114. }
115. }
buaawhl 2004-07-27 11:48
lllyq 写道
以前作unit test时,我也写过一个类似,主要就是getTrueMethod() 这个方法,其中还包括jdk1.3的方法
可以直接使用。只要在类初始化时MethodTrace methodTrace = new MethodTrace();,
然后在方法中methodTrace.getTrueMethod()
非常棒的代码。
for jdk1.3 的部分,和log4j的思路一样。在这方面,你和log4j大师有一比。
for jdk1.4 的部分也和JDK 1.4 Log的实现方法一样。
我以前怎么也不明白log4j是如何实现的。只有看了源代码才明白。
JDK1.4后来为了支持log才加入了StackTraceElement。
JDK1.3的时候,确实只能用分析Throwable StackTrace字符串这个方法实现。