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
018// WARNING This class MUST not have references to the Category or
019// WARNING RootCategory classes in its static initiliazation neither
020// WARNING directly nor indirectly.
021
022// Contributors:
023//                Luke Blanshard <luke@quiq.com>
024//                Mario Schomburg - IBM Global Services/Germany
025//                Anders Kristensen
026//                Igor Poteryaev
027
028package org.apache.log4j;
029
030
031import java.util.Hashtable;
032import java.util.Enumeration;
033import java.util.Vector;
034
035import org.apache.log4j.spi.LoggerFactory;
036import org.apache.log4j.spi.HierarchyEventListener;
037import org.apache.log4j.spi.LoggerRepository;
038import org.apache.log4j.spi.RendererSupport;
039import org.apache.log4j.or.RendererMap;
040import org.apache.log4j.or.ObjectRenderer;
041import org.apache.log4j.helpers.LogLog;
042import org.apache.log4j.spi.ThrowableRendererSupport;
043import org.apache.log4j.spi.ThrowableRenderer;
044
045/**
046   This class is specialized in retrieving loggers by name and also
047   maintaining the logger hierarchy.
048
049   <p><em>The casual user does not have to deal with this class
050   directly.</em>
051
052   <p>The structure of the logger hierarchy is maintained by the
053   {@link #getLogger} method. The hierarchy is such that children link
054   to their parent but parents do not have any pointers to their
055   children. Moreover, loggers can be instantiated in any order, in
056   particular descendant before ancestor.
057
058   <p>In case a descendant is created before a particular ancestor,
059   then it creates a provision node for the ancestor and adds itself
060   to the provision node. Other descendants of the same ancestor add
061   themselves to the previously created provision node.
062
063   @author Ceki G&uuml;lc&uuml;
064
065*/
066public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport {
067
068  private LoggerFactory defaultFactory;
069  private Vector listeners;
070
071  Hashtable ht;
072  Logger root;
073  RendererMap rendererMap;
074
075  int thresholdInt;
076  Level threshold;
077
078  boolean emittedNoAppenderWarning = false;
079  boolean emittedNoResourceBundleWarning = false;
080
081  private ThrowableRenderer throwableRenderer = null;
082
083  /**
084     Create a new logger hierarchy.
085
086     @param root The root of the new hierarchy.
087
088   */
089  public
090  Hierarchy(Logger root) {
091    ht = new Hashtable();
092    listeners = new Vector(1);
093    this.root = root;
094    // Enable all level levels by default.
095    setThreshold(Level.ALL);
096    this.root.setHierarchy(this);
097    rendererMap = new RendererMap();
098    defaultFactory = new DefaultCategoryFactory();
099  }
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