View Javadoc

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