Hierarchy.java
1 /* 2 * Copyright 1999-2005 The Apache Software Foundation. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // WARNING This class MUST not have references to the Category or 18 // WARNING RootCategory classes in its static initiliazation neither 19 // WARNING directly nor indirectly. 20 21 // Contributors: 22 // Luke Blanshard <luke@quiq.com> 23 // Mario Schomburg - IBM Global Services/Germany 24 // Anders Kristensen 25 // Igor Poteryaev 26 27 package org.apache.log4j; 28 29 30 import java.util.Hashtable; 31 import java.util.Enumeration; 32 import java.util.Vector; 33 34 import org.apache.log4j.spi.LoggerFactory; 35 import org.apache.log4j.spi.HierarchyEventListener; 36 import org.apache.log4j.spi.LoggerRepository; 37 import org.apache.log4j.spi.RendererSupport; 38 import org.apache.log4j.Appender; 39 import org.apache.log4j.or.RendererMap; 40 import org.apache.log4j.or.ObjectRenderer; 41 import org.apache.log4j.helpers.LogLog; 42 43 /** 44 This class is specialized in retrieving loggers by name and also 45 maintaining the logger hierarchy. 46 47 <p><em>The casual user does not have to deal with this class 48 directly.</em> 49 50 <p>The structure of the logger hierarchy is maintained by the 51 {@link #getLogger} method. The hierarchy is such that children link 52 to their parent but parents do not have any pointers to their 53 children. Moreover, loggers can be instantiated in any order, in 54 particular descendant before ancestor. 55 56 <p>In case a descendant is created before a particular ancestor, 57 then it creates a provision node for the ancestor and adds itself 58 to the provision node. Other descendants of the same ancestor add 59 themselves to the previously created provision node. 60 61 @author Ceki Gülcü 62 63 */ 64 public class Hierarchy implements LoggerRepository, RendererSupport { 65 66 private LoggerFactory defaultFactory; 67 private Vector listeners; 68 69 Hashtable ht; 70 Logger root; 71 RendererMap rendererMap; 72 73 int thresholdInt; 74 Level threshold; 75 76 boolean emittedNoAppenderWarning = false; 77 boolean emittedNoResourceBundleWarning = false; 78 79 /** 80 Create a new logger hierarchy. 81 82 @param root The root of the new hierarchy. 83 84 */ 85 public 86 Hierarchy(Logger root) { 87 ht = new Hashtable(); 88 listeners = new Vector(1); 89 this.root = root; 90 // Enable all level levels by default. 91 setThreshold(Level.ALL); 92 this.root.setHierarchy(this); 93 rendererMap = new RendererMap(); 94 defaultFactory = new DefaultCategoryFactory(); 95 } 96 97 /** 98 Add an object renderer for a specific class. 99 */ 100 public 101 void addRenderer(Class classToRender, ObjectRenderer or) { 102 rendererMap.put(classToRender, or); 103 } 104 105 public 106 void addHierarchyEventListener(HierarchyEventListener listener) { 107 if(listeners.contains(listener)) { 108 LogLog.warn("Ignoring attempt to add an existent listener."); 109 } else { 110 listeners.addElement(listener); 111 } 112 } 113 114 /** 115 This call will clear all logger definitions from the internal 116 hashtable. Invoking this method will irrevocably mess up the 117 logger hierarchy. 118 119 <p>You should <em>really</em> know what you are doing before 120 invoking this method. 121 122 @since 0.9.0 */ 123 public 124 void clear() { 125 //System.out.println("\n\nAbout to clear internal hash table."); 126 ht.clear(); 127 } 128 129 public 130 void emitNoAppenderWarning(Category cat) { 131 // No appenders in hierarchy, warn user only once. 132 if(!this.emittedNoAppenderWarning) { 133 LogLog.warn("No appenders could be found for logger (" + 134 cat.getName() + ")."); 135 LogLog.warn("Please initialize the log4j system properly."); 136 this.emittedNoAppenderWarning = true; 137 } 138 } 139 140 /** 141 Check if the named logger exists in the hierarchy. If so return 142 its reference, otherwise returns <code>null</code>. 143 144 @param name The name of the logger to search for. 145 146 */ 147 public 148 Logger exists(String name) { 149 Object o = ht.get(new CategoryKey(name)); 150 if(o instanceof Logger) { 151 return (Logger) o; 152 } else { 153 return null; 154 } 155 } 156 157 /** 158 The string form of {@link #setThreshold(Level)}. 159 */ 160 public 161 void setThreshold(String levelStr) { 162 Level l = (Level) Level.toLevel(levelStr, null); 163 if(l != null) { 164 setThreshold(l); 165 } else { 166 LogLog.warn("Could not convert ["+levelStr+"] to Level."); 167 } 168 } 169 170 171 /** 172 Enable logging for logging requests with level <code>l</code> or 173 higher. By default all levels are enabled. 174 175 @param l The minimum level for which logging requests are sent to 176 their appenders. */ 177 public 178 void setThreshold(Level l) { 179 if(l != null) { 180 thresholdInt = l.level; 181 threshold = l; 182 } 183 } 184 185 public 186 void fireAddAppenderEvent(Category logger, Appender appender) { 187 if(listeners != null) { 188 int size = listeners.size(); 189 HierarchyEventListener listener; 190 for(int i = 0; i < size; i++) { 191 listener = (HierarchyEventListener) listeners.elementAt(i); 192 listener.addAppenderEvent(logger, appender); 193 } 194 } 195 } 196 197 void fireRemoveAppenderEvent(Category logger, Appender appender) { 198 if(listeners != null) { 199 int size = listeners.size(); 200 HierarchyEventListener listener; 201 for(int i = 0; i < size; i++) { 202 listener = (HierarchyEventListener) listeners.elementAt(i); 203 listener.removeAppenderEvent(logger, appender); 204 } 205 } 206 } 207 208 /** 209 Returns a {@link Level} representation of the <code>enable</code> 210 state. 211 212 @since 1.2 */ 213 public 214 Level getThreshold() { 215 return threshold; 216 } 217 218 /** 219 Returns an integer representation of the this repository's 220 threshold. 221 222 @since 1.2 */ 223 //public 224 //int getThresholdInt() { 225 // return thresholdInt; 226 //} 227 228 229 /** 230 Return a new logger instance named as the first parameter using 231 the default factory. 232 233 <p>If a logger of that name already exists, then it will be 234 returned. Otherwise, a new logger will be instantiated and 235 then linked with its existing ancestors as well as children. 236 237 @param name The name of the logger to retrieve. 238 239 */ 240 public 241 Logger getLogger(String name) { 242 return getLogger(name, defaultFactory); 243 } 244 245 /** 246 Return a new logger instance named as the first parameter using 247 <code>factory</code>. 248 249 <p>If a logger of that name already exists, then it will be 250 returned. Otherwise, a new logger will be instantiated by the 251 <code>factory</code> parameter and linked with its existing 252 ancestors as well as children. 253 254 @param name The name of the logger to retrieve. 255 @param factory The factory that will make the new logger instance. 256 257 */ 258 public 259 Logger getLogger(String name, LoggerFactory factory) { 260 //System.out.println("getInstance("+name+") called."); 261 CategoryKey key = new CategoryKey(name); 262 // Synchronize to prevent write conflicts. Read conflicts (in 263 // getChainedLevel method) are possible only if variable 264 // assignments are non-atomic. 265 Logger logger; 266 267 synchronized(ht) { 268 Object o = ht.get(key); 269 if(o == null) { 270 logger = factory.makeNewLoggerInstance(name); 271 logger.setHierarchy(this); 272 ht.put(key, logger); 273 updateParents(logger); 274 return logger; 275 } else if(o instanceof Logger) { 276 return (Logger) o; 277 } else if (o instanceof ProvisionNode) { 278 //System.out.println("("+name+") ht.get(this) returned ProvisionNode"); 279 logger = factory.makeNewLoggerInstance(name); 280 logger.setHierarchy(this); 281 ht.put(key, logger); 282 updateChildren((ProvisionNode) o, logger); 283 updateParents(logger); 284 return logger; 285 } 286 else { 287 // It should be impossible to arrive here 288 return null; // but let's keep the compiler happy. 289 } 290 } 291 } 292 293 /** 294 Returns all the currently defined categories in this hierarchy as 295 an {@link java.util.Enumeration Enumeration}. 296 297 <p>The root logger is <em>not</em> included in the returned 298 {@link Enumeration}. */ 299 public 300 Enumeration getCurrentLoggers() { 301 // The accumlation in v is necessary because not all elements in 302 // ht are Logger objects as there might be some ProvisionNodes 303 // as well. 304 Vector v = new Vector(ht.size()); 305 306 Enumeration elems = ht.elements(); 307 while(elems.hasMoreElements()) { 308 Object o = elems.nextElement(); 309 if(o instanceof Logger) { 310 v.addElement(o); 311 } 312 } 313 return v.elements(); 314 } 315 316 /** 317 @deprecated Please use {@link #getCurrentLoggers} instead. 318 */ 319 public 320 Enumeration getCurrentCategories() { 321 return getCurrentLoggers(); 322 } 323 324 325 /** 326 Get the renderer map for this hierarchy. 327 */ 328 public 329 RendererMap getRendererMap() { 330 return rendererMap; 331 } 332 333 334 /** 335 Get the root of this hierarchy. 336 337 @since 0.9.0 338 */ 339 public 340 Logger getRootLogger() { 341 return root; 342 } 343 344 /** 345 This method will return <code>true</code> if this repository is 346 disabled for <code>level</code> object passed as parameter and 347 <code>false</code> otherwise. See also the {@link 348 #setThreshold(Level) threshold} emthod. */ 349 public 350 boolean isDisabled(int level) { 351 return thresholdInt > level; 352 } 353 354 /** 355 @deprecated Deprecated with no replacement. 356 */ 357 public 358 void overrideAsNeeded(String override) { 359 LogLog.warn("The Hiearchy.overrideAsNeeded method has been deprecated."); 360 } 361 362 /** 363 Reset all values contained in this hierarchy instance to their 364 default. This removes all appenders from all categories, sets 365 the level of all non-root categories to <code>null</code>, 366 sets their additivity flag to <code>true</code> and sets the level 367 of the root logger to {@link Level#DEBUG DEBUG}. Moreover, 368 message disabling is set its default "off" value. 369 370 <p>Existing categories are not removed. They are just reset. 371 372 <p>This method should be used sparingly and with care as it will 373 block all logging until it is completed.</p> 374 375 @since 0.8.5 */ 376 public 377 void resetConfiguration() { 378 379 getRootLogger().setLevel((Level) Level.DEBUG); 380 root.setResourceBundle(null); 381 setThreshold(Level.ALL); 382 383 // the synchronization is needed to prevent JDK 1.2.x hashtable 384 // surprises 385 synchronized(ht) { 386 shutdown(); // nested locks are OK 387 388 Enumeration cats = getCurrentLoggers(); 389 while(cats.hasMoreElements()) { 390 Logger c = (Logger) cats.nextElement(); 391 c.setLevel(null); 392 c.setAdditivity(true); 393 c.setResourceBundle(null); 394 } 395 } 396 rendererMap.clear(); 397 } 398 399 /** 400 Does mothing. 401 402 @deprecated Deprecated with no replacement. 403 */ 404 public 405 void setDisableOverride(String override) { 406 LogLog.warn("The Hiearchy.setDisableOverride method has been deprecated."); 407 } 408 409 410 411 /** 412 Used by subclasses to add a renderer to the hierarchy passed as parameter. 413 */ 414 public 415 void setRenderer(Class renderedClass, ObjectRenderer renderer) { 416 rendererMap.put(renderedClass, renderer); 417 } 418 419 420 /** 421 Shutting down a hierarchy will <em>safely</em> close and remove 422 all appenders in all categories including the root logger. 423 424 <p>Some appenders such as {@link org.apache.log4j.net.SocketAppender} 425 and {@link AsyncAppender} need to be closed before the 426 application exists. Otherwise, pending logging events might be 427 lost. 428 429 <p>The <code>shutdown</code> method is careful to close nested 430 appenders before closing regular appenders. This is allows 431 configurations where a regular appender is attached to a logger 432 and again to a nested appender. 433 434 435 @since 1.0 */ 436 public 437 void shutdown() { 438 Logger root = getRootLogger(); 439 440 // begin by closing nested appenders 441 root.closeNestedAppenders(); 442 443 synchronized(ht) { 444 Enumeration cats = this.getCurrentLoggers(); 445 while(cats.hasMoreElements()) { 446 Logger c = (Logger) cats.nextElement(); 447 c.closeNestedAppenders(); 448 } 449 450 // then, remove all appenders 451 root.removeAllAppenders(); 452 cats = this.getCurrentLoggers(); 453 while(cats.hasMoreElements()) { 454 Logger c = (Logger) cats.nextElement(); 455 c.removeAllAppenders(); 456 } 457 } 458 } 459 460 461 /** 462 This method loops through all the *potential* parents of 463 'cat'. There 3 possible cases: 464 465 1) No entry for the potential parent of 'cat' exists 466 467 We create a ProvisionNode for this potential parent and insert 468 'cat' in that provision node. 469 470 2) There entry is of type Logger for the potential parent. 471 472 The entry is 'cat's nearest existing parent. We update cat's 473 parent field with this entry. We also break from the loop 474 because updating our parent's parent is our parent's 475 responsibility. 476 477 3) There entry is of type ProvisionNode for this potential parent. 478 479 We add 'cat' to the list of children for this potential parent. 480 */ 481 final 482 private 483 void updateParents(Logger cat) { 484 String name = cat.name; 485 int length = name.length(); 486 boolean parentFound = false; 487 488 //System.out.println("UpdateParents called for " + name); 489 490 // if name = "w.x.y.z", loop thourgh "w.x.y", "w.x" and "w", but not "w.x.y.z" 491 for(int i = name.lastIndexOf('.', length-1); i >= 0; 492 i = name.lastIndexOf('.', i-1)) { 493 String substr = name.substring(0, i); 494 495 //System.out.println("Updating parent : " + substr); 496 CategoryKey key = new CategoryKey(substr); // simple constructor 497 Object o = ht.get(key); 498 // Create a provision node for a future parent. 499 if(o == null) { 500 //System.out.println("No parent "+substr+" found. Creating ProvisionNode."); 501 ProvisionNode pn = new ProvisionNode(cat); 502 ht.put(key, pn); 503 } else if(o instanceof Category) { 504 parentFound = true; 505 cat.parent = (Category) o; 506 //System.out.println("Linking " + cat.name + " -> " + ((Category) o).name); 507 break; // no need to update the ancestors of the closest ancestor 508 } else if(o instanceof ProvisionNode) { 509 ((ProvisionNode) o).addElement(cat); 510 } else { 511 Exception e = new IllegalStateException("unexpected object type " + 512 o.getClass() + " in ht."); 513 e.printStackTrace(); 514 } 515 } 516 // If we could not find any existing parents, then link with root. 517 if(!parentFound) 518 cat.parent = root; 519 } 520 521 /** 522 We update the links for all the children that placed themselves 523 in the provision node 'pn'. The second argument 'cat' is a 524 reference for the newly created Logger, parent of all the 525 children in 'pn' 526 527 We loop on all the children 'c' in 'pn': 528 529 If the child 'c' has been already linked to a child of 530 'cat' then there is no need to update 'c'. 531 532 Otherwise, we set cat's parent field to c's parent and set 533 c's parent field to cat. 534 535 */ 536 final 537 private 538 void updateChildren(ProvisionNode pn, Logger logger) { 539 //System.out.println("updateChildren called for " + logger.name); 540 final int last = pn.size(); 541 542 for(int i = 0; i < last; i++) { 543 Logger l = (Logger) pn.elementAt(i); 544 //System.out.println("Updating child " +p.name); 545 546 // Unless this child already points to a correct (lower) parent, 547 // make cat.parent point to l.parent and l.parent to cat. 548 if(!l.parent.name.startsWith(logger.name)) { 549 logger.parent = l.parent; 550 l.parent = logger; 551 } 552 } 553 } 554 555 } 556 557