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