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 018package org.apache.log4j.spi; 019 020import java.io.InterruptedIOException; 021import java.io.ObjectInputStream; 022import java.io.ObjectOutputStream; 023import java.lang.reflect.InvocationTargetException; 024import java.lang.reflect.Method; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.Hashtable; 028import java.util.Map; 029import java.util.Set; 030 031import org.apache.log4j.Category; 032import org.apache.log4j.Level; 033import org.apache.log4j.MDC; 034import org.apache.log4j.NDC; 035import org.apache.log4j.Priority; 036import org.apache.log4j.helpers.Loader; 037import org.apache.log4j.helpers.LogLog; 038 039// Contributors: Nelson Minar <nelson@monkey.org> 040// Wolf Siberski 041// Anders Kristensen <akristensen@dynamicsoft.com> 042 043/** 044 The internal representation of logging events. When an affirmative 045 decision is made to log then a <code>LoggingEvent</code> instance 046 is created. This instance is passed around to the different log4j 047 components. 048 049 <p>This class is of concern to those wishing to extend log4j. 050 051 @author Ceki Gülcü 052 @author James P. Cakalic 053 054 @since 0.8.2 */ 055public class LoggingEvent implements java.io.Serializable { 056 057 private static long startTime = System.currentTimeMillis(); 058 059 /** Fully qualified name of the calling category class. */ 060 transient public final String fqnOfCategoryClass; 061 062 /** 063 * The category of the logging event. This field is not serialized 064 * for performance reasons. 065 * 066 * <p>It is set by the LoggingEvent constructor or set by a remote 067 * entity after deserialization. 068 * 069 * @deprecated This field will be marked as private or be completely 070 * removed in future releases. Please do not use it. 071 * */ 072 transient private Category logger; 073 074 /** 075 * <p>The category (logger) name. 076 * 077 * @deprecated This field will be marked as private in future 078 * releases. Please do not access it directly. Use the {@link 079 * #getLoggerName} method instead. 080 081 * */ 082 final public String categoryName; 083 084 /** 085 * Level of logging event. Level cannot be serializable because it 086 * is a flyweight. Due to its special seralization it cannot be 087 * declared final either. 088 * 089 * <p> This field should not be accessed directly. You shoud use the 090 * {@link #getLevel} method instead. 091 * 092 * @deprecated This field will be marked as private in future 093 * releases. Please do not access it directly. Use the {@link 094 * #getLevel} method instead. 095 * */ 096 transient public Priority level; 097 098 /** The nested diagnostic context (NDC) of logging event. */ 099 private String ndc; 100 101 /** The mapped diagnostic context (MDC) of logging event. */ 102 private Hashtable mdcCopy; 103 104 105 /** Have we tried to do an NDC lookup? If we did, there is no need 106 * to do it again. Note that its value is always false when 107 * serialized. Thus, a receiving SocketNode will never use it's own 108 * (incorrect) NDC. See also writeObject method. */ 109 private boolean ndcLookupRequired = true; 110 111 112 /** Have we tried to do an MDC lookup? If we did, there is no need 113 * to do it again. Note that its value is always false when 114 * serialized. See also the getMDC and getMDCCopy methods. */ 115 private boolean mdcCopyLookupRequired = true; 116 117 /** The application supplied message of logging event. */ 118 transient private Object message; 119 120 /** The application supplied message rendered through the log4j 121 objet rendering mechanism.*/ 122 private String renderedMessage; 123 124 /** The name of thread in which this logging event was generated. */ 125 private String threadName; 126 127 128 /** This 129 variable contains information about this event's throwable 130 */ 131 private ThrowableInformation throwableInfo; 132 133 /** The number of milliseconds elapsed from 1/1/1970 until logging event 134 was created. */ 135 public final long timeStamp; 136 /** Location information for the caller. */ 137 private LocationInfo locationInfo; 138 139 // Serialization 140 static final long serialVersionUID = -868428216207166145L; 141 142 static final Integer[] PARAM_ARRAY = new Integer[1]; 143 static final String TO_LEVEL = "toLevel"; 144 static final Class[] TO_LEVEL_PARAMS = new Class[] {int.class}; 145 static final Hashtable methodCache = new Hashtable(3); // use a tiny table 146 147 /** 148 Instantiate a LoggingEvent from the supplied parameters. 149 150 <p>Except {@link #timeStamp} all the other fields of 151 <code>LoggingEvent</code> are filled when actually needed. 152 <p> 153 @param logger The logger generating this event. 154 @param level The level of this event. 155 @param message The message of this event. 156 @param throwable The throwable of this event. */ 157 public LoggingEvent(String fqnOfCategoryClass, Category logger, 158 Priority level, Object message, Throwable throwable) { 159 this.fqnOfCategoryClass = fqnOfCategoryClass; 160 this.logger = logger; 161 this.categoryName = logger.getName(); 162 this.level = level; 163 this.message = message; 164 if(throwable != null) { 165 this.throwableInfo = new ThrowableInformation(throwable, logger); 166 } 167 timeStamp = System.currentTimeMillis(); 168 } 169 170 /** 171 Instantiate a LoggingEvent from the supplied parameters. 172 173 <p>Except {@link #timeStamp} all the other fields of 174 <code>LoggingEvent</code> are filled when actually needed. 175 <p> 176 @param logger The logger generating this event. 177 @param timeStamp the timestamp of this logging event 178 @param level The level of this event. 179 @param message The message of this event. 180 @param throwable The throwable of this event. */ 181 public LoggingEvent(String fqnOfCategoryClass, Category logger, 182 long timeStamp, Priority level, Object message, 183 Throwable throwable) { 184 this.fqnOfCategoryClass = fqnOfCategoryClass; 185 this.logger = logger; 186 this.categoryName = logger.getName(); 187 this.level = level; 188 this.message = message; 189 if(throwable != null) { 190 this.throwableInfo = new ThrowableInformation(throwable, logger); 191 } 192 193 this.timeStamp = timeStamp; 194 } 195 196 /** 197 Create new instance. 198 @since 1.2.15 199 @param fqnOfCategoryClass Fully qualified class name 200 of Logger implementation. 201 @param logger The logger generating this event. 202 @param timeStamp the timestamp of this logging event 203 @param level The level of this event. 204 @param message The message of this event. 205 @param threadName thread name 206 @param throwable The throwable of this event. 207 @param ndc Nested diagnostic context 208 @param info Location info 209 @param properties MDC properties 210 */ 211 public LoggingEvent(final String fqnOfCategoryClass, 212 final Category logger, 213 final long timeStamp, 214 final Level level, 215 final Object message, 216 final String threadName, 217 final ThrowableInformation throwable, 218 final String ndc, 219 final LocationInfo info, 220 final java.util.Map properties) { 221 super(); 222 this.fqnOfCategoryClass = fqnOfCategoryClass; 223 this.logger = logger; 224 if (logger != null) { 225 categoryName = logger.getName(); 226 } else { 227 categoryName = null; 228 } 229 this.level = level; 230 this.message = message; 231 if(throwable != null) { 232 this.throwableInfo = throwable; 233 } 234 235 this.timeStamp = timeStamp; 236 this.threadName = threadName; 237 ndcLookupRequired = false; 238 this.ndc = ndc; 239 this.locationInfo = info; 240 mdcCopyLookupRequired = false; 241 if (properties != null) { 242 mdcCopy = new java.util.Hashtable(properties); 243 } 244 } 245 246 247 /** 248 Set the location information for this logging event. The collected 249 information is cached for future use. 250 */ 251 public LocationInfo getLocationInformation() { 252 if(locationInfo == null) { 253 locationInfo = new LocationInfo(new Throwable(), fqnOfCategoryClass); 254 } 255 return locationInfo; 256 } 257 258 /** 259 * Return the level of this event. Use this form instead of directly 260 * accessing the <code>level</code> field. */ 261 public Level getLevel() { 262 return (Level) level; 263 } 264 265 /** 266 * Return the name of the logger. Use this form instead of directly 267 * accessing the <code>categoryName</code> field. 268 */ 269 public String getLoggerName() { 270 return categoryName; 271 } 272 273 /** 274 * Gets the logger of the event. 275 * Use should be restricted to cloning events. 276 * @since 1.2.15 277 */ 278 public Category getLogger() { 279 return logger; 280 } 281 282 /** 283 Return the message for this logging event. 284 285 <p>Before serialization, the returned object is the message 286 passed by the user to generate the logging event. After 287 serialization, the returned value equals the String form of the 288 message possibly after object rendering. 289 290 @since 1.1 */ 291 public 292 Object getMessage() { 293 if(message != null) { 294 return message; 295 } else { 296 return getRenderedMessage(); 297 } 298 } 299 300 /** 301 * This method returns the NDC for this event. It will return the 302 * correct content even if the event was generated in a different 303 * thread or even on a different machine. The {@link NDC#get} method 304 * should <em>never</em> be called directly. */ 305 public 306 String getNDC() { 307 if(ndcLookupRequired) { 308 ndcLookupRequired = false; 309 ndc = NDC.get(); 310 } 311 return ndc; 312 } 313 314 315 /** 316 Returns the the context corresponding to the <code>key</code> 317 parameter. If there is a local MDC copy, possibly because we are 318 in a logging server or running inside AsyncAppender, then we 319 search for the key in MDC copy, if a value is found it is 320 returned. Otherwise, if the search in MDC copy returns a null 321 result, then the current thread's <code>MDC</code> is used. 322 323 <p>Note that <em>both</em> the local MDC copy and the current 324 thread's MDC are searched. 325 326 */ 327 public 328 Object getMDC(String key) { 329 Object r; 330 // Note the mdcCopy is used if it exists. Otherwise we use the MDC 331 // that is associated with the thread. 332 if(mdcCopy != null) { 333 r = mdcCopy.get(key); 334 if(r != null) { 335 return r; 336 } 337 } 338 return MDC.get(key); 339 } 340 341 /** 342 Obtain a copy of this thread's MDC prior to serialization or 343 asynchronous logging. 344 */ 345 public 346 void getMDCCopy() { 347 if(mdcCopyLookupRequired) { 348 mdcCopyLookupRequired = false; 349 // the clone call is required for asynchronous logging. 350 // See also bug #5932. 351 Hashtable t = (Hashtable) MDC.getContext(); 352 if(t != null) { 353 mdcCopy = (Hashtable) t.clone(); 354 } 355 } 356 } 357 358 public 359 String getRenderedMessage() { 360 if(renderedMessage == null && message != null) { 361 if(message instanceof String) 362 renderedMessage = (String) message; 363 else { 364 LoggerRepository repository = logger.getLoggerRepository(); 365 366 if(repository instanceof RendererSupport) { 367 RendererSupport rs = (RendererSupport) repository; 368 renderedMessage= rs.getRendererMap().findAndRender(message); 369 } else { 370 renderedMessage = message.toString(); 371 } 372 } 373 } 374 return renderedMessage; 375 } 376 377 /** 378 Returns the time when the application started, in milliseconds 379 elapsed since 01.01.1970. */ 380 public static long getStartTime() { 381 return startTime; 382 } 383 384 public 385 String getThreadName() { 386 if(threadName == null) 387 threadName = (Thread.currentThread()).getName(); 388 return threadName; 389 } 390 391 /** 392 Returns the throwable information contained within this 393 event. May be <code>null</code> if there is no such information. 394 395 <p>Note that the {@link Throwable} object contained within a 396 {@link ThrowableInformation} does not survive serialization. 397 398 @since 1.1 */ 399 public 400 ThrowableInformation getThrowableInformation() { 401 return throwableInfo; 402 } 403 404 /** 405 Return this event's throwable's string[] representaion. 406 */ 407 public 408 String[] getThrowableStrRep() { 409 410 if(throwableInfo == null) 411 return null; 412 else 413 return throwableInfo.getThrowableStrRep(); 414 } 415 416 417 private 418 void readLevel(ObjectInputStream ois) 419 throws java.io.IOException, ClassNotFoundException { 420 421 int p = ois.readInt(); 422 try { 423 String className = (String) ois.readObject(); 424 if(className == null) { 425 level = Level.toLevel(p); 426 } else { 427 Method m = (Method) methodCache.get(className); 428 if(m == null) { 429 Class clazz = Loader.loadClass(className); 430 // Note that we use Class.getDeclaredMethod instead of 431 // Class.getMethod. This assumes that the Level subclass 432 // implements the toLevel(int) method which is a 433 // requirement. Actually, it does not make sense for Level 434 // subclasses NOT to implement this method. Also note that 435 // only Level can be subclassed and not Priority. 436 m = clazz.getDeclaredMethod(TO_LEVEL, TO_LEVEL_PARAMS); 437 methodCache.put(className, m); 438 } 439 level = (Level) m.invoke(null, new Integer[] { new Integer(p) } ); 440 } 441 } catch(InvocationTargetException e) { 442 if (e.getTargetException() instanceof InterruptedException 443 || e.getTargetException() instanceof InterruptedIOException) { 444 Thread.currentThread().interrupt(); 445 } 446 LogLog.warn("Level deserialization failed, reverting to default.", e); 447 level = Level.toLevel(p); 448 } catch(NoSuchMethodException e) { 449 LogLog.warn("Level deserialization failed, reverting to default.", e); 450 level = Level.toLevel(p); 451 } catch(IllegalAccessException e) { 452 LogLog.warn("Level deserialization failed, reverting to default.", e); 453 level = Level.toLevel(p); 454 } catch(RuntimeException e) { 455 LogLog.warn("Level deserialization failed, reverting to default.", e); 456 level = Level.toLevel(p); 457 } 458 } 459 460 private void readObject(ObjectInputStream ois) 461 throws java.io.IOException, ClassNotFoundException { 462 ois.defaultReadObject(); 463 readLevel(ois); 464 465 // Make sure that no location info is available to Layouts 466 if(locationInfo == null) 467 locationInfo = new LocationInfo(null, null); 468 } 469 470 private 471 void writeObject(ObjectOutputStream oos) throws java.io.IOException { 472 // Aside from returning the current thread name the wgetThreadName 473 // method sets the threadName variable. 474 this.getThreadName(); 475 476 // This sets the renders the message in case it wasn't up to now. 477 this.getRenderedMessage(); 478 479 // This call has a side effect of setting this.ndc and 480 // setting ndcLookupRequired to false if not already false. 481 this.getNDC(); 482 483 // This call has a side effect of setting this.mdcCopy and 484 // setting mdcLookupRequired to false if not already false. 485 this.getMDCCopy(); 486 487 // This sets the throwable sting representation of the event throwable. 488 this.getThrowableStrRep(); 489 490 oos.defaultWriteObject(); 491 492 // serialize this event's level 493 writeLevel(oos); 494 } 495 496 private 497 void writeLevel(ObjectOutputStream oos) throws java.io.IOException { 498 499 oos.writeInt(level.toInt()); 500 501 Class clazz = level.getClass(); 502 if(clazz == Level.class) { 503 oos.writeObject(null); 504 } else { 505 // writing directly the Class object would be nicer, except that 506 // serialized a Class object can not be read back by JDK 507 // 1.1.x. We have to resort to this hack instead. 508 oos.writeObject(clazz.getName()); 509 } 510 } 511 512 /** 513 * Set value for MDC property. 514 * This adds the specified MDC property to the event. 515 * Access to the MDC is not synchronized, so this 516 * method should only be called when it is known that 517 * no other threads are accessing the MDC. 518 * @since 1.2.15 519 * @param propName 520 * @param propValue 521 */ 522 public final void setProperty(final String propName, 523 final String propValue) { 524 if (mdcCopy == null) { 525 getMDCCopy(); 526 } 527 if (mdcCopy == null) { 528 mdcCopy = new Hashtable(); 529 } 530 mdcCopy.put(propName, propValue); 531 } 532 533 /** 534 * Return a property for this event. The return value can be null. 535 * 536 * Equivalent to getMDC(String) in log4j 1.2. Provided 537 * for compatibility with log4j 1.3. 538 * 539 * @param key property name 540 * @return property value or null if property not set 541 * @since 1.2.15 542 */ 543 public final String getProperty(final String key) { 544 Object value = getMDC(key); 545 String retval = null; 546 if (value != null) { 547 retval = value.toString(); 548 } 549 return retval; 550 } 551 552 /** 553 * Check for the existence of location information without creating it 554 * (a byproduct of calling getLocationInformation). 555 * @return true if location information has been extracted. 556 * @since 1.2.15 557 */ 558 public final boolean locationInformationExists() { 559 return (locationInfo != null); 560 } 561 562 /** 563 * Getter for the event's time stamp. The time stamp is calculated starting 564 * from 1970-01-01 GMT. 565 * @return timestamp 566 * 567 * @since 1.2.15 568 */ 569 public final long getTimeStamp() { 570 return timeStamp; 571 } 572 573 /** 574 * Returns the set of the key values in the properties 575 * for the event. 576 * 577 * The returned set is unmodifiable by the caller. 578 * 579 * Provided for compatibility with log4j 1.3 580 * 581 * @return Set an unmodifiable set of the property keys. 582 * @since 1.2.15 583 */ 584 public Set getPropertyKeySet() { 585 return getProperties().keySet(); 586 } 587 588 /** 589 * Returns the set of properties 590 * for the event. 591 * 592 * The returned set is unmodifiable by the caller. 593 * 594 * Provided for compatibility with log4j 1.3 595 * 596 * @return Set an unmodifiable map of the properties. 597 * @since 1.2.15 598 */ 599 public Map getProperties() { 600 getMDCCopy(); 601 Map properties; 602 if (mdcCopy == null) { 603 properties = new HashMap(); 604 } else { 605 properties = mdcCopy; 606 } 607 return Collections.unmodifiableMap(properties); 608 } 609 610 /** 611 * Get the fully qualified name of the calling logger sub-class/wrapper. 612 * Provided for compatibility with log4j 1.3 613 * @return fully qualified class name, may be null. 614 * @since 1.2.15 615 */ 616 public String getFQNOfLoggerClass() { 617 return fqnOfCategoryClass; 618 } 619 620 621 /** 622 * This removes the specified MDC property from the event. 623 * Access to the MDC is not synchronized, so this 624 * method should only be called when it is known that 625 * no other threads are accessing the MDC. 626 * @param propName the property name to remove 627 * @since 1.2.16 628 */ 629 public Object removeProperty(String propName) { 630 if (mdcCopy == null) { 631 getMDCCopy(); 632 } 633 if (mdcCopy == null) { 634 mdcCopy = new Hashtable(); 635 } 636 return mdcCopy.remove(propName); 637 } 638}