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&uuml;lc&uuml;
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}