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 package org.apache.logging.log4j.core.config; 018 019 import org.apache.logging.log4j.Level; 020 import org.apache.logging.log4j.Logger; 021 import org.apache.logging.log4j.core.Appender; 022 import org.apache.logging.log4j.core.Filter; 023 import org.apache.logging.log4j.core.Layout; 024 import org.apache.logging.log4j.core.LogEvent; 025 import org.apache.logging.log4j.core.appender.ConsoleAppender; 026 import org.apache.logging.log4j.core.config.plugins.PluginAttr; 027 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; 028 import org.apache.logging.log4j.core.config.plugins.PluginFactory; 029 import org.apache.logging.log4j.core.config.plugins.PluginManager; 030 import org.apache.logging.log4j.core.config.plugins.PluginElement; 031 import org.apache.logging.log4j.core.config.plugins.PluginNode; 032 import org.apache.logging.log4j.core.config.plugins.PluginType; 033 import org.apache.logging.log4j.core.config.plugins.PluginValue; 034 import org.apache.logging.log4j.core.filter.AbstractFilterable; 035 import org.apache.logging.log4j.core.helpers.NameUtil; 036 import org.apache.logging.log4j.core.layout.PatternLayout; 037 import org.apache.logging.log4j.core.lookup.Interpolator; 038 import org.apache.logging.log4j.core.lookup.StrLookup; 039 import org.apache.logging.log4j.core.lookup.StrSubstitutor; 040 import org.apache.logging.log4j.status.StatusLogger; 041 042 import java.lang.annotation.Annotation; 043 import java.lang.reflect.Array; 044 import java.lang.reflect.Method; 045 import java.lang.reflect.Modifier; 046 import java.util.ArrayList; 047 import java.util.Collections; 048 import java.util.List; 049 import java.util.Map; 050 import java.util.concurrent.ConcurrentHashMap; 051 import java.util.concurrent.ConcurrentMap; 052 import java.util.concurrent.CopyOnWriteArrayList; 053 054 /** 055 * The Base Configuration. Many configuration implementations will extend this class. 056 */ 057 public class BaseConfiguration extends AbstractFilterable implements Configuration { 058 /** 059 * Allow subclasses access to the status logger without creating another instance. 060 */ 061 protected static final Logger LOGGER = StatusLogger.getLogger(); 062 063 /** 064 * The root node of the configuration. 065 */ 066 protected Node rootNode; 067 068 /** 069 * The Plugin Manager. 070 */ 071 protected PluginManager pluginManager; 072 073 /** 074 * Listeners for configuration changes. 075 */ 076 protected final List<ConfigurationListener> listeners = 077 new CopyOnWriteArrayList<ConfigurationListener>(); 078 079 /** 080 * The ConfigurationMonitor that checks for configuration changes. 081 */ 082 protected ConfigurationMonitor monitor = new DefaultConfigurationMonitor(); 083 084 private String name; 085 086 private ConcurrentMap<String, Appender> appenders = new ConcurrentHashMap<String, Appender>(); 087 088 private ConcurrentMap<String, LoggerConfig> loggers = new ConcurrentHashMap<String, LoggerConfig>(); 089 090 private StrLookup tempLookup = new Interpolator(); 091 092 private StrSubstitutor subst = new StrSubstitutor(tempLookup); 093 094 private LoggerConfig root = new LoggerConfig(); 095 096 private boolean started = false; 097 098 private ConcurrentMap<String, Object> componentMap = new ConcurrentHashMap<String, Object>(); 099 100 /** 101 * Constructor. 102 */ 103 protected BaseConfiguration() { 104 pluginManager = new PluginManager("Core"); 105 rootNode = new Node(); 106 } 107 108 /** 109 * Initialize the configuration. 110 */ 111 public void start() { 112 pluginManager.collectPlugins(); 113 setup(); 114 doConfigure(); 115 for (LoggerConfig logger : loggers.values()) { 116 logger.startFilter(); 117 } 118 for (Appender appender : appenders.values()) { 119 appender.start(); 120 } 121 122 startFilter(); 123 } 124 125 /** 126 * Tear down the configuration. 127 */ 128 public void stop() { 129 for (LoggerConfig logger : loggers.values()) { 130 logger.clearAppenders(); 131 logger.stopFilter(); 132 } 133 // Stop the appenders in reverse order in case they still have activity. 134 Appender[] array = appenders.values().toArray(new Appender[appenders.size()]); 135 for (int i = array.length - 1; i > 0; --i) { 136 array[i].stop(); 137 } 138 stopFilter(); 139 } 140 141 protected void setup() { 142 } 143 144 public Object getComponent(String name) { 145 return componentMap.get(name); 146 } 147 148 public void addComponent(String name, Object obj) { 149 componentMap.putIfAbsent(name, obj); 150 } 151 152 protected void doConfigure() { 153 boolean setRoot = false; 154 boolean setLoggers = false; 155 for (Node child : rootNode.getChildren()) { 156 createConfiguration(child, null); 157 if (child.getObject() == null) { 158 continue; 159 } 160 if (child.getName().equalsIgnoreCase("properties")) { 161 if (tempLookup == subst.getVariableResolver()) { 162 subst.setVariableResolver((StrLookup) child.getObject()); 163 } else { 164 LOGGER.error("Properties declaration must be the first element in the configuration"); 165 } 166 continue; 167 } else if (tempLookup == subst.getVariableResolver()) { 168 subst.setVariableResolver(new Interpolator(null)); 169 } 170 if (child.getName().equalsIgnoreCase("appenders")) { 171 appenders = (ConcurrentMap<String, Appender>) child.getObject(); 172 } else if (child.getObject() instanceof Filter) { 173 addFilter((Filter) child.getObject()); 174 } else if (child.getName().equalsIgnoreCase("loggers")) { 175 Loggers l = (Loggers) child.getObject(); 176 loggers = l.getMap(); 177 setLoggers = true; 178 if (l.getRoot() != null) { 179 root = l.getRoot(); 180 setRoot = true; 181 } 182 } else { 183 LOGGER.error("Unknown object \"" + child.getName() + "\" of type " + 184 child.getObject().getClass().getName() + " is ignored"); 185 } 186 } 187 188 if (!setLoggers) { 189 LOGGER.warn("No Loggers were configured, using default. Is the Loggers element missing?"); 190 setToDefault(); 191 return; 192 } else if (!setRoot) { 193 LOGGER.warn("No Root logger was configured, using default"); 194 setToDefault(); 195 return; 196 } 197 198 for (Map.Entry<String, LoggerConfig> entry : loggers.entrySet()) { 199 LoggerConfig l = entry.getValue(); 200 for (AppenderRef ref : l.getAppenderRefs()) { 201 Appender app = appenders.get(ref.getRef()); 202 if (app != null) { 203 l.addAppender(app, ref.getLevel(), ref.getFilter()); 204 } else { 205 LOGGER.error("Unable to locate appender " + ref.getRef() + " for logger " + l.getName()); 206 } 207 } 208 209 } 210 211 setParents(); 212 } 213 214 private void setToDefault() { 215 setName(DefaultConfiguration.DEFAULT_NAME); 216 Layout layout = PatternLayout.createLayout("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n", 217 null, null, null); 218 Appender appender = ConsoleAppender.createAppender(layout, null, "SYSTEM_OUT", "Console", "true"); 219 appender.start(); 220 addAppender(appender); 221 LoggerConfig root = getRootLogger(); 222 root.addAppender(appender, null, null); 223 224 String levelName = System.getProperty(DefaultConfiguration.DEFAULT_LEVEL); 225 Level level = levelName != null && Level.valueOf(levelName) != null ? Level.valueOf(levelName) : Level.ERROR; 226 root.setLevel(level); 227 } 228 229 protected PluginManager getPluginManager() { 230 return pluginManager; 231 } 232 233 /** 234 * Set the name of the configuration. 235 * @param name The name. 236 */ 237 public void setName(String name) { 238 this.name = name; 239 } 240 241 /** 242 * Returns the name of the configuration. 243 * @return the name of the configuration. 244 */ 245 public String getName() { 246 return name; 247 } 248 249 /** 250 * Add a listener for changes on the configuration. 251 * @param listener The ConfigurationListener to add. 252 */ 253 public void addListener(ConfigurationListener listener) { 254 listeners.add(listener); 255 } 256 257 /** 258 * Remove a ConfigurationListener. 259 * @param listener The ConfigurationListener to remove. 260 */ 261 public void removeListener(ConfigurationListener listener) { 262 listeners.remove(listener); 263 } 264 265 /** 266 * Returns the Appender with the specified name. 267 * @param name The name of the Appender. 268 * @return the Appender with the specified name or null if the Appender cannot be located. 269 */ 270 public Appender getAppender(String name) { 271 return appenders.get(name); 272 } 273 274 /** 275 * Returns a Map containing all the Appenders and their name. 276 * @return A Map containing each Appender's naem and the Appender object. 277 */ 278 public Map<String, Appender> getAppenders() { 279 return appenders; 280 } 281 282 /** 283 * Adds an Appender to the configuration. 284 * @param appender The Appender to add. 285 */ 286 public void addAppender(Appender appender) { 287 appenders.put(appender.getName(), appender); 288 } 289 290 public StrSubstitutor getSubst() { 291 return subst; 292 } 293 294 public ConfigurationMonitor getConfigurationMonitor() { 295 return monitor; 296 } 297 298 /** 299 * Associates an Appender with a LoggerConfig. This method is synchronized in case a Logger with the 300 * same name is being updated at the same time. 301 * 302 * Note: This method is not used when configuring via configuration. It is primarily used by 303 * unit tests. 304 * @param logger The Logger the Appender will be associated with. 305 * @param appender The Appender. 306 */ 307 public synchronized void addLoggerAppender(org.apache.logging.log4j.core.Logger logger, Appender appender) { 308 String name = logger.getName(); 309 appenders.putIfAbsent(name, appender); 310 LoggerConfig lc = getLoggerConfig(name); 311 if (lc.getName().equals(name)) { 312 lc.addAppender(appender, null, null); 313 } else { 314 LoggerConfig nlc = new LoggerConfig(name, lc.getLevel(), lc.isAdditive()); 315 nlc.addAppender(appender, null, null); 316 nlc.setParent(lc); 317 loggers.putIfAbsent(name, nlc); 318 setParents(); 319 logger.getContext().updateLoggers(); 320 } 321 } 322 /** 323 * Associates a Filter with a LoggerConfig. This method is synchronized in case a Logger with the 324 * same name is being updated at the same time. 325 * 326 * Note: This method is not used when configuring via configuration. It is primarily used by 327 * unit tests. 328 * @param logger The Logger the Fo;ter will be associated with. 329 * @param filter The Filter. 330 */ 331 public synchronized void addLoggerFilter(org.apache.logging.log4j.core.Logger logger, Filter filter) { 332 String name = logger.getName(); 333 LoggerConfig lc = getLoggerConfig(name); 334 if (lc.getName().equals(name)) { 335 336 lc.addFilter(filter); 337 } else { 338 LoggerConfig nlc = new LoggerConfig(name, lc.getLevel(), lc.isAdditive()); 339 nlc.addFilter(filter); 340 nlc.setParent(lc); 341 loggers.putIfAbsent(name, nlc); 342 setParents(); 343 logger.getContext().updateLoggers(); 344 } 345 } 346 /** 347 * Marks a LoggerConfig as additive. This method is synchronized in case a Logger with the 348 * same name is being updated at the same time. 349 * 350 * Note: This method is not used when configuring via configuration. It is primarily used by 351 * unit tests. 352 * @param logger The Logger the Appender will be associated with. 353 * @param additive True if the LoggerConfig should be additive, false otherwise. 354 */ 355 public synchronized void setLoggerAdditive(org.apache.logging.log4j.core.Logger logger, boolean additive) { 356 String name = logger.getName(); 357 LoggerConfig lc = getLoggerConfig(name); 358 if (lc.getName().equals(name)) { 359 lc.setAdditive(additive); 360 } else { 361 LoggerConfig nlc = new LoggerConfig(name, lc.getLevel(), additive); 362 nlc.setParent(lc); 363 loggers.putIfAbsent(name, nlc); 364 setParents(); 365 logger.getContext().updateLoggers(); 366 } 367 } 368 369 /** 370 * Remove an Appender. First removes any associations between LoggerContigs and the Appender, removes 371 * the Appender from this appender list and then stops the appender. This method is synchronized in 372 * case an Appender with the same name is being added during the removal. 373 * @param name the name of the appender to remove. 374 */ 375 public synchronized void removeAppender(String name) { 376 for (LoggerConfig logger : loggers.values()) { 377 logger.removeAppender(name); 378 } 379 Appender app = appenders.remove(name); 380 381 if (app != null) { 382 app.stop(); 383 } 384 } 385 386 /** 387 * Locates the appropriate LoggerConfig for a Logger name. This will remove tokens from the 388 * package name as necessary or return the root LoggerConfig if no other matches were found. 389 * @param name The Logger name. 390 * @return The located LoggerConfig. 391 */ 392 public LoggerConfig getLoggerConfig(String name) { 393 if (loggers.containsKey(name)) { 394 return loggers.get(name); 395 } 396 String substr = name; 397 while ((substr = NameUtil.getSubName(substr)) != null) { 398 if (loggers.containsKey(substr)) { 399 return loggers.get(substr); 400 } 401 } 402 return root; 403 } 404 405 /** 406 * Returns the root Logger. 407 * @return the root Logger. 408 */ 409 public LoggerConfig getRootLogger() { 410 return root; 411 } 412 413 /** 414 * Returns a Map of all the LoggerConfigs. 415 * @return a Map with each entry containing the name of the Logger and the LoggerConfig. 416 */ 417 public Map<String, LoggerConfig> getLoggers() { 418 return Collections.unmodifiableMap(loggers); 419 } 420 421 /** 422 * Returns the LoggerConfig with the specified name. 423 * @param name The Logger name. 424 * @return The LoggerConfig or null if no match was found. 425 */ 426 public LoggerConfig getLogger(String name) { 427 return loggers.get(name); 428 } 429 430 /** 431 * Adding a logger cannot be done atomically so is not allowed in an active configuration. Adding 432 * or removing a Logger requires creating a new configuration and then switching. 433 * 434 * @param name The name of the Logger. 435 * @param loggerConfig The LoggerConfig. 436 */ 437 public void addLogger(String name, LoggerConfig loggerConfig) { 438 if (started) { 439 String msg = "Cannot add logger " + name + " to an active configuration"; 440 LOGGER.warn(msg); 441 throw new IllegalStateException(msg); 442 } 443 loggers.put(name, loggerConfig); 444 setParents(); 445 } 446 447 /** 448 * Removing a logger cannot be done atomically so is not allowed in an active configuration. Adding 449 * or removing a Logger requires creating a new configuration and then switching. 450 * 451 * @param name The name of the Logger. 452 */ 453 public void removeLogger(String name) { 454 if (started) { 455 String msg = "Cannot remove logger " + name + " in an active configuration"; 456 LOGGER.warn(msg); 457 throw new IllegalStateException(msg); 458 } 459 loggers.remove(name); 460 setParents(); 461 } 462 463 public void createConfiguration(Node node, LogEvent event) { 464 PluginType type = node.getType(); 465 if (type != null && type.isDeferChildren()) { 466 node.setObject(createPluginObject(type, node, event)); 467 } else { 468 for (Node child : node.getChildren()) { 469 createConfiguration(child, event); 470 } 471 472 if (type == null) { 473 if (node.getParent() != null) { 474 LOGGER.error("Unable to locate plugin for " + node.getName()); 475 } 476 } else { 477 node.setObject(createPluginObject(type, node, event)); 478 } 479 } 480 } 481 482 /* 483 * Retrieve a static public 'method to create the desired object. Every parameter 484 * will be annotated to identify the appropriate attribute or element to use to 485 * set the value of the paraemter. 486 * Parameters annotated with PluginAttr will always be set as Strings. 487 * Parameters annotated with PluginElement may be Objects or arrays. Collections 488 * and Maps are currently not supported, although the factory method that is called 489 * can create these from an array. 490 * 491 * Although the happy path works, more work still needs to be done to log incorrect 492 * parameters. These will generally result in unhelpful InvocationTargetExceptions. 493 * @param classClass the class. 494 * @return the instantiate method or null if there is none by that 495 * description. 496 */ 497 private Object createPluginObject(PluginType type, Node node, LogEvent event) 498 { 499 Class clazz = type.getPluginClass(); 500 501 if (Map.class.isAssignableFrom(clazz)) { 502 try { 503 Map<String, Object> map = (Map<String, Object>) clazz.newInstance(); 504 for (Node child : node.getChildren()) { 505 map.put(child.getName(), child.getObject()); 506 } 507 return map; 508 } catch (Exception ex) { 509 LOGGER.warn("Unable to create Map for " + type.getElementName() + " of class " + 510 clazz); 511 } 512 } 513 514 if (List.class.isAssignableFrom(clazz)) { 515 try { 516 List<Object> list = (List<Object>) clazz.newInstance(); 517 for (Node child : node.getChildren()) { 518 list.add(child.getObject()); 519 } 520 return list; 521 } catch (Exception ex) { 522 LOGGER.warn("Unable to create List for " + type.getElementName() + " of class " + 523 clazz); 524 } 525 } 526 527 Method factoryMethod = null; 528 529 for (Method method : clazz.getMethods()) { 530 if (method.isAnnotationPresent(PluginFactory.class)) { 531 factoryMethod = method; 532 break; 533 } 534 } 535 if (factoryMethod == null) { 536 return null; 537 } 538 539 Annotation[][] parmArray = factoryMethod.getParameterAnnotations(); 540 Class[] parmClasses = factoryMethod.getParameterTypes(); 541 if (parmArray.length != parmClasses.length) { 542 LOGGER.error("Number of parameter annotations does not equal the number of paramters"); 543 } 544 Object[] parms = new Object[parmClasses.length]; 545 546 int index = 0; 547 Map<String, String> attrs = node.getAttributes(); 548 List<Node> children = node.getChildren(); 549 StringBuilder sb = new StringBuilder(); 550 List<Node> used = new ArrayList<Node>(); 551 552 /* 553 * For each parameter: 554 * If the parameter is an attribute store the value of the attribute in the parameter array. 555 * If the parameter is an element: 556 * Determine if the required parameter is an array. 557 * If so, if a child contains the array, use it, 558 * otherwise create the array from all child nodes of the correct type. 559 * Store the array into the parameter array. 560 * If not an array, store the object in the child node into the parameter array. 561 */ 562 for (Annotation[] parmTypes : parmArray) { 563 for (Annotation a : parmTypes) { 564 if (sb.length() == 0) { 565 sb.append(" with params("); 566 } else { 567 sb.append(", "); 568 } 569 if (a instanceof PluginNode) { 570 parms[index] = node; 571 sb.append("Node=").append(node.getName()); 572 } else if (a instanceof PluginConfiguration) { 573 parms[index] = this; 574 if (this.name != null) { 575 sb.append("Configuration(").append(name).append(")"); 576 } else { 577 sb.append("Configuration"); 578 } 579 } else if (a instanceof PluginValue) { 580 String name = ((PluginValue) a).value(); 581 String v = node.getValue(); 582 if (v == null) { 583 v = getAttrValue("value", attrs); 584 } 585 String value = subst.replace(event, v); 586 sb.append(name).append("=\"").append(value).append("\""); 587 parms[index] = value; 588 } else if (a instanceof PluginAttr) { 589 String name = ((PluginAttr) a).value(); 590 String value = subst.replace(event, getAttrValue(name, attrs)); 591 sb.append(name).append("=\"").append(value).append("\""); 592 parms[index] = value; 593 } else if (a instanceof PluginElement) { 594 PluginElement elem = (PluginElement) a; 595 String name = elem.value(); 596 if (parmClasses[index].isArray()) { 597 Class parmClass = parmClasses[index].getComponentType(); 598 List<Object> list = new ArrayList<Object>(); 599 sb.append(name).append("={"); 600 boolean first = true; 601 for (Node child : children) { 602 PluginType childType = child.getType(); 603 if (elem.value().equalsIgnoreCase(childType.getElementName()) || 604 parmClass.isAssignableFrom(childType.getPluginClass())) { 605 used.add(child); 606 if (!first) { 607 sb.append(", "); 608 } 609 first = false; 610 Object obj = child.getObject(); 611 if (obj == null) { 612 LOGGER.error("Null object returned for " + child.getName() + " in " + 613 node.getName()); 614 continue; 615 } 616 if (obj.getClass().isArray()) { 617 printArray(sb, (Object[]) obj); 618 parms[index] = obj; 619 break; 620 } 621 sb.append(child.toString()); 622 list.add(obj); 623 } 624 } 625 sb.append("}"); 626 if (parms[index] != null) { 627 break; 628 } 629 if (list.size() > 0 && !parmClass.isAssignableFrom(list.get(0).getClass())) { 630 LOGGER.error("Attempted to assign List containing class " + 631 list.get(0).getClass().getName() + " to array of type " + parmClass + 632 " for attribute " + name); 633 break; 634 } 635 Object[] array = (Object[]) Array.newInstance(parmClass, list.size()); 636 int i = 0; 637 for (Object obj : list) { 638 array[i] = obj; 639 ++i; 640 } 641 parms[index] = array; 642 } else { 643 Class parmClass = parmClasses[index]; 644 boolean present = false; 645 for (Node child : children) { 646 PluginType childType = child.getType(); 647 if (elem.value().equals(childType.getElementName()) || 648 parmClass.isAssignableFrom(childType.getPluginClass())) { 649 sb.append(child.getName()).append("(").append(child.toString()).append(")"); 650 present = true; 651 used.add(child); 652 parms[index] = child.getObject(); 653 break; 654 } 655 } 656 if (!present) { 657 sb.append("null"); 658 } 659 } 660 } 661 } 662 ++index; 663 } 664 if (sb.length() > 0) { 665 sb.append(")"); 666 } 667 668 if (attrs.size() > 0) { 669 StringBuilder eb = new StringBuilder(); 670 for (String key : attrs.keySet()) { 671 if (eb.length() == 0) { 672 eb.append(node.getName()); 673 eb.append(" contains "); 674 if (attrs.size() == 1) { 675 eb.append("an invalid element or attribute "); 676 } else { 677 eb.append("invalid attributes "); 678 } 679 } else { 680 eb.append(", "); 681 } 682 eb.append("\""); 683 eb.append(key); 684 eb.append("\""); 685 686 } 687 LOGGER.error(eb.toString()); 688 } 689 690 if (!type.isDeferChildren() && used.size() != children.size()) { 691 for (Node child : children) { 692 if (used.contains(child)) { 693 continue; 694 } 695 String nodeType = node.getType().getElementName(); 696 String start = nodeType.equals(node.getName()) ? node.getName() : nodeType + " " + node.getName(); 697 LOGGER.error(start + " has no parameter that matches element " + child.getName()); 698 } 699 } 700 701 try { 702 int mod = factoryMethod.getModifiers(); 703 if (!Modifier.isStatic(mod)) { 704 LOGGER.error(factoryMethod.getName() + " method is not static on class " + 705 clazz.getName() + " for element " + node.getName()); 706 return null; 707 } 708 LOGGER.debug("Calling {} on class {} for element {}{}", factoryMethod.getName(), clazz.getName(), 709 node.getName(), sb.toString()); 710 //if (parms.length > 0) { 711 return factoryMethod.invoke(null, parms); 712 //} 713 //return factoryMethod.invoke(null, node); 714 } catch (Exception e) { 715 LOGGER.error("Unable to invoke method " + factoryMethod.getName() + " in class " + 716 clazz.getName() + " for element " + node.getName(), e); 717 } 718 return null; 719 } 720 721 private void printArray(StringBuilder sb, Object... array) { 722 boolean first = true; 723 for (Object obj : array) { 724 if (!first) { 725 sb.append(", "); 726 } 727 sb.append(obj.toString()); 728 first = false; 729 } 730 } 731 732 private String getAttrValue(String name, Map<String, String> attrs) { 733 for (String key : attrs.keySet()) { 734 if (key.equalsIgnoreCase(name)) { 735 String attr = attrs.get(key); 736 attrs.remove(key); 737 return attr; 738 } 739 } 740 return null; 741 } 742 743 private void setParents() { 744 for (Map.Entry<String, LoggerConfig> entry : loggers.entrySet()) { 745 LoggerConfig logger = entry.getValue(); 746 String name = entry.getKey(); 747 if (!name.equals("")) { 748 int i = name.lastIndexOf('.'); 749 if (i > 0) { 750 name = name.substring(0, i); 751 LoggerConfig parent = getLoggerConfig(name); 752 if (parent == null) { 753 parent = root; 754 } 755 logger.setParent(parent); 756 } 757 } 758 } 759 } 760 }