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