2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 // Contributors: Mathias Rupprecht <mmathias.rupprecht@fja.com>
20 package org.apache.log4j.spi;
22 import org.apache.log4j.Layout;
23 import org.apache.log4j.helpers.LogLog;
25 import java.io.PrintWriter;
26 import java.io.StringWriter;
27 import java.io.InterruptedIOException;
28 import java.lang.reflect.Method;
29 import java.lang.reflect.InvocationTargetException;
32 The internal representation of caller location information.
36 public class LocationInfo implements java.io.Serializable {
41 transient String lineNumber;
45 transient String fileName;
47 Caller's fully qualified class name.
49 transient String className;
53 transient String methodName;
55 All available caller information, in the format
56 <code>fully.qualified.classname.of.caller.methodName(Filename.java:line)</code>
58 public String fullInfo;
60 private static StringWriter sw = new StringWriter();
61 private static PrintWriter pw = new PrintWriter(sw);
63 private static Method getStackTraceMethod;
64 private static Method getClassNameMethod;
65 private static Method getMethodNameMethod;
66 private static Method getFileNameMethod;
67 private static Method getLineNumberMethod;
71 When location information is not available the constant
72 <code>NA</code> is returned. Current value of this string
73 constant is <b>?</b>. */
74 public final static String NA = "?";
76 static final long serialVersionUID = -1325822038990805636L;
79 * NA_LOCATION_INFO is provided for compatibility with log4j 1.3.
82 public static final LocationInfo NA_LOCATION_INFO =
83 new LocationInfo(NA, NA, NA, NA);
87 // Check if we are running in IBM's visual age.
88 static boolean inVisualAge = false;
91 inVisualAge = Class.forName("com.ibm.uvm.tools.DebugSupport") != null;
92 LogLog.debug("Detected IBM VisualAge environment.");
93 } catch(Throwable e) {
97 Class[] noArgs = null;
98 getStackTraceMethod = Throwable.class.getMethod("getStackTrace", noArgs);
99 Class stackTraceElementClass = Class.forName("java.lang.StackTraceElement");
100 getClassNameMethod = stackTraceElementClass.getMethod("getClassName", noArgs);
101 getMethodNameMethod = stackTraceElementClass.getMethod("getMethodName", noArgs);
102 getFileNameMethod = stackTraceElementClass.getMethod("getFileName", noArgs);
103 getLineNumberMethod = stackTraceElementClass.getMethod("getLineNumber", noArgs);
104 } catch(ClassNotFoundException ex) {
105 LogLog.debug("LocationInfo will use pre-JDK 1.4 methods to determine location.");
106 } catch(NoSuchMethodException ex) {
107 LogLog.debug("LocationInfo will use pre-JDK 1.4 methods to determine location.");
112 Instantiate location information based on a Throwable. We
113 expect the Throwable <code>t</code>, to be in the format
118 at org.apache.log4j.PatternLayout.format(PatternLayout.java:413)
119 at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183)
120 at org.apache.log4j.Category.callAppenders(Category.java:131)
121 at org.apache.log4j.Category.log(Category.java:512)
122 at callers.fully.qualified.className.methodName(FileName.java:74)
126 <p>However, we can also deal with JIT compilers that "lose" the
127 location information, especially between the parentheses.
128 @param t throwable used to determine location, may be null.
129 @param fqnOfCallingClass class name of first class considered part of
130 the logging framework. Location will be site that calls a method on this class.
133 public LocationInfo(Throwable t, String fqnOfCallingClass) {
134 if(t == null || fqnOfCallingClass == null) {
137 if (getLineNumberMethod != null) {
139 Object[] noArgs = null;
140 Object[] elements = (Object[]) getStackTraceMethod.invoke(t, noArgs);
141 String prevClass = NA;
142 for(int i = elements.length - 1; i >= 0; i--) {
143 String thisClass = (String) getClassNameMethod.invoke(elements[i], noArgs);
144 if(fqnOfCallingClass.equals(thisClass)) {
146 if (caller < elements.length) {
147 className = prevClass;
148 methodName = (String) getMethodNameMethod.invoke(elements[caller], noArgs);
149 fileName = (String) getFileNameMethod.invoke(elements[caller], noArgs);
150 if (fileName == null) {
153 int line = ((Integer) getLineNumberMethod.invoke(elements[caller], noArgs)).intValue();
157 lineNumber = String.valueOf(line);
159 StringBuffer buf = new StringBuffer();
160 buf.append(className);
162 buf.append(methodName);
164 buf.append(fileName);
166 buf.append(lineNumber);
168 this.fullInfo = buf.toString();
172 prevClass = thisClass;
175 } catch(IllegalAccessException ex) {
176 LogLog.debug("LocationInfo failed using JDK 1.4 methods", ex);
177 } catch(InvocationTargetException ex) {
178 if (ex.getTargetException() instanceof InterruptedException
179 || ex.getTargetException() instanceof InterruptedIOException) {
180 Thread.currentThread().interrupt();
182 LogLog.debug("LocationInfo failed using JDK 1.4 methods", ex);
183 } catch(RuntimeException ex) {
184 LogLog.debug("LocationInfo failed using JDK 1.4 methods", ex);
189 // Protect against multiple access to sw.
191 t.printStackTrace(pw);
193 sw.getBuffer().setLength(0);
195 //System.out.println("s is ["+s+"].");
198 // Given the current structure of the package, the line
199 // containing "org.apache.log4j.Category." should be printed just
200 // before the caller.
202 // This method of searching may not be fastest but it's safer
203 // than counting the stack depth which is not guaranteed to be
204 // constant across JVM implementations.
205 ibegin = s.lastIndexOf(fqnOfCallingClass);
211 // if the next character after the class name exists
212 // but is not a period, see if the classname is
213 // followed by a period earlier in the trace.
214 // Minimizes mistakeningly matching on a class whose
215 // name is a substring of the desired class.
217 if (ibegin + fqnOfCallingClass.length() < s.length() &&
218 s.charAt(ibegin + fqnOfCallingClass.length()) != '.') {
219 int i = s.lastIndexOf(fqnOfCallingClass + ".");
226 ibegin = s.indexOf(Layout.LINE_SEP, ibegin);
230 ibegin+= Layout.LINE_SEP_LEN;
232 // determine end of line
233 iend = s.indexOf(Layout.LINE_SEP, ibegin);
238 // VA has a different stack trace format which doesn't
239 // need to skip the inital 'at'
241 // back up to first blank character
242 ibegin = s.lastIndexOf("at ", iend);
246 // Add 3 to skip "at ";
249 // everything between is the requested stack item
250 this.fullInfo = s.substring(ibegin, iend);
254 * Appends a location fragment to a buffer to build the
255 * full location info.
256 * @param buf StringBuffer to receive content.
257 * @param fragment fragment of location (class, method, file, line),
258 * if null the value of NA will be appended.
261 private static final void appendFragment(final StringBuffer buf,
262 final String fragment) {
263 if (fragment == null) {
266 buf.append(fragment);
271 * Create new instance.
272 * @param file source file name
273 * @param classname class name
274 * @param method method
275 * @param line source line number
281 final String classname,
284 this.fileName = file;
285 this.className = classname;
286 this.methodName = method;
287 this.lineNumber = line;
288 StringBuffer buf = new StringBuffer();
289 appendFragment(buf, classname);
291 appendFragment(buf, method);
293 appendFragment(buf, file);
295 appendFragment(buf, line);
297 this.fullInfo = buf.toString();
301 Return the fully qualified class name of the caller making the
305 String getClassName() {
306 if(fullInfo == null) {
309 if(className == null) {
310 // Starting the search from '(' is safer because there is
311 // potentially a dot between the parentheses.
312 int iend = fullInfo.lastIndexOf('(');
316 iend =fullInfo.lastIndexOf('.', iend);
318 // This is because a stack trace in VisualAge looks like:
320 //java.lang.RuntimeException
321 // java.lang.Throwable()
322 // java.lang.Exception()
323 // java.lang.RuntimeException()
324 // void test.test.B.print()
325 // void test.test.A.printIndirect()
326 // void test.test.Run.main(java.lang.String [])
329 ibegin = fullInfo.lastIndexOf(' ', iend)+1;
335 className = this.fullInfo.substring(ibegin, iend);
343 Return the file name of the caller.
345 <p>This information is not always available.
348 String getFileName() {
349 if(fullInfo == null) {
353 if(fileName == null) {
354 int iend = fullInfo.lastIndexOf(':');
358 int ibegin = fullInfo.lastIndexOf('(', iend - 1);
359 fileName = this.fullInfo.substring(ibegin + 1, iend);
366 Returns the line number of the caller.
368 <p>This information is not always available.
371 String getLineNumber() {
372 if(fullInfo == null) {
376 if(lineNumber == null) {
377 int iend = fullInfo.lastIndexOf(')');
378 int ibegin = fullInfo.lastIndexOf(':', iend -1);
382 lineNumber = this.fullInfo.substring(ibegin + 1, iend);
389 Returns the method name of the caller.
392 String getMethodName() {
393 if(fullInfo == null) {
396 if(methodName == null) {
397 int iend = fullInfo.lastIndexOf('(');
398 int ibegin = fullInfo.lastIndexOf('.', iend);
402 methodName = this.fullInfo.substring(ibegin + 1, iend);