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.pattern; 019 020import org.apache.log4j.Category; 021import org.apache.log4j.Level; 022import org.apache.log4j.Logger; 023import org.apache.log4j.MDC; 024import org.apache.log4j.NDC; 025import org.apache.log4j.Priority; 026import org.apache.log4j.helpers.Loader; 027import org.apache.log4j.helpers.LogLog; 028import org.apache.log4j.spi.LocationInfo; 029import org.apache.log4j.spi.LoggerRepository; 030import org.apache.log4j.spi.RendererSupport; 031import org.apache.log4j.spi.ThrowableInformation; 032 033import java.io.ObjectInputStream; 034import java.io.ObjectOutputStream; 035import java.lang.reflect.Method; 036import java.util.Collections; 037import java.util.HashMap; 038import java.util.Hashtable; 039import java.util.Map; 040import java.util.Set; 041 042// Contributors: Nelson Minar <nelson@monkey.org> 043// Wolf Siberski 044// Anders Kristensen <akristensen@dynamicsoft.com> 045 046/** 047 * This class is a copy of o.a.l.spi.LoggingEvent from 048 * log4j 1.2.15 which has been renamed to keep 049 * the same overall class name length to allow a 050 * serialization written with a prior instance of o.a.l.spi.LoggingEvent 051 * to be deserialized with this class just by mangling the class name 052 * in the byte stream. 053 * 054 */ 055public class LogEvent 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 LogEvent(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); 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 LogEvent(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); 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 LogEvent(final String fqnOfCategoryClass, 212 final Logger 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 Set the location information for this logging event. The collected 248 information is cached for future use. 249 */ 250 public LocationInfo getLocationInformation() { 251 if(locationInfo == null) { 252 locationInfo = new LocationInfo(new Throwable(), fqnOfCategoryClass); 253 } 254 return locationInfo; 255 } 256 257 /** 258 * Return the level of this event. Use this form instead of directly 259 * accessing the <code>level</code> field. */ 260 public Level getLevel() { 261 return (Level) level; 262 } 263 264 /** 265 * Return the name of the logger. Use this form instead of directly 266 * accessing the <code>categoryName</code> field. 267 */ 268 public String getLoggerName() { 269 return categoryName; 270 } 271 272 /** 273 Return the message for this logging event. 274 275 <p>Before serialization, the returned object is the message 276 passed by the user to generate the logging event. After 277 serialization, the returned value equals the String form of the 278 message possibly after object rendering. 279 280 @since 1.1 */ 281 public 282 Object getMessage() { 283 if(message != null) { 284 return message; 285 } else { 286 return getRenderedMessage(); 287 } 288 } 289 290 /** 291 * This method returns the NDC for this event. It will return the 292 * correct content even if the event was generated in a different 293 * thread or even on a different machine. The {@link NDC#get} method 294 * should <em>never</em> be called directly. */ 295 public 296 String getNDC() { 297 if(ndcLookupRequired) { 298 ndcLookupRequired = false; 299 ndc = NDC.get(); 300 } 301 return ndc; 302 } 303 304 305 /** 306 Returns the the context corresponding to the <code>key</code> 307 parameter. If there is a local MDC copy, possibly because we are 308 in a logging server or running inside AsyncAppender, then we 309 search for the key in MDC copy, if a value is found it is 310 returned. Otherwise, if the search in MDC copy returns a null 311 result, then the current thread's <code>MDC</code> is used. 312 313 <p>Note that <em>both</em> the local MDC copy and the current 314 thread's MDC are searched. 315 316 */ 317 public 318 Object getMDC(String key) { 319 Object r; 320 // Note the mdcCopy is used if it exists. Otherwise we use the MDC 321 // that is associated with the thread. 322 if(mdcCopy != null) { 323 r = mdcCopy.get(key); 324 if(r != null) { 325 return r; 326 } 327 } 328 return MDC.get(key); 329 } 330 331 /** 332 Obtain a copy of this thread's MDC prior to serialization or 333 asynchronous logging. 334 */ 335 public 336 void getMDCCopy() { 337 if(mdcCopyLookupRequired) { 338 mdcCopyLookupRequired = false; 339 // the clone call is required for asynchronous logging. 340 // See also bug #5932. 341 Hashtable t = (Hashtable) MDC.getContext(); 342 if(t != null) { 343 mdcCopy = (Hashtable) t.clone(); 344 } 345 } 346 } 347 348 public 349 String getRenderedMessage() { 350 if(renderedMessage == null && message != null) { 351 if(message instanceof String) 352 renderedMessage = (String) message; 353 else { 354 LoggerRepository repository = logger.getLoggerRepository(); 355 356 if(repository instanceof RendererSupport) { 357 RendererSupport rs = (RendererSupport) repository; 358 renderedMessage= rs.getRendererMap().findAndRender(message); 359 } else { 360 renderedMessage = message.toString(); 361 } 362 } 363 } 364 return renderedMessage; 365 } 366 367 /** 368 Returns the time when the application started, in milliseconds 369 elapsed since 01.01.1970. */ 370 public static long getStartTime() { 371 return startTime; 372 } 373 374 public 375 String getThreadName() { 376 if(threadName == null) 377 threadName = (Thread.currentThread()).getName(); 378 return threadName; 379 } 380 381 /** 382 Returns the throwable information contained within this 383 event. May be <code>null</code> if there is no such information. 384 385 <p>Note that the {@link Throwable} object contained within a 386 {@link ThrowableInformation} does not survive serialization. 387 388 @since 1.1 */ 389 public 390 ThrowableInformation getThrowableInformation() { 391 return throwableInfo; 392 } 393 394 /** 395 Return this event's throwable's string[] representaion. 396 */ 397 public 398 String[] getThrowableStrRep() { 399 400 if(throwableInfo == null) 401 return null; 402 else 403 return throwableInfo.getThrowableStrRep(); 404 } 405 406 407 private 408 void readLevel(ObjectInputStream ois) 409 throws java.io.IOException, ClassNotFoundException { 410 411 int p = ois.readInt(); 412 try { 413 String className = (String) ois.readObject(); 414 if(className == null) { 415 level = Level.toLevel(p); 416 } else { 417 Method m = (Method) methodCache.get(className); 418 if(m == null) { 419 Class clazz = Loader.loadClass(className); 420 // Note that we use Class.getDeclaredMethod instead of 421 // Class.getMethod. This assumes that the Level subclass 422 // implements the toLevel(int) method which is a 423 // requirement. Actually, it does not make sense for Level 424 // subclasses NOT to implement this method. Also note that 425 // only Level can be subclassed and not Priority. 426 m = clazz.getDeclaredMethod(TO_LEVEL, TO_LEVEL_PARAMS); 427 methodCache.put(className, m); 428 } 429 PARAM_ARRAY[0] = new Integer(p); 430 level = (Level) m.invoke(null, PARAM_ARRAY); 431 } 432 } catch(Exception e) { 433 LogLog.warn("Level deserialization failed, reverting to default.", e); 434 level = Level.toLevel(p); 435 } 436 } 437 438 private void readObject(ObjectInputStream ois) 439 throws java.io.IOException, ClassNotFoundException { 440 ois.defaultReadObject(); 441 readLevel(ois); 442 443 // Make sure that no location info is available to Layouts 444 if(locationInfo == null) 445 locationInfo = new LocationInfo(null, null); 446 } 447 448 private 449 void writeObject(ObjectOutputStream oos) throws java.io.IOException { 450 // Aside from returning the current thread name the wgetThreadName 451 // method sets the threadName variable. 452 this.getThreadName(); 453 454 // This sets the renders the message in case it wasn't up to now. 455 this.getRenderedMessage(); 456 457 // This call has a side effect of setting this.ndc and 458 // setting ndcLookupRequired to false if not already false. 459 this.getNDC(); 460 461 // This call has a side effect of setting this.mdcCopy and 462 // setting mdcLookupRequired to false if not already false. 463 this.getMDCCopy(); 464 465 // This sets the throwable sting representation of the event throwable. 466 this.getThrowableStrRep(); 467 468 oos.defaultWriteObject(); 469 470 // serialize this event's level 471 writeLevel(oos); 472 } 473 474 private 475 void writeLevel(ObjectOutputStream oos) throws java.io.IOException { 476 477 oos.writeInt(level.toInt()); 478 479 Class clazz = level.getClass(); 480 if(clazz == Level.class) { 481 oos.writeObject(null); 482 } else { 483 // writing directly the Class object would be nicer, except that 484 // serialized a Class object can not be read back by JDK 485 // 1.1.x. We have to resort to this hack instead. 486 oos.writeObject(clazz.getName()); 487 } 488 } 489 490 /** 491 * Set value for MDC property. 492 * This adds the specified MDC property to the event. 493 * Access to the MDC is not synchronized, so this 494 * method should only be called when it is known that 495 * no other threads are accessing the MDC. 496 * @since 1.2.15 497 * @param propName 498 * @param propValue 499 */ 500 public final void setProperty(final String propName, 501 final String propValue) { 502 if (mdcCopy == null) { 503 getMDCCopy(); 504 } 505 if (mdcCopy == null) { 506 mdcCopy = new Hashtable(); 507 } 508 mdcCopy.put(propName, propValue); 509 } 510 511 /** 512 * Return a property for this event. The return value can be null. 513 * 514 * Equivalent to getMDC(String) in log4j 1.2. Provided 515 * for compatibility with log4j 1.3. 516 * 517 * @param key property name 518 * @return property value or null if property not set 519 * @since 1.2.15 520 */ 521 public final String getProperty(final String key) { 522 Object value = getMDC(key); 523 String retval = null; 524 if (value != null) { 525 retval = value.toString(); 526 } 527 return retval; 528 } 529 530 /** 531 * Check for the existence of location information without creating it 532 * (a byproduct of calling getLocationInformation). 533 * @return true if location information has been extracted. 534 * @since 1.2.15 535 */ 536 public final boolean locationInformationExists() { 537 return (locationInfo != null); 538 } 539 540 /** 541 * Getter for the event's time stamp. The time stamp is calculated starting 542 * from 1970-01-01 GMT. 543 * @return timestamp 544 * 545 * @since 1.2.15 546 */ 547 public final long getTimeStamp() { 548 return timeStamp; 549 } 550 551 /** 552 * Returns the set of the key values in the properties 553 * for the event. 554 * 555 * The returned set is unmodifiable by the caller. 556 * 557 * Provided for compatibility with log4j 1.3 558 * 559 * @return Set an unmodifiable set of the property keys. 560 * @since 1.2.15 561 */ 562 public Set getPropertyKeySet() { 563 return getProperties().keySet(); 564 } 565 566 /** 567 * Returns the set of properties 568 * for the event. 569 * 570 * The returned set is unmodifiable by the caller. 571 * 572 * Provided for compatibility with log4j 1.3 573 * 574 * @return Set an unmodifiable map of the properties. 575 * @since 1.2.15 576 */ 577 public Map getProperties() { 578 getMDCCopy(); 579 Map properties; 580 if (mdcCopy == null) { 581 properties = new HashMap(); 582 } else { 583 properties = mdcCopy; 584 } 585 return Collections.unmodifiableMap(properties); 586 } 587 588 /** 589 * Get the fully qualified name of the calling logger sub-class/wrapper. 590 * Provided for compatibility with log4j 1.3 591 * @return fully qualified class name, may be null. 592 * @since 1.2.15 593 */ 594 public String getFQNOfLoggerClass() { 595 return fqnOfCategoryClass; 596 } 597 598 599 600}