View Javadoc

1   /*
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
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
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.
16   */
17  
18  // Contributors: Mathias Rupprecht <mmathias.rupprecht@fja.com>
19  
20  package org.apache.log4j.spi;
21  
22  import org.apache.log4j.Layout;
23  import org.apache.log4j.helpers.LogLog;
24  
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;
30  
31  /**
32     The internal representation of caller location information.
33  
34     @since 0.8.3
35  */
36  public class LocationInfo implements java.io.Serializable {
37  
38    /**
39       Caller's line number.
40    */
41    transient String lineNumber;
42    /**
43       Caller's file name.
44    */
45    transient String fileName;
46    /**
47       Caller's fully qualified class name.
48    */
49    transient String className;
50    /**
51       Caller's method name.
52    */
53    transient String methodName;
54    /**
55       All available caller information, in the format
56       <code>fully.qualified.classname.of.caller.methodName(Filename.java:line)</code>
57      */
58    public String fullInfo;
59  
60    private static StringWriter sw = new StringWriter();
61    private static PrintWriter pw = new PrintWriter(sw);
62  
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;
68  
69  
70    /**
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 = "?";
75  
76    static final long serialVersionUID = -1325822038990805636L;
77  
78      /**
79       * NA_LOCATION_INFO is provided for compatibility with log4j 1.3.
80       * @since 1.2.15
81       */
82      public static final LocationInfo NA_LOCATION_INFO =
83              new LocationInfo(NA, NA, NA, NA);
84  
85  
86  
87    // Check if we are running in IBM's visual age.
88    static boolean inVisualAge = false;
89    static {
90      try {
91        inVisualAge = Class.forName("com.ibm.uvm.tools.DebugSupport") != null;
92        LogLog.debug("Detected IBM VisualAge environment.");
93      } catch(Throwable e) {
94        // nothing to do
95      }
96        try {
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.");
108       }
109   }
110 
111   /**
112      Instantiate location information based on a Throwable. We
113      expect the Throwable <code>t</code>, to be in the format
114 
115        <pre>
116         java.lang.Throwable
117         ...
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)
123 	...
124        </pre>
125 
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.
131 
132     */
133     public LocationInfo(Throwable t, String fqnOfCallingClass) {
134       if(t == null || fqnOfCallingClass == null)
135 	return;
136       if (getLineNumberMethod != null) {
137           try {
138               Object[] noArgs = null;
139               Object[] elements =  (Object[]) getStackTraceMethod.invoke(t, noArgs);
140               String prevClass = NA;
141               for(int i = elements.length - 1; i >= 0; i--) {
142                   String thisClass = (String) getClassNameMethod.invoke(elements[i], noArgs);
143                   if(fqnOfCallingClass.equals(thisClass)) {
144                       int caller = i + 1;
145                       if (caller < elements.length) {
146                           className = prevClass;
147                           methodName = (String) getMethodNameMethod.invoke(elements[caller], noArgs);
148                           fileName = (String) getFileNameMethod.invoke(elements[caller], noArgs);
149                           if (fileName == null) {
150                               fileName = NA;
151                           }
152                           int line = ((Integer) getLineNumberMethod.invoke(elements[caller], noArgs)).intValue();
153                           if (line < 0) {
154                               lineNumber = NA;
155                           } else {
156                               lineNumber = String.valueOf(line);
157                           }
158                           StringBuffer buf = new StringBuffer();
159                           buf.append(className);
160                           buf.append(".");
161                           buf.append(methodName);
162                           buf.append("(");
163                           buf.append(fileName);
164                           buf.append(":");
165                           buf.append(lineNumber);
166                           buf.append(")");
167                           this.fullInfo = buf.toString();
168                       }
169                       return;
170                   }
171                   prevClass = thisClass;
172               }
173               return;
174           } catch(IllegalAccessException ex) {
175               LogLog.debug("LocationInfo failed using JDK 1.4 methods", ex);
176           } catch(InvocationTargetException ex) {
177               if (ex.getTargetException() instanceof InterruptedException
178                       || ex.getTargetException() instanceof InterruptedIOException) {
179                   Thread.currentThread().interrupt();
180               }
181               LogLog.debug("LocationInfo failed using JDK 1.4 methods", ex);
182           } catch(RuntimeException ex) {
183               LogLog.debug("LocationInfo failed using JDK 1.4 methods", ex);
184           }
185       }
186 
187       String s;
188       // Protect against multiple access to sw.
189       synchronized(sw) {
190 	t.printStackTrace(pw);
191 	s = sw.toString();
192 	sw.getBuffer().setLength(0);
193       }
194       //System.out.println("s is ["+s+"].");
195       int ibegin, iend;
196 
197       // Given the current structure of the package, the line
198       // containing "org.apache.log4j.Category." should be printed just
199       // before the caller.
200 
201       // This method of searching may not be fastest but it's safer
202       // than counting the stack depth which is not guaranteed to be
203       // constant across JVM implementations.
204       ibegin = s.lastIndexOf(fqnOfCallingClass);
205       if(ibegin == -1)
206 	return;
207 
208       //
209       //   if the next character after the class name exists
210       //       but is not a period, see if the classname is
211       //       followed by a period earlier in the trace.
212       //       Minimizes mistakeningly matching on a class whose
213       //       name is a substring of the desired class.
214       //       See bug 44888.
215       if (ibegin + fqnOfCallingClass.length() < s.length() &&
216               s.charAt(ibegin + fqnOfCallingClass.length()) != '.') {
217           int i = s.lastIndexOf(fqnOfCallingClass + ".");
218           if (i != -1) {
219               ibegin = i;
220           }
221       }
222 
223 
224       ibegin = s.indexOf(Layout.LINE_SEP, ibegin);
225       if(ibegin == -1)
226 	return;
227       ibegin+= Layout.LINE_SEP_LEN;
228 
229       // determine end of line
230       iend = s.indexOf(Layout.LINE_SEP, ibegin);
231       if(iend == -1)
232 	return;
233 
234       // VA has a different stack trace format which doesn't
235       // need to skip the inital 'at'
236       if(!inVisualAge) {
237 	// back up to first blank character
238 	ibegin = s.lastIndexOf("at ", iend);
239 	if(ibegin == -1)
240 	  return;
241 	// Add 3 to skip "at ";
242 	ibegin += 3;
243       }
244       // everything between is the requested stack item
245       this.fullInfo = s.substring(ibegin, iend);
246     }
247 
248     /**
249      *   Appends a location fragment to a buffer to build the 
250      *     full location info.
251      *    @param buf StringBuffer to receive content.
252      *    @param fragment fragment of location (class, method, file, line),
253      *        if null the value of NA will be appended.
254      *    @since 1.2.15
255      */
256     private static final void appendFragment(final StringBuffer buf,
257                                              final String fragment) {
258           if (fragment == null) {
259              buf.append(NA);
260           } else {
261              buf.append(fragment);
262           }
263     }
264 
265     /**
266      * Create new instance.
267      * @param file source file name
268      * @param classname class name
269      * @param method method
270      * @param line source line number
271      *
272      * @since 1.2.15
273      */
274     public LocationInfo(
275       final String file,
276       final String classname,
277       final String method,
278       final String line) {
279       this.fileName = file;
280       this.className = classname;
281       this.methodName = method;
282       this.lineNumber = line;
283       StringBuffer buf = new StringBuffer();
284 	  appendFragment(buf, classname);
285 	  buf.append(".");
286 	  appendFragment(buf, method);
287 	  buf.append("(");
288 	  appendFragment(buf, file);
289 	  buf.append(":");
290 	  appendFragment(buf, line);
291 	  buf.append(")");
292 	  this.fullInfo = buf.toString();
293     }
294 
295     /**
296        Return the fully qualified class name of the caller making the
297        logging request.
298     */
299     public
300     String getClassName() {
301       if(fullInfo == null) return NA;
302       if(className == null) {
303 	// Starting the search from '(' is safer because there is
304 	// potentially a dot between the parentheses.
305 	int iend = fullInfo.lastIndexOf('(');
306 	if(iend == -1)
307 	  className = NA;
308 	else {
309 	  iend =fullInfo.lastIndexOf('.', iend);
310 
311 	  // This is because a stack trace in VisualAge looks like:
312 
313           //java.lang.RuntimeException
314 	  //  java.lang.Throwable()
315 	  //  java.lang.Exception()
316 	  //  java.lang.RuntimeException()
317 	  //  void test.test.B.print()
318 	  //  void test.test.A.printIndirect()
319 	  //  void test.test.Run.main(java.lang.String [])
320           int ibegin = 0;
321 	  if (inVisualAge) {
322 	    ibegin = fullInfo.lastIndexOf(' ', iend)+1;
323           }
324 
325 	  if(iend == -1)
326 	    className = NA;
327 	  else
328 	    className = this.fullInfo.substring(ibegin, iend);
329 	}
330       }
331       return className;
332     }
333 
334     /**
335        Return the file name of the caller.
336 
337        <p>This information is not always available.
338     */
339     public
340     String getFileName() {
341       if(fullInfo == null) return NA;
342 
343       if(fileName == null) {
344 	int iend = fullInfo.lastIndexOf(':');
345 	if(iend == -1)
346 	  fileName = NA;
347 	else {
348 	  int ibegin = fullInfo.lastIndexOf('(', iend - 1);
349 	  fileName = this.fullInfo.substring(ibegin + 1, iend);
350 	}
351       }
352       return fileName;
353     }
354 
355     /**
356        Returns the line number of the caller.
357 
358        <p>This information is not always available.
359     */
360     public
361     String getLineNumber() {
362       if(fullInfo == null) return NA;
363 
364       if(lineNumber == null) {
365 	int iend = fullInfo.lastIndexOf(')');
366 	int ibegin = fullInfo.lastIndexOf(':', iend -1);
367 	if(ibegin == -1)
368 	  lineNumber = NA;
369 	else
370 	  lineNumber = this.fullInfo.substring(ibegin + 1, iend);
371       }
372       return lineNumber;
373     }
374 
375     /**
376        Returns the method name of the caller.
377     */
378     public
379     String getMethodName() {
380       if(fullInfo == null) return NA;
381       if(methodName == null) {
382 	int iend = fullInfo.lastIndexOf('(');
383 	int ibegin = fullInfo.lastIndexOf('.', iend);
384 	if(ibegin == -1)
385 	  methodName = NA;
386 	else
387 	  methodName = this.fullInfo.substring(ibegin + 1, iend);
388       }
389       return methodName;
390     }
391 }