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 }