View Javadoc

    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&uuml;lc&uuml;
    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 	PARAM_ARRAY[0] = new Integer(p);
    440 	level = (Level) m.invoke(null,  PARAM_ARRAY);
    441       }
    442     } catch(InvocationTargetException e) {
    443         if (e.getTargetException() instanceof InterruptedException
    444                 || e.getTargetException() instanceof InterruptedIOException) {
    445             Thread.currentThread().interrupt();
    446         }
    447     LogLog.warn("Level deserialization failed, reverting to default.", e);
    448 	level = Level.toLevel(p);
    449     } catch(NoSuchMethodException e) {
    450 	LogLog.warn("Level deserialization failed, reverting to default.", e);
    451 	level = Level.toLevel(p);
    452     } catch(IllegalAccessException e) {
    453 	LogLog.warn("Level deserialization failed, reverting to default.", e);
    454 	level = Level.toLevel(p);
    455     } catch(RuntimeException e) {
    456 	LogLog.warn("Level deserialization failed, reverting to default.", e);
    457 	level = Level.toLevel(p);
    458     }
    459   }
    460
    461   private void readObject(ObjectInputStream ois)
    462                         throws java.io.IOException, ClassNotFoundException {
    463     ois.defaultReadObject();
    464     readLevel(ois);
    465
    466     // Make sure that no location info is available to Layouts
    467     if(locationInfo == null)
    468       locationInfo = new LocationInfo(null, null);
    469   }
    470
    471   private
    472   void writeObject(ObjectOutputStream oos) throws java.io.IOException {
    473     // Aside from returning the current thread name the wgetThreadName
    474     // method sets the threadName variable.
    475     this.getThreadName();
    476
    477     // This sets the renders the message in case it wasn't up to now.
    478     this.getRenderedMessage();
    479
    480     // This call has a side effect of setting this.ndc and
    481     // setting ndcLookupRequired to false if not already false.
    482     this.getNDC();
    483
    484     // This call has a side effect of setting this.mdcCopy and
    485     // setting mdcLookupRequired to false if not already false.
    486     this.getMDCCopy();
    487
    488     // This sets the throwable sting representation of the event throwable.
    489     this.getThrowableStrRep();
    490
    491     oos.defaultWriteObject();
    492
    493     // serialize this event's level
    494     writeLevel(oos);
    495   }
    496
    497   private
    498   void writeLevel(ObjectOutputStream oos) throws java.io.IOException {
    499
    500     oos.writeInt(level.toInt());
    501
    502     Class clazz = level.getClass();
    503     if(clazz == Level.class) {
    504       oos.writeObject(null);
    505     } else {
    506       // writing directly the Class object would be nicer, except that
    507       // serialized a Class object can not be read back by JDK
    508       // 1.1.x. We have to resort to this hack instead.
    509       oos.writeObject(clazz.getName());
    510     }
    511   }
    512
    513     /**
    514      * Set value for MDC property.
    515      * This adds the specified MDC property to the event.
    516      * Access to the MDC is not synchronized, so this
    517      * method should only be called when it is known that
    518      * no other threads are accessing the MDC.
    519      * @since 1.2.15
    520      * @param propName
    521      * @param propValue
    522      */
    523   public final void setProperty(final String propName,
    524                           final String propValue) {
    525         if (mdcCopy == null) {
    526             getMDCCopy();
    527         }
    528         if (mdcCopy == null) {
    529             mdcCopy = new Hashtable();
    530         }
    531         mdcCopy.put(propName, propValue);
    532   }
    533
    534     /**
    535      * Return a property for this event. The return value can be null.
    536      *
    537      * Equivalent to getMDC(String) in log4j 1.2.  Provided
    538      * for compatibility with log4j 1.3.
    539      *
    540      * @param key property name
    541      * @return property value or null if property not set
    542      * @since 1.2.15
    543      */
    544     public final String getProperty(final String key) {
    545         Object value = getMDC(key);
    546         String retval = null;
    547         if (value != null) {
    548             retval = value.toString();
    549         }
    550         return retval;
    551     }
    552
    553     /**
    554      * Check for the existence of location information without creating it
    555      * (a byproduct of calling getLocationInformation).
    556      * @return true if location information has been extracted.
    557      * @since 1.2.15
    558      */
    559     public final boolean locationInformationExists() {
    560       return (locationInfo != null);
    561     }
    562
    563     /**
    564      * Getter for the event's time stamp. The time stamp is calculated starting
    565      * from 1970-01-01 GMT.
    566      * @return timestamp
    567      *
    568      * @since 1.2.15
    569      */
    570     public final long getTimeStamp() {
    571       return timeStamp;
    572     }
    573
    574     /**
    575      * Returns the set of the key values in the properties
    576      * for the event.
    577      *
    578      * The returned set is unmodifiable by the caller.
    579      *
    580      * Provided for compatibility with log4j 1.3
    581      *
    582      * @return Set an unmodifiable set of the property keys.
    583      * @since 1.2.15
    584      */
    585     public Set getPropertyKeySet() {
    586       return getProperties().keySet();
    587     }
    588
    589     /**
    590      * Returns the set of properties
    591      * for the event.
    592      *
    593      * The returned set is unmodifiable by the caller.
    594      *
    595      * Provided for compatibility with log4j 1.3
    596      *
    597      * @return Set an unmodifiable map of the properties.
    598      * @since 1.2.15
    599      */
    600     public Map getProperties() {
    601       getMDCCopy();
    602       Map properties;
    603       if (mdcCopy == null) {
    604          properties = new HashMap();
    605       } else {
    606          properties = mdcCopy;
    607       }
    608       return Collections.unmodifiableMap(properties);
    609     }
    610
    611     /**
    612      * Get the fully qualified name of the calling logger sub-class/wrapper.
    613      * Provided for compatibility with log4j 1.3
    614      * @return fully qualified class name, may be null.
    615      * @since 1.2.15
    616      */
    617     public String getFQNOfLoggerClass() {
    618       return fqnOfCategoryClass;
    619     }
    620
    621
    622     /**
    623      * This removes the specified MDC property from the event.
    624      * Access to the MDC is not synchronized, so this
    625      * method should only be called when it is known that
    626      * no other threads are accessing the MDC.
    627      * @param propName the property name to remove
    628      * @since 1.2.16
    629      */
    630     public Object removeProperty(String propName) {
    631         if (mdcCopy == null) {
    632             getMDCCopy();
    633         }
    634         if (mdcCopy == null) {
    635             mdcCopy = new Hashtable();
    636         }
    637         return mdcCopy.remove(propName);
    638     }
    639 }