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 }