001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018// Contributors: Mathias Rupprecht <mmathias.rupprecht@fja.com> 019 020package org.apache.log4j.spi; 021 022import org.apache.log4j.Layout; 023import org.apache.log4j.helpers.LogLog; 024 025import java.io.PrintWriter; 026import java.io.StringWriter; 027import java.io.InterruptedIOException; 028import java.lang.reflect.Method; 029import java.lang.reflect.InvocationTargetException; 030 031/** 032 The internal representation of caller location information. 033 034 @since 0.8.3 035*/ 036public class LocationInfo implements java.io.Serializable { 037 038 /** 039 Caller's line number. 040 */ 041 transient String lineNumber; 042 /** 043 Caller's file name. 044 */ 045 transient String fileName; 046 /** 047 Caller's fully qualified class name. 048 */ 049 transient String className; 050 /** 051 Caller's method name. 052 */ 053 transient String methodName; 054 /** 055 All available caller information, in the format 056 <code>fully.qualified.classname.of.caller.methodName(Filename.java:line)</code> 057 */ 058 public String fullInfo; 059 060 private static StringWriter sw = new StringWriter(); 061 private static PrintWriter pw = new PrintWriter(sw); 062 063 private static Method getStackTraceMethod; 064 private static Method getClassNameMethod; 065 private static Method getMethodNameMethod; 066 private static Method getFileNameMethod; 067 private static Method getLineNumberMethod; 068 069 070 /** 071 When location information is not available the constant 072 <code>NA</code> is returned. Current value of this string 073 constant is <b>?</b>. */ 074 public final static String NA = "?"; 075 076 static final long serialVersionUID = -1325822038990805636L; 077 078 /** 079 * NA_LOCATION_INFO is provided for compatibility with log4j 1.3. 080 * @since 1.2.15 081 */ 082 public static final LocationInfo NA_LOCATION_INFO = 083 new LocationInfo(NA, NA, NA, NA); 084 085 086 087 // Check if we are running in IBM's visual age. 088 static boolean inVisualAge = false; 089 static { 090 try { 091 inVisualAge = Class.forName("com.ibm.uvm.tools.DebugSupport") != null; 092 LogLog.debug("Detected IBM VisualAge environment."); 093 } catch(Throwable e) { 094 // nothing to do 095 } 096 try { 097 Class[] noArgs = null; 098 getStackTraceMethod = Throwable.class.getMethod("getStackTrace", noArgs); 099 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}