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