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ülcü
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