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 package org.apache.log4j.spi;
19
20 import java.io.InterruptedIOException;
21 import java.io.ObjectInputStream;
22 import java.io.ObjectOutputStream;
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Method;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.Hashtable;
28 import java.util.Map;
29 import java.util.Set;
30
31 import org.apache.log4j.Category;
32 import org.apache.log4j.Level;
33 import org.apache.log4j.MDC;
34 import org.apache.log4j.NDC;
35 import org.apache.log4j.Priority;
36 import org.apache.log4j.helpers.Loader;
37 import org.apache.log4j.helpers.LogLog;
38
39 // Contributors: Nelson Minar <nelson@monkey.org>
40 // Wolf Siberski
41 // Anders Kristensen <akristensen@dynamicsoft.com>
42
43 /**
44 The internal representation of logging events. When an affirmative
45 decision is made to log then a <code>LoggingEvent</code> instance
46 is created. This instance is passed around to the different log4j
47 components.
48
49 <p>This class is of concern to those wishing to extend log4j.
50
51 @author Ceki Gülcü
52 @author James P. Cakalic
53
54 @since 0.8.2 */
55 public class LoggingEvent implements java.io.Serializable {
56
57 private static long startTime = System.currentTimeMillis();
58
59 /** Fully qualified name of the calling category class. */
60 transient public final String fqnOfCategoryClass;
61
62 /**
63 * The category of the logging event. This field is not serialized
64 * for performance reasons.
65 *
66 * <p>It is set by the LoggingEvent constructor or set by a remote
67 * entity after deserialization.
68 *
69 * @deprecated This field will be marked as private or be completely
70 * removed in future releases. Please do not use it.
71 * */
72 transient private Category logger;
73
74 /**
75 * <p>The category (logger) name.
76 *
77 * @deprecated This field will be marked as private in future
78 * releases. Please do not access it directly. Use the {@link
79 * #getLoggerName} method instead.
80
81 * */
82 final public String categoryName;
83
84 /**
85 * Level of logging event. Level cannot be serializable because it
86 * is a flyweight. Due to its special seralization it cannot be
87 * declared final either.
88 *
89 * <p> This field should not be accessed directly. You shoud use the
90 * {@link #getLevel} method instead.
91 *
92 * @deprecated This field will be marked as private in future
93 * releases. Please do not access it directly. Use the {@link
94 * #getLevel} method instead.
95 * */
96 transient public Priority level;
97
98 /** The nested diagnostic context (NDC) of logging event. */
99 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 }