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 java.io.StringWriter;
    23  import java.io.PrintWriter;
    24  import org.apache.log4j.helpers.LogLog;
    25  import org.apache.log4j.Layout;
    26
    27  /***
    28     The internal representation of caller location information.
    29
    30     @since 0.8.3
    31  */
    32  public class LocationInfo implements java.io.Serializable {
    33
    34    /***
    35       Caller's line number.
    36    */
    37    transient String lineNumber;
    38    /***
    39       Caller's file name.
    40    */
    41    transient String fileName;
    42    /***
    43       Caller's fully qualified class name.
    44    */
    45    transient String className;
    46    /***
    47       Caller's method name.
    48    */
    49    transient String methodName;
    50    /***
    51       All available caller information, in the format
    52       <code>fully.qualified.classname.of.caller.methodName(Filename.java:line)</code>
    53      */
    54    public String fullInfo;
    55
    56    private static StringWriter sw = new StringWriter();
    57    private static PrintWriter pw = new PrintWriter(sw);
    58
    59    /***
    60       When location information is not available the constant
    61       <code>NA</code> is returned. Current value of this string
    62       constant is <b>?</b>.  */
    63    public final static String NA = "?";
    64
    65    static final long serialVersionUID = -1325822038990805636L;
    66
    67      /***
    68       * NA_LOCATION_INFO is provided for compatibility with log4j 1.3.
    69       * @since 1.2.15
    70       */
    71      public static final LocationInfo NA_LOCATION_INFO =
    72              new LocationInfo(NA, NA, NA, NA);
    73
    74
    75
    76    // Check if we are running in IBM's visual age.
    77    static boolean inVisualAge = false;
    78    static {
    79      try {
    80        inVisualAge = Class.forName("com.ibm.uvm.tools.DebugSupport") != null;
    81        LogLog.debug("Detected IBM VisualAge environment.");
    82      } catch(Throwable e) {
    83        // nothing to do
    84      }
    85    }
    86
    87    /***
    88       Instantiate location information based on a Throwable. We
    89       expect the Throwable <code>t</code>, to be in the format
    90
    91         <pre>
    92          java.lang.Throwable
    93          ...
    94            at org.apache.log4j.PatternLayout.format(PatternLayout.java:413)
    95            at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183)
    96          at org.apache.log4j.Category.callAppenders(Category.java:131)
    97          at org.apache.log4j.Category.log(Category.java:512)
    98          at callers.fully.qualified.className.methodName(FileName.java:74)
    99  	...
    100        </pre>
    101
    102        <p>However, we can also deal with JIT compilers that "lose" the
    103        location information, especially between the parentheses.
    104
    105     */
    106     public LocationInfo(Throwable t, String fqnOfCallingClass) {
    107       if(t == null || fqnOfCallingClass == null)
    108 	return;
    109
    110       String s;
    111       // Protect against multiple access to sw.
    112       synchronized(sw) {
    113 	t.printStackTrace(pw);
    114 	s = sw.toString();
    115 	sw.getBuffer().setLength(0);
    116       }
    117       //System.out.println("s is ["+s+"].");
    118       int ibegin, iend;
    119
    120       // Given the current structure of the package, the line
    121       // containing "org.apache.log4j.Category." should be printed just
    122       // before the caller.
    123
    124       // This method of searching may not be fastest but it's safer
    125       // than counting the stack depth which is not guaranteed to be
    126       // constant across JVM implementations.
    127       ibegin = s.lastIndexOf(fqnOfCallingClass);
    128       if(ibegin == -1)
    129 	return;
    130
    131
    132       ibegin = s.indexOf(Layout.LINE_SEP, ibegin);
    133       if(ibegin == -1)
    134 	return;
    135       ibegin+= Layout.LINE_SEP_LEN;
    136
    137       // determine end of line
    138       iend = s.indexOf(Layout.LINE_SEP, ibegin);
    139       if(iend == -1)
    140 	return;
    141
    142       // VA has a different stack trace format which doesn't
    143       // need to skip the inital 'at'
    144       if(!inVisualAge) {
    145 	// back up to first blank character
    146 	ibegin = s.lastIndexOf("at ", iend);
    147 	if(ibegin == -1)
    148 	  return;
    149 	// Add 3 to skip "at ";
    150 	ibegin += 3;
    151       }
    152       // everything between is the requested stack item
    153       this.fullInfo = s.substring(ibegin, iend);
    154     }
    155
    156     /***
    157      *   Appends a location fragment to a buffer to build the
    158      *     full location info.
    159      *    @param buf StringBuffer to receive content.
    160      *    @param fragment fragment of location (class, method, file, line),
    161      *        if null the value of NA will be appended.
    162      *    @since 1.2.15
    163      */
    164     private static final void appendFragment(final StringBuffer buf,
    165                                              final String fragment) {
    166           if (fragment == null) {
    167              buf.append(NA);
    168           } else {
    169              buf.append(fragment);
    170           }
    171     }
    172
    173     /***
    174      * Create new instance.
    175      * @param file source file name
    176      * @param classname class name
    177      * @param method method
    178      * @param line source line number
    179      *
    180      * @since 1.2.15
    181      */
    182     public LocationInfo(
    183       final String file,
    184       final String classname,
    185       final String method,
    186       final String line) {
    187       this.fileName = file;
    188       this.className = classname;
    189       this.methodName = method;
    190       this.lineNumber = line;
    191       StringBuffer buf = new StringBuffer();
    192 	  appendFragment(buf, classname);
    193 	  buf.append(".");
    194 	  appendFragment(buf, method);
    195 	  buf.append("(");
    196 	  appendFragment(buf, file);
    197 	  buf.append(":");
    198 	  appendFragment(buf, line);
    199 	  buf.append(")");
    200 	  this.fullInfo = buf.toString();
    201     }
    202
    203     /***
    204        Return the fully qualified class name of the caller making the
    205        logging request.
    206     */
    207     public
    208     String getClassName() {
    209       if(fullInfo == null) return NA;
    210       if(className == null) {
    211 	// Starting the search from '(' is safer because there is
    212 	// potentially a dot between the parentheses.
    213 	int iend = fullInfo.lastIndexOf('(');
    214 	if(iend == -1)
    215 	  className = NA;
    216 	else {
    217 	  iend =fullInfo.lastIndexOf('.', iend);
    218
    219 	  // This is because a stack trace in VisualAge looks like:
    220
    221           //java.lang.RuntimeException
    222 	  //  java.lang.Throwable()
    223 	  //  java.lang.Exception()
    224 	  //  java.lang.RuntimeException()
    225 	  //  void test.test.B.print()
    226 	  //  void test.test.A.printIndirect()
    227 	  //  void test.test.Run.main(java.lang.String [])
    228           int ibegin = 0;
    229 	  if (inVisualAge) {
    230 	    ibegin = fullInfo.lastIndexOf(' ', iend)+1;
    231           }
    232
    233 	  if(iend == -1)
    234 	    className = NA;
    235 	  else
    236 	    className = this.fullInfo.substring(ibegin, iend);
    237 	}
    238       }
    239       return className;
    240     }
    241
    242     /***
    243        Return the file name of the caller.
    244
    245        <p>This information is not always available.
    246     */
    247     public
    248     String getFileName() {
    249       if(fullInfo == null) return NA;
    250
    251       if(fileName == null) {
    252 	int iend = fullInfo.lastIndexOf(':');
    253 	if(iend == -1)
    254 	  fileName = NA;
    255 	else {
    256 	  int ibegin = fullInfo.lastIndexOf('(', iend - 1);
    257 	  fileName = this.fullInfo.substring(ibegin + 1, iend);
    258 	}
    259       }
    260       return fileName;
    261     }
    262
    263     /***
    264        Returns the line number of the caller.
    265
    266        <p>This information is not always available.
    267     */
    268     public
    269     String getLineNumber() {
    270       if(fullInfo == null) return NA;
    271
    272       if(lineNumber == null) {
    273 	int iend = fullInfo.lastIndexOf(')');
    274 	int ibegin = fullInfo.lastIndexOf(':', iend -1);
    275 	if(ibegin == -1)
    276 	  lineNumber = NA;
    277 	else
    278 	  lineNumber = this.fullInfo.substring(ibegin + 1, iend);
    279       }
    280       return lineNumber;
    281     }
    282
    283     /***
    284        Returns the method name of the caller.
    285     */
    286     public
    287     String getMethodName() {
    288       if(fullInfo == null) return NA;
    289       if(methodName == null) {
    290 	int iend = fullInfo.lastIndexOf('(');
    291 	int ibegin = fullInfo.lastIndexOf('.', iend);
    292 	if(ibegin == -1)
    293 	  methodName = NA;
    294 	else
    295 	  methodName = this.fullInfo.substring(ibegin + 1, iend);
    296       }
    297       return methodName;
    298     }
    299 }