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.pattern;
    19
    20  import org.apache.log4j.Category;
    21  import org.apache.log4j.Level;
    22  import org.apache.log4j.Logger;
    23  import org.apache.log4j.MDC;
    24  import org.apache.log4j.NDC;
    25  import org.apache.log4j.Priority;
    26  import org.apache.log4j.helpers.Loader;
    27  import org.apache.log4j.helpers.LogLog;
    28  import org.apache.log4j.spi.LocationInfo;
    29  import org.apache.log4j.spi.LoggerRepository;
    30  import org.apache.log4j.spi.RendererSupport;
    31  import org.apache.log4j.spi.ThrowableInformation;
    32
    33  import java.io.ObjectInputStream;
    34  import java.io.ObjectOutputStream;
    35  import java.lang.reflect.Method;
    36  import java.util.Collections;
    37  import java.util.HashMap;
    38  import java.util.Hashtable;
    39  import java.util.Map;
    40  import java.util.Set;
    41
    42  // Contributors:   Nelson Minar <nelson@monkey.org>
    43  //                 Wolf Siberski
    44  //                 Anders Kristensen <akristensen@dynamicsoft.com>
    45
    46  /**
    47   * This class is a copy of o.a.l.spi.LoggingEvent from
    48   * log4j 1.2.15 which has been renamed to keep
    49   * the same overall class name length to allow a
    50   * serialization written with a prior instance of o.a.l.spi.LoggingEvent
    51   * to be deserialized with this class just by mangling the class name
    52   * in the byte stream.
    53   *
    54   */
    55  public class LogEvent 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 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 }