1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.log4j.spi;
19
20 import org.apache.log4j.*;
21
22 import org.apache.log4j.helpers.LogLog;
23 import org.apache.log4j.helpers.Loader;
24 import java.lang.reflect.Method;
25 import java.io.ObjectOutputStream;
26 import java.io.ObjectInputStream;
27 import java.util.Hashtable;
28 import java.util.Set;
29 import java.util.Collections;
30 import java.util.Map;
31 import java.util.HashMap;
32
33
34
35
36
37 /***
38 The internal representation of logging events. When an affirmative
39 decision is made to log then a <code>LoggingEvent</code> instance
40 is created. This instance is passed around to the different log4j
41 components.
42
43 <p>This class is of concern to those wishing to extend log4j.
44
45 @author Ceki Gülcü
46 @author James P. Cakalic
47
48 @since 0.8.2 */
49 public class LoggingEvent implements java.io.Serializable {
50
51 private static long startTime = System.currentTimeMillis();
52
53 /*** Fully qualified name of the calling category class. */
54 transient public final String fqnOfCategoryClass;
55
56 /***
57 * The category of the logging event. This field is not serialized
58 * for performance reasons.
59 *
60 * <p>It is set by the LoggingEvent constructor or set by a remote
61 * entity after deserialization.
62 *
63 * @deprecated This field will be marked as private or be completely
64 * removed in future releases. Please do not use it.
65 * */
66 transient private Category logger;
67
68 /***
69 * <p>The category (logger) name.
70 *
71 * @deprecated This field will be marked as private in future
72 * releases. Please do not access it directly. Use the {@link
73 * #getLoggerName} method instead.
74
75 * */
76 final public String categoryName;
77
78 /***
79 * Level of logging event. Level cannot be serializable because it
80 * is a flyweight. Due to its special seralization it cannot be
81 * declared final either.
82 *
83 * <p> This field should not be accessed directly. You shoud use the
84 * {@link #getLevel} method instead.
85 *
86 * @deprecated This field will be marked as private in future
87 * releases. Please do not access it directly. Use the {@link
88 * #getLevel} method instead.
89 * */
90 transient public Priority level;
91
92 /*** The nested diagnostic context (NDC) of logging event. */
93 private String ndc;
94
95 /*** The mapped diagnostic context (MDC) of logging event. */
96 private Hashtable mdcCopy;
97
98
99 /*** Have we tried to do an NDC lookup? If we did, there is no need
100 * to do it again. Note that its value is always false when
101 * serialized. Thus, a receiving SocketNode will never use it's own
102 * (incorrect) NDC. See also writeObject method. */
103 private boolean ndcLookupRequired = true;
104
105
106 /*** Have we tried to do an MDC lookup? If we did, there is no need
107 * to do it again. Note that its value is always false when
108 * serialized. See also the getMDC and getMDCCopy methods. */
109 private boolean mdcCopyLookupRequired = true;
110
111 /*** The application supplied message of logging event. */
112 transient private Object message;
113
114 /*** The application supplied message rendered through the log4j
115 objet rendering mechanism.*/
116 private String renderedMessage;
117
118 /*** The name of thread in which this logging event was generated. */
119 private String threadName;
120
121
122 /*** This
123 variable contains information about this event's throwable
124 */
125 private ThrowableInformation throwableInfo;
126
127 /*** The number of milliseconds elapsed from 1/1/1970 until logging event
128 was created. */
129 public final long timeStamp;
130 /*** Location information for the caller. */
131 private LocationInfo locationInfo;
132
133
134 static final long serialVersionUID = -868428216207166145L;
135
136 static final Integer[] PARAM_ARRAY = new Integer[1];
137 static final String TO_LEVEL = "toLevel";
138 static final Class[] TO_LEVEL_PARAMS = new Class[] {int.class};
139 static final Hashtable methodCache = new Hashtable(3);
140
141 /***
142 Instantiate a LoggingEvent from the supplied parameters.
143
144 <p>Except {@link #timeStamp} all the other fields of
145 <code>LoggingEvent</code> are filled when actually needed.
146 <p>
147 @param logger The logger generating this event.
148 @param level The level of this event.
149 @param message The message of this event.
150 @param throwable The throwable of this event. */
151 public LoggingEvent(String fqnOfCategoryClass, Category logger,
152 Priority level, Object message, Throwable throwable) {
153 this.fqnOfCategoryClass = fqnOfCategoryClass;
154 this.logger = logger;
155 this.categoryName = logger.getName();
156 this.level = level;
157 this.message = message;
158 if(throwable != null) {
159 this.throwableInfo = new ThrowableInformation(throwable);
160 }
161 timeStamp = System.currentTimeMillis();
162 }
163
164 /***
165 Instantiate a LoggingEvent from the supplied parameters.
166
167 <p>Except {@link #timeStamp} all the other fields of
168 <code>LoggingEvent</code> are filled when actually needed.
169 <p>
170 @param logger The logger generating this event.
171 @param timeStamp the timestamp of this logging event
172 @param level The level of this event.
173 @param message The message of this event.
174 @param throwable The throwable of this event. */
175 public LoggingEvent(String fqnOfCategoryClass, Category logger,
176 long timeStamp, Priority level, Object message,
177 Throwable throwable) {
178 this.fqnOfCategoryClass = fqnOfCategoryClass;
179 this.logger = logger;
180 this.categoryName = logger.getName();
181 this.level = level;
182 this.message = message;
183 if(throwable != null) {
184 this.throwableInfo = new ThrowableInformation(throwable);
185 }
186
187 this.timeStamp = timeStamp;
188 }
189
190 /***
191 Create new instance.
192 @since 1.2.15
193 @param fqnOfCategoryClass Fully qualified class name
194 of Logger implementation.
195 @param logger The logger generating this event.
196 @param timeStamp the timestamp of this logging event
197 @param level The level of this event.
198 @param message The message of this event.
199 @param threadName thread name
200 @param throwable The throwable of this event.
201 @param ndc Nested diagnostic context
202 @param info Location info
203 @param properties MDC properties
204 */
205 public LoggingEvent(final String fqnOfCategoryClass,
206 final Category logger,
207 final long timeStamp,
208 final Level level,
209 final Object message,
210 final String threadName,
211 final ThrowableInformation throwable,
212 final String ndc,
213 final LocationInfo info,
214 final java.util.Map properties) {
215 super();
216 this.fqnOfCategoryClass = fqnOfCategoryClass;
217 this.logger = logger;
218 if (logger != null) {
219 categoryName = logger.getName();
220 } else {
221 categoryName = null;
222 }
223 this.level = level;
224 this.message = message;
225 if(throwable != null) {
226 this.throwableInfo = throwable;
227 }
228
229 this.timeStamp = timeStamp;
230 this.threadName = threadName;
231 ndcLookupRequired = false;
232 this.ndc = ndc;
233 this.locationInfo = info;
234 mdcCopyLookupRequired = false;
235 if (properties != null) {
236 mdcCopy = new java.util.Hashtable(properties);
237 }
238 }
239
240
241 /***
242 Set the location information for this logging event. The collected
243 information is cached for future use.
244 */
245 public LocationInfo getLocationInformation() {
246 if(locationInfo == null) {
247 locationInfo = new LocationInfo(new Throwable(), fqnOfCategoryClass);
248 }
249 return locationInfo;
250 }
251
252 /***
253 * Return the level of this event. Use this form instead of directly
254 * accessing the <code>level</code> field. */
255 public Level getLevel() {
256 return (Level) level;
257 }
258
259 /***
260 * Return the name of the logger. Use this form instead of directly
261 * accessing the <code>categoryName</code> field.
262 */
263 public String getLoggerName() {
264 return categoryName;
265 }
266
267 /***
268 * Gets the logger of the event.
269 * Use should be restricted to cloning events.
270 * @since 1.2.15
271 */
272 public Category getLogger() {
273 return logger;
274 }
275
276 /***
277 Return the message for this logging event.
278
279 <p>Before serialization, the returned object is the message
280 passed by the user to generate the logging event. After
281 serialization, the returned value equals the String form of the
282 message possibly after object rendering.
283
284 @since 1.1 */
285 public
286 Object getMessage() {
287 if(message != null) {
288 return message;
289 } else {
290 return getRenderedMessage();
291 }
292 }
293
294 /***
295 * This method returns the NDC for this event. It will return the
296 * correct content even if the event was generated in a different
297 * thread or even on a different machine. The {@link NDC#get} method
298 * should <em>never</em> be called directly. */
299 public
300 String getNDC() {
301 if(ndcLookupRequired) {
302 ndcLookupRequired = false;
303 ndc = NDC.get();
304 }
305 return ndc;
306 }
307
308
309 /***
310 Returns the the context corresponding to the <code>key</code>
311 parameter. If there is a local MDC copy, possibly because we are
312 in a logging server or running inside AsyncAppender, then we
313 search for the key in MDC copy, if a value is found it is
314 returned. Otherwise, if the search in MDC copy returns a null
315 result, then the current thread's <code>MDC</code> is used.
316
317 <p>Note that <em>both</em> the local MDC copy and the current
318 thread's MDC are searched.
319
320 */
321 public
322 Object getMDC(String key) {
323 Object r;
324
325
326 if(mdcCopy != null) {
327 r = mdcCopy.get(key);
328 if(r != null) {
329 return r;
330 }
331 }
332 return MDC.get(key);
333 }
334
335 /***
336 Obtain a copy of this thread's MDC prior to serialization or
337 asynchronous logging.
338 */
339 public
340 void getMDCCopy() {
341 if(mdcCopyLookupRequired) {
342 mdcCopyLookupRequired = false;
343
344
345 Hashtable t = (Hashtable) MDC.getContext();
346 if(t != null) {
347 mdcCopy = (Hashtable) t.clone();
348 }
349 }
350 }
351
352 public
353 String getRenderedMessage() {
354 if(renderedMessage == null && message != null) {
355 if(message instanceof String)
356 renderedMessage = (String) message;
357 else {
358 LoggerRepository repository = logger.getLoggerRepository();
359
360 if(repository instanceof RendererSupport) {
361 RendererSupport rs = (RendererSupport) repository;
362 renderedMessage= rs.getRendererMap().findAndRender(message);
363 } else {
364 renderedMessage = message.toString();
365 }
366 }
367 }
368 return renderedMessage;
369 }
370
371 /***
372 Returns the time when the application started, in milliseconds
373 elapsed since 01.01.1970. */
374 public static long getStartTime() {
375 return startTime;
376 }
377
378 public
379 String getThreadName() {
380 if(threadName == null)
381 threadName = (Thread.currentThread()).getName();
382 return threadName;
383 }
384
385 /***
386 Returns the throwable information contained within this
387 event. May be <code>null</code> if there is no such information.
388
389 <p>Note that the {@link Throwable} object contained within a
390 {@link ThrowableInformation} does not survive serialization.
391
392 @since 1.1 */
393 public
394 ThrowableInformation getThrowableInformation() {
395 return throwableInfo;
396 }
397
398 /***
399 Return this event's throwable's string[] representaion.
400 */
401 public
402 String[] getThrowableStrRep() {
403
404 if(throwableInfo == null)
405 return null;
406 else
407 return throwableInfo.getThrowableStrRep();
408 }
409
410
411 private
412 void readLevel(ObjectInputStream ois)
413 throws java.io.IOException, ClassNotFoundException {
414
415 int p = ois.readInt();
416 try {
417 String className = (String) ois.readObject();
418 if(className == null) {
419 level = Level.toLevel(p);
420 } else {
421 Method m = (Method) methodCache.get(className);
422 if(m == null) {
423 Class clazz = Loader.loadClass(className);
424
425
426
427
428
429
430 m = clazz.getDeclaredMethod(TO_LEVEL, TO_LEVEL_PARAMS);
431 methodCache.put(className, m);
432 }
433 PARAM_ARRAY[0] = new Integer(p);
434 level = (Level) m.invoke(null, PARAM_ARRAY);
435 }
436 } catch(Exception e) {
437 LogLog.warn("Level deserialization failed, reverting to default.", e);
438 level = Level.toLevel(p);
439 }
440 }
441
442 private void readObject(ObjectInputStream ois)
443 throws java.io.IOException, ClassNotFoundException {
444 ois.defaultReadObject();
445 readLevel(ois);
446
447
448 if(locationInfo == null)
449 locationInfo = new LocationInfo(null, null);
450 }
451
452 private
453 void writeObject(ObjectOutputStream oos) throws java.io.IOException {
454
455
456 this.getThreadName();
457
458
459 this.getRenderedMessage();
460
461
462
463 this.getNDC();
464
465
466
467 this.getMDCCopy();
468
469
470 this.getThrowableStrRep();
471
472 oos.defaultWriteObject();
473
474
475 writeLevel(oos);
476 }
477
478 private
479 void writeLevel(ObjectOutputStream oos) throws java.io.IOException {
480
481 oos.writeInt(level.toInt());
482
483 Class clazz = level.getClass();
484 if(clazz == Level.class) {
485 oos.writeObject(null);
486 } else {
487
488
489
490 oos.writeObject(clazz.getName());
491 }
492 }
493
494 /***
495 * Set value for MDC property.
496 * This adds the specified MDC property to the event.
497 * Access to the MDC is not synchronized, so this
498 * method should only be called when it is known that
499 * no other threads are accessing the MDC.
500 * @since 1.2.15
501 * @param propName
502 * @param propValue
503 */
504 public final void setProperty(final String propName,
505 final String propValue) {
506 if (mdcCopy == null) {
507 getMDCCopy();
508 }
509 if (mdcCopy == null) {
510 mdcCopy = new Hashtable();
511 }
512 mdcCopy.put(propName, propValue);
513 }
514
515 /***
516 * Return a property for this event. The return value can be null.
517 *
518 * Equivalent to getMDC(String) in log4j 1.2. Provided
519 * for compatibility with log4j 1.3.
520 *
521 * @param key property name
522 * @return property value or null if property not set
523 * @since 1.2.15
524 */
525 public final String getProperty(final String key) {
526 Object value = getMDC(key);
527 String retval = null;
528 if (value != null) {
529 retval = value.toString();
530 }
531 return retval;
532 }
533
534 /***
535 * Check for the existence of location information without creating it
536 * (a byproduct of calling getLocationInformation).
537 * @return true if location information has been extracted.
538 * @since 1.2.15
539 */
540 public final boolean locationInformationExists() {
541 return (locationInfo != null);
542 }
543
544 /***
545 * Getter for the event's time stamp. The time stamp is calculated starting
546 * from 1970-01-01 GMT.
547 * @return timestamp
548 *
549 * @since 1.2.15
550 */
551 public final long getTimeStamp() {
552 return timeStamp;
553 }
554
555 /***
556 * Returns the set of the key values in the properties
557 * for the event.
558 *
559 * The returned set is unmodifiable by the caller.
560 *
561 * Provided for compatibility with log4j 1.3
562 *
563 * @return Set an unmodifiable set of the property keys.
564 * @since 1.2.15
565 */
566 public Set getPropertyKeySet() {
567 return getProperties().keySet();
568 }
569
570 /***
571 * Returns the set of properties
572 * for the event.
573 *
574 * The returned set is unmodifiable by the caller.
575 *
576 * Provided for compatibility with log4j 1.3
577 *
578 * @return Set an unmodifiable map of the properties.
579 * @since 1.2.15
580 */
581 public Map getProperties() {
582 getMDCCopy();
583 Map properties;
584 if (mdcCopy == null) {
585 properties = new HashMap();
586 } else {
587 properties = mdcCopy;
588 }
589 return Collections.unmodifiableMap(properties);
590 }
591
592 /***
593 * Get the fully qualified name of the calling logger sub-class/wrapper.
594 * Provided for compatibility with log4j 1.3
595 * @return fully qualified class name, may be null.
596 * @since 1.2.15
597 */
598 public String getFQNOfLoggerClass() {
599 return fqnOfCategoryClass;
600 }
601
602
603
604 }