1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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ülcü
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
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
127 ht.clear();
128 }
129
130 public
131 void emitNoAppenderWarning(Category cat) {
132
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
225
226
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
262 CategoryKey key = new CategoryKey(name);
263
264
265
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
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
289 return null;
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
303
304
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
385
386 synchronized(ht) {
387 shutdown();
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
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
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
490
491
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
497 CategoryKey key = new CategoryKey(substr);
498 Object o = ht.get(key);
499
500 if(o == null) {
501
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
508 break;
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
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
541 final int last = pn.size();
542
543 for(int i = 0; i < last; i++) {
544 Logger l = (Logger) pn.elementAt(i);
545
546
547
548
549 if(!l.parent.name.startsWith(logger.name)) {
550 logger.parent = l.parent;
551 l.parent = logger;
552 }
553 }
554 }
555
556 }
557
558