001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018// WARNING This class MUST not have references to the Category or 019// WARNING RootCategory classes in its static initiliazation neither 020// WARNING directly nor indirectly. 021 022// Contributors: 023// Luke Blanshard <luke@quiq.com> 024// Mario Schomburg - IBM Global Services/Germany 025// Anders Kristensen 026// Igor Poteryaev 027 028package org.apache.log4j; 029 030 031import java.util.Hashtable; 032import java.util.Enumeration; 033import java.util.Vector; 034 035import org.apache.log4j.spi.LoggerFactory; 036import org.apache.log4j.spi.HierarchyEventListener; 037import org.apache.log4j.spi.LoggerRepository; 038import org.apache.log4j.spi.RendererSupport; 039import org.apache.log4j.or.RendererMap; 040import org.apache.log4j.or.ObjectRenderer; 041import org.apache.log4j.helpers.LogLog; 042import org.apache.log4j.spi.ThrowableRendererSupport; 043import org.apache.log4j.spi.ThrowableRenderer; 044 045/** 046 This class is specialized in retrieving loggers by name and also 047 maintaining the logger hierarchy. 048 049 <p><em>The casual user does not have to deal with this class 050 directly.</em> 051 052 <p>The structure of the logger hierarchy is maintained by the 053 {@link #getLogger} method. The hierarchy is such that children link 054 to their parent but parents do not have any pointers to their 055 children. Moreover, loggers can be instantiated in any order, in 056 particular descendant before ancestor. 057 058 <p>In case a descendant is created before a particular ancestor, 059 then it creates a provision node for the ancestor and adds itself 060 to the provision node. Other descendants of the same ancestor add 061 themselves to the previously created provision node. 062 063 @author Ceki Gülcü 064 065*/ 066public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport { 067 068 private LoggerFactory defaultFactory; 069 private Vector listeners; 070 071 Hashtable ht; 072 Logger root; 073 RendererMap rendererMap; 074 075 int thresholdInt; 076 Level threshold; 077 078 boolean emittedNoAppenderWarning = false; 079 boolean emittedNoResourceBundleWarning = false; 080 081 private ThrowableRenderer throwableRenderer = null; 082 083 /** 084 Create a new logger hierarchy. 085 086 @param root The root of the new hierarchy. 087 088 */ 089 public 090 Hierarchy(Logger root) { 091 ht = new Hashtable(); 092 listeners = new Vector(1); 093 this.root = root; 094 // Enable all level levels by default. 095 setThreshold(Level.ALL); 096 this.root.setHierarchy(this); 097 rendererMap = new RendererMap(); 098 defaultFactory = new DefaultCategoryFactory(); 099 } 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