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