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  // WARNING This class MUST not have references to the Category or
    19  // WARNING RootCategory classes in its static initiliazation neither
    20  // WARNING directly nor indirectly.
    21
    22  // Contributors:
    23  //                Luke Blanshard <luke@quiq.com>
    24  //                Mario Schomburg - IBM Global Services/Germany
    25  //                Anders Kristensen
    26  //                Igor Poteryaev
    27
    28  package org.apache.log4j;
    29
    30
    31  import java.util.Hashtable;
    32  import java.util.Enumeration;
    33  import java.util.Vector;
    34
    35  import org.apache.log4j.spi.LoggerFactory;
    36  import org.apache.log4j.spi.HierarchyEventListener;
    37  import org.apache.log4j.spi.LoggerRepository;
    38  import org.apache.log4j.spi.RendererSupport;
    39  import org.apache.log4j.or.RendererMap;
    40  import org.apache.log4j.or.ObjectRenderer;
    41  import org.apache.log4j.helpers.LogLog;
    42  import org.apache.log4j.spi.ThrowableRendererSupport;
    43  import org.apache.log4j.spi.ThrowableRenderer;
    44
    45  /**
    46     This class is specialized in retrieving loggers by name and also
    47     maintaining the logger hierarchy.
    48
    49     <p><em>The casual user does not have to deal with this class
    50     directly.</em>
    51
    52     <p>The structure of the logger hierarchy is maintained by the
    53     {@link #getLogger} method. The hierarchy is such that children link
    54     to their parent but parents do not have any pointers to their
    55     children. Moreover, loggers can be instantiated in any order, in
    56     particular descendant before ancestor.
    57
    58     <p>In case a descendant is created before a particular ancestor,
    59     then it creates a provision node for the ancestor and adds itself
    60     to the provision node. Other descendants of the same ancestor add
    61     themselves to the previously created provision node.
    62
    63     @author Ceki G&uuml;lc&uuml;
    64
    65  */
    66  public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport {
    67
    68    private LoggerFactory defaultFactory;
    69    private Vector listeners;
    70
    71    Hashtable ht;
    72    Logger root;
    73    RendererMap rendererMap;
    74
    75    int thresholdInt;
    76    Level threshold;
    77
    78    boolean emittedNoAppenderWarning = false;
    79    boolean emittedNoResourceBundleWarning = false;
    80
    81    private ThrowableRenderer throwableRenderer = null;
    82
    83    /**
    84       Create a new logger hierarchy.
    85
    86       @param root The root of the new hierarchy.
    87
    88     */
    89    public
    90    Hierarchy(Logger root) {
    91      ht = new Hashtable();
    92      listeners = new Vector(1);
    93      this.root = root;
    94      // Enable all level levels by default.
    95      setThreshold(Level.ALL);
    96      this.root.setHierarchy(this);
    97      rendererMap = new RendererMap();
    98      defaultFactory = new DefaultCategoryFactory();
    99    }
    100
    101   /**
    102      Add an object renderer for a specific class.
    103    */
    104   public
    105   void addRenderer(Class classToRender, ObjectRenderer or) {
    106     rendererMap.put(classToRender, or);
    107   }
    108
    109   public
    110   void addHierarchyEventListener(HierarchyEventListener listener) {
    111     if(listeners.contains(listener)) {
    112       LogLog.warn("Ignoring attempt to add an existent listener.");
    113     } else {
    114       listeners.addElement(listener);
    115     }
    116   }
    117
    118   /**
    119      This call will clear all logger definitions from the internal
    120      hashtable. Invoking this method will irrevocably mess up the
    121      logger hierarchy.
    122
    123      <p>You should <em>really</em> know what you are doing before
    124      invoking this method.
    125
    126      @since 0.9.0 */
    127   public
    128   void clear() {
    129     //System.out.println("\n\nAbout to clear internal hash table.");
    130     ht.clear();
    131   }
    132
    133   public
    134   void emitNoAppenderWarning(Category cat) {
    135     // No appenders in hierarchy, warn user only once.
    136     if(!this.emittedNoAppenderWarning) {
    137       LogLog.warn("No appenders could be found for logger (" +
    138 		   cat.getName() + ").");
    139       LogLog.warn("Please initialize the log4j system properly.");
    140       LogLog.warn("See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.");
    141       this.emittedNoAppenderWarning = true;
    142     }
    143   }
    144
    145   /**
    146      Check if the named logger exists in the hierarchy. If so return
    147      its reference, otherwise returns <code>null</code>.
    148
    149      @param name The name of the logger to search for.
    150
    151   */
    152   public
    153   Logger exists(String name) {
    154     Object o = ht.get(new CategoryKey(name));
    155     if(o instanceof Logger) {
    156       return (Logger) o;
    157     } else {
    158       return null;
    159     }
    160   }
    161
    162   /**
    163      The string form of {@link #setThreshold(Level)}.
    164   */
    165   public
    166   void setThreshold(String levelStr) {
    167     Level l = (Level) Level.toLevel(levelStr, null);
    168     if(l != null) {
    169       setThreshold(l);
    170     } else {
    171       LogLog.warn("Could not convert ["+levelStr+"] to Level.");
    172     }
    173   }
    174
    175
    176   /**
    177      Enable logging for logging requests with level <code>l</code> or
    178      higher. By default all levels are enabled.
    179
    180      @param l The minimum level for which logging requests are sent to
    181      their appenders.  */
    182   public
    183   void setThreshold(Level l) {
    184     if(l != null) {
    185       thresholdInt = l.level;
    186       threshold = l;
    187     }
    188   }
    189
    190   public
    191   void fireAddAppenderEvent(Category logger, Appender appender) {
    192     if(listeners != null) {
    193       int size = listeners.size();
    194       HierarchyEventListener listener;
    195       for(int i = 0; i < size; i++) {
    196 	listener = (HierarchyEventListener) listeners.elementAt(i);
    197 	listener.addAppenderEvent(logger, appender);
    198       }
    199     }
    200   }
    201
    202   void fireRemoveAppenderEvent(Category logger, Appender appender) {
    203     if(listeners != null) {
    204       int size = listeners.size();
    205       HierarchyEventListener listener;
    206       for(int i = 0; i < size; i++) {
    207 	listener = (HierarchyEventListener) listeners.elementAt(i);
    208 	listener.removeAppenderEvent(logger, appender);
    209       }
    210     }
    211   }
    212
    213   /**
    214      Returns a {@link Level} representation of the <code>enable</code>
    215      state.
    216
    217      @since 1.2 */
    218   public
    219   Level getThreshold() {
    220     return threshold;
    221   }
    222
    223   /**
    224      Returns an integer representation of the this repository's
    225      threshold.
    226
    227      @since 1.2 */
    228   //public
    229   //int getThresholdInt() {
    230   //  return thresholdInt;
    231   //}
    232
    233
    234   /**
    235      Return a new logger instance named as the first parameter using
    236      the default factory.
    237
    238      <p>If a logger of that name already exists, then it will be
    239      returned.  Otherwise, a new logger will be instantiated and
    240      then linked with its existing ancestors as well as children.
    241
    242      @param name The name of the logger to retrieve.
    243
    244  */
    245   public
    246   Logger getLogger(String name) {
    247     return getLogger(name, defaultFactory);
    248   }
    249
    250  /**
    251      Return a new logger instance named as the first parameter using
    252      <code>factory</code>.
    253
    254      <p>If a logger of that name already exists, then it will be
    255      returned.  Otherwise, a new logger will be instantiated by the
    256      <code>factory</code> parameter and linked with its existing
    257      ancestors as well as children.
    258
    259      @param name The name of the logger to retrieve.
    260      @param factory The factory that will make the new logger instance.
    261
    262  */
    263   public
    264   Logger getLogger(String name, LoggerFactory factory) {
    265     //System.out.println("getInstance("+name+") called.");
    266     CategoryKey key = new CategoryKey(name);
    267     // Synchronize to prevent write conflicts. Read conflicts (in
    268     // getChainedLevel method) are possible only if variable
    269     // assignments are non-atomic.
    270     Logger logger;
    271
    272     synchronized(ht) {
    273       Object o = ht.get(key);
    274       if(o == null) {
    275 	logger = factory.makeNewLoggerInstance(name);
    276 	logger.setHierarchy(this);
    277 	ht.put(key, logger);
    278 	updateParents(logger);
    279 	return logger;
    280       } else if(o instanceof Logger) {
    281 	return (Logger) o;
    282       } else if (o instanceof ProvisionNode) {
    283 	//System.out.println("("+name+") ht.get(this) returned ProvisionNode");
    284 	logger = factory.makeNewLoggerInstance(name);
    285 	logger.setHierarchy(this);
    286 	ht.put(key, logger);
    287 	updateChildren((ProvisionNode) o, logger);
    288 	updateParents(logger);
    289 	return logger;
    290       }
    291       else {
    292 	// It should be impossible to arrive here
    293 	return null;  // but let's keep the compiler happy.
    294       }
    295     }
    296   }
    297
    298   /**
    299      Returns all the currently defined categories in this hierarchy as
    300      an {@link java.util.Enumeration Enumeration}.
    301
    302      <p>The root logger is <em>not</em> included in the returned
    303      {@link Enumeration}.  */
    304   public
    305   Enumeration getCurrentLoggers() {
    306     // The accumlation in v is necessary because not all elements in
    307     // ht are Logger objects as there might be some ProvisionNodes
    308     // as well.
    309     Vector v = new Vector(ht.size());
    310
    311     Enumeration elems = ht.elements();
    312     while(elems.hasMoreElements()) {
    313       Object o = elems.nextElement();
    314       if(o instanceof Logger) {
    315 	v.addElement(o);
    316       }
    317     }
    318     return v.elements();
    319   }
    320
    321   /**
    322      @deprecated Please use {@link #getCurrentLoggers} instead.
    323    */
    324   public
    325   Enumeration getCurrentCategories() {
    326     return getCurrentLoggers();
    327   }
    328
    329
    330   /**
    331      Get the renderer map for this hierarchy.
    332   */
    333   public
    334   RendererMap getRendererMap() {
    335     return rendererMap;
    336   }
    337
    338
    339   /**
    340      Get the root of this hierarchy.
    341
    342      @since 0.9.0
    343    */
    344   public
    345   Logger getRootLogger() {
    346     return root;
    347   }
    348
    349   /**
    350      This method will return <code>true</code> if this repository is
    351      disabled for <code>level</code> object passed as parameter and
    352      <code>false</code> otherwise. See also the {@link
    353      #setThreshold(Level) threshold} emthod.  */
    354   public
    355   boolean isDisabled(int level) {
    356     return thresholdInt > level;
    357   }
    358
    359   /**
    360      @deprecated Deprecated with no replacement.
    361   */
    362   public
    363   void overrideAsNeeded(String override) {
    364     LogLog.warn("The Hiearchy.overrideAsNeeded method has been deprecated.");
    365   }
    366
    367   /**
    368      Reset all values contained in this hierarchy instance to their
    369      default.  This removes all appenders from all categories, sets
    370      the level of all non-root categories to <code>null</code>,
    371      sets their additivity flag to <code>true</code> and sets the level
    372      of the root logger to {@link Level#DEBUG DEBUG}.  Moreover,
    373      message disabling is set its default "off" value.
    374
    375      <p>Existing categories are not removed. They are just reset.
    376
    377      <p>This method should be used sparingly and with care as it will
    378      block all logging until it is completed.</p>
    379
    380      @since 0.8.5 */
    381   public
    382   void resetConfiguration() {
    383
    384     getRootLogger().setLevel((Level) Level.DEBUG);
    385     root.setResourceBundle(null);
    386     setThreshold(Level.ALL);
    387
    388     // the synchronization is needed to prevent JDK 1.2.x hashtable
    389     // surprises
    390     synchronized(ht) {
    391       shutdown(); // nested locks are OK
    392
    393       Enumeration cats = getCurrentLoggers();
    394       while(cats.hasMoreElements()) {
    395 	Logger c = (Logger) cats.nextElement();
    396 	c.setLevel(null);
    397 	c.setAdditivity(true);
    398 	c.setResourceBundle(null);
    399       }
    400     }
    401     rendererMap.clear();
    402     throwableRenderer = null;
    403   }
    404
    405   /**
    406      Does nothing.
    407
    408      @deprecated Deprecated with no replacement.
    409    */
    410   public
    411   void setDisableOverride(String override) {
    412     LogLog.warn("The Hiearchy.setDisableOverride method has been deprecated.");
    413   }
    414
    415
    416
    417   /**
    418      Used by subclasses to add a renderer to the hierarchy passed as parameter.
    419    */
    420   public
    421   void setRenderer(Class renderedClass, ObjectRenderer renderer) {
    422     rendererMap.put(renderedClass, renderer);
    423   }
    424
    425     /**
    426      * {@inheritDoc}
    427      */
    428   public void setThrowableRenderer(final ThrowableRenderer renderer) {
    429       throwableRenderer = renderer;
    430   }
    431
    432     /**
    433      * {@inheritDoc}
    434      */
    435   public ThrowableRenderer getThrowableRenderer() {
    436       return throwableRenderer;
    437   }
    438
    439
    440   /**
    441      Shutting down a hierarchy will <em>safely</em> close and remove
    442      all appenders in all categories including the root logger.
    443
    444      <p>Some appenders such as {@link org.apache.log4j.net.SocketAppender}
    445      and {@link AsyncAppender} need to be closed before the
    446      application exists. Otherwise, pending logging events might be
    447      lost.
    448
    449      <p>The <code>shutdown</code> method is careful to close nested
    450      appenders before closing regular appenders. This is allows
    451      configurations where a regular appender is attached to a logger
    452      and again to a nested appender.
    453
    454
    455      @since 1.0 */
    456   public
    457   void shutdown() {
    458     Logger root = getRootLogger();
    459
    460     // begin by closing nested appenders
    461     root.closeNestedAppenders();
    462
    463     synchronized(ht) {
    464       Enumeration cats = this.getCurrentLoggers();
    465       while(cats.hasMoreElements()) {
    466 	Logger c = (Logger) cats.nextElement();
    467 	c.closeNestedAppenders();
    468       }
    469
    470       // then, remove all appenders
    471       root.removeAllAppenders();
    472       cats = this.getCurrentLoggers();
    473       while(cats.hasMoreElements()) {
    474 	Logger c = (Logger) cats.nextElement();
    475 	c.removeAllAppenders();
    476       }
    477     }
    478   }
    479
    480
    481   /**
    482      This method loops through all the *potential* parents of
    483      'cat'. There 3 possible cases:
    484
    485      1) No entry for the potential parent of 'cat' exists
    486
    487         We create a ProvisionNode for this potential parent and insert
    488         'cat' in that provision node.
    489
    490      2) There entry is of type Logger for the potential parent.
    491
    492         The entry is 'cat's nearest existing parent. We update cat's
    493         parent field with this entry. We also break from the loop
    494         because updating our parent's parent is our parent's
    495         responsibility.
    496
    497      3) There entry is of type ProvisionNode for this potential parent.
    498
    499         We add 'cat' to the list of children for this potential parent.
    500    */
    501   final
    502   private
    503   void updateParents(Logger cat) {
    504     String name = cat.name;
    505     int length = name.length();
    506     boolean parentFound = false;
    507
    508     //System.out.println("UpdateParents called for " + name);
    509
    510     // if name = "w.x.y.z", loop thourgh "w.x.y", "w.x" and "w", but not "w.x.y.z"
    511     for(int i = name.lastIndexOf('.', length-1); i >= 0;
    512 	                                 i = name.lastIndexOf('.', i-1))  {
    513       String substr = name.substring(0, i);
    514
    515       //System.out.println("Updating parent : " + substr);
    516       CategoryKey key = new CategoryKey(substr); // simple constructor
    517       Object o = ht.get(key);
    518       // Create a provision node for a future parent.
    519       if(o == null) {
    520 	//System.out.println("No parent "+substr+" found. Creating ProvisionNode.");
    521 	ProvisionNode pn = new ProvisionNode(cat);
    522 	ht.put(key, pn);
    523       } else if(o instanceof Category) {
    524 	parentFound = true;
    525 	cat.parent = (Category) o;
    526 	//System.out.println("Linking " + cat.name + " -> " + ((Category) o).name);
    527 	break; // no need to update the ancestors of the closest ancestor
    528       } else if(o instanceof ProvisionNode) {
    529 	((ProvisionNode) o).addElement(cat);
    530       } else {
    531 	Exception e = new IllegalStateException("unexpected object type " +
    532 					o.getClass() + " in ht.");
    533 	e.printStackTrace();
    534       }
    535     }
    536     // If we could not find any existing parents, then link with root.
    537     if(!parentFound)
    538       cat.parent = root;
    539   }
    540
    541   /**
    542       We update the links for all the children that placed themselves
    543       in the provision node 'pn'. The second argument 'cat' is a
    544       reference for the newly created Logger, parent of all the
    545       children in 'pn'
    546
    547       We loop on all the children 'c' in 'pn':
    548
    549          If the child 'c' has been already linked to a child of
    550          'cat' then there is no need to update 'c'.
    551
    552 	 Otherwise, we set cat's parent field to c's parent and set
    553 	 c's parent field to cat.
    554
    555   */
    556   final
    557   private
    558   void updateChildren(ProvisionNode pn, Logger logger) {
    559     //System.out.println("updateChildren called for " + logger.name);
    560     final int last = pn.size();
    561
    562     for(int i = 0; i < last; i++) {
    563       Logger l = (Logger) pn.elementAt(i);
    564       //System.out.println("Updating child " +p.name);
    565
    566       // Unless this child already points to a correct (lower) parent,
    567       // make cat.parent point to l.parent and l.parent to cat.
    568       if(!l.parent.name.startsWith(logger.name)) {
    569 	logger.parent = l.parent;
    570 	l.parent = logger;
    571       }
    572     }
    573   }
    574
    575 }
    576
    577