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
578