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.log4j.xml;
18  
19  import java.io.IOException;
20  import java.io.InterruptedIOException;
21  import java.lang.reflect.Method;
22  import java.util.HashMap;
23  import java.util.Map;
24  import java.util.Properties;
25  import java.util.function.Consumer;
26  
27  import javax.xml.parsers.DocumentBuilder;
28  import javax.xml.parsers.DocumentBuilderFactory;
29  import javax.xml.parsers.FactoryConfigurationError;
30  
31  import org.apache.log4j.Appender;
32  import org.apache.log4j.Layout;
33  import org.apache.log4j.Level;
34  import org.apache.log4j.bridge.AppenderAdapter;
35  import org.apache.log4j.bridge.AppenderWrapper;
36  import org.apache.log4j.config.Log4j1Configuration;
37  import org.apache.log4j.config.PropertySetter;
38  import org.apache.log4j.helpers.OptionConverter;
39  import org.apache.log4j.rewrite.RewritePolicy;
40  import org.apache.log4j.spi.AppenderAttachable;
41  import org.apache.log4j.spi.ErrorHandler;
42  import org.apache.log4j.spi.Filter;
43  import org.apache.logging.log4j.core.LoggerContext;
44  import org.apache.logging.log4j.core.config.Configuration;
45  import org.apache.logging.log4j.core.config.ConfigurationSource;
46  import org.apache.logging.log4j.core.config.LoggerConfig;
47  import org.apache.logging.log4j.core.config.status.StatusConfiguration;
48  import org.apache.logging.log4j.status.StatusLogger;
49  import org.apache.logging.log4j.util.LoaderUtil;
50  import org.w3c.dom.Document;
51  import org.w3c.dom.Element;
52  import org.w3c.dom.NamedNodeMap;
53  import org.w3c.dom.Node;
54  import org.w3c.dom.NodeList;
55  import org.xml.sax.InputSource;
56  import org.xml.sax.SAXException;
57  import org.xml.sax.SAXParseException;
58  
59  /**
60   * Class Description goes here.
61   */
62  public class XmlConfiguration extends Log4j1Configuration {
63  
64      private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger();
65  
66      private static final String CONFIGURATION_TAG = "log4j:configuration";
67      private static final String OLD_CONFIGURATION_TAG = "configuration";
68      private static final String RENDERER_TAG = "renderer";
69      private static final String APPENDER_TAG = "appender";
70      public  static final String PARAM_TAG = "param";
71      public static final String LAYOUT_TAG = "layout";
72      private static final String CATEGORY = "category";
73      private static final String LOGGER_ELEMENT = "logger";
74      private static final String CATEGORY_FACTORY_TAG = "categoryFactory";
75      private static final String LOGGER_FACTORY_TAG = "loggerFactory";
76      public static final String NAME_ATTR = "name";
77      private static final String CLASS_ATTR = "class";
78      public static final String VALUE_ATTR = "value";
79      private static final String ROOT_TAG = "root";
80      private static final String LEVEL_TAG = "level";
81      private static final String PRIORITY_TAG = "priority";
82      public static final String FILTER_TAG = "filter";
83      private static final String ERROR_HANDLER_TAG = "errorHandler";
84      public static final String REF_ATTR = "ref";
85      private static final String ADDITIVITY_ATTR = "additivity";
86      private static final String CONFIG_DEBUG_ATTR = "configDebug";
87      private static final String INTERNAL_DEBUG_ATTR = "debug";
88      private static final String EMPTY_STR = "";
89      private static final Class<?>[] ONE_STRING_PARAM = new Class[] { String.class };
90      private static final String dbfKey = "javax.xml.parsers.DocumentBuilderFactory";
91      private static final String THROWABLE_RENDERER_TAG = "throwableRenderer";
92  
93      public static final long DEFAULT_DELAY = 60000;
94  
95      /**
96       * File name prefix for test configurations.
97       */
98      protected static final String TEST_PREFIX = "log4j-test";
99  
100     /**
101      * File name prefix for standard configurations.
102      */
103     protected static final String DEFAULT_PREFIX = "log4j";
104 
105     // key: appenderName, value: appender
106     private Map<String, Appender> appenderMap;
107 
108     private Properties props = null;
109 
110     public XmlConfiguration(final LoggerContext loggerContext, final ConfigurationSource source,
111             int monitorIntervalSeconds) {
112         super(loggerContext, source, monitorIntervalSeconds);
113         appenderMap = new HashMap<>();
114     }
115 
116     public void addAppenderIfAbsent(Appender appender) {
117         appenderMap.putIfAbsent(appender.getName(), appender);
118     }
119 
120     /**
121      * Configures log4j by reading in a log4j.dtd compliant XML
122      * configuration file.
123      */
124     @Override
125     public void doConfigure() throws FactoryConfigurationError {
126         ConfigurationSource source = getConfigurationSource();
127         ParseAction action = new ParseAction() {
128             @Override
129             public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
130                 @SuppressWarnings("resource") // The ConfigurationSource and its caller manages the InputStream.
131                 InputSource inputSource = new InputSource(source.getInputStream());
132                 inputSource.setSystemId("dummy://log4j.dtd");
133                 return parser.parse(inputSource);
134             }
135 
136             @Override
137             public String toString() {
138                 return getConfigurationSource().getLocation();
139             }
140         };
141         doConfigure(action);
142     }
143 
144     private void doConfigure(final ParseAction action) throws FactoryConfigurationError {
145         DocumentBuilderFactory dbf;
146         try {
147             LOGGER.debug("System property is : {}", OptionConverter.getSystemProperty(dbfKey, null));
148             dbf = DocumentBuilderFactory.newInstance();
149             LOGGER.debug("Standard DocumentBuilderFactory search succeded.");
150             LOGGER.debug("DocumentBuilderFactory is: " + dbf.getClass().getName());
151         } catch (FactoryConfigurationError fce) {
152             Exception e = fce.getException();
153             LOGGER.debug("Could not instantiate a DocumentBuilderFactory.", e);
154             throw fce;
155         }
156 
157         try {
158             dbf.setValidating(true);
159 
160             DocumentBuilder docBuilder = dbf.newDocumentBuilder();
161 
162             docBuilder.setErrorHandler(new SAXErrorHandler());
163             docBuilder.setEntityResolver(new Log4jEntityResolver());
164 
165             Document doc = action.parse(docBuilder);
166             parse(doc.getDocumentElement());
167         } catch (Exception e) {
168             if (e instanceof InterruptedException || e instanceof InterruptedIOException) {
169                 Thread.currentThread().interrupt();
170             }
171             // I know this is miserable...
172             LOGGER.error("Could not parse " + action.toString() + ".", e);
173         }
174     }
175 
176     @Override
177     public Configuration reconfigure() {
178         try {
179             final ConfigurationSource source = getConfigurationSource().resetInputStream();
180             if (source == null) {
181                 return null;
182             }
183             final XmlConfigurationFactory factory = new XmlConfigurationFactory();
184             final XmlConfiguration config =
185                     (XmlConfiguration) factory.getConfiguration(getLoggerContext(), source);
186             return config == null || config.getState() != State.INITIALIZING ? null : config;
187         } catch (final IOException ex) {
188             LOGGER.error("Cannot locate file {}: {}", getConfigurationSource(), ex);
189         }
190         return null;
191     }
192 
193     /**
194      * Delegates unrecognized content to created instance if it supports UnrecognizedElementParser.
195      *
196      * @param instance instance, may be null.
197      * @param element  element, may not be null.
198      * @param props    properties
199      * @throws IOException thrown if configuration of owner object should be abandoned.
200      */
201     private void parseUnrecognizedElement(final Object instance, final Element element,
202             final Properties props) throws Exception {
203         boolean recognized = false;
204         if (instance instanceof UnrecognizedElementHandler) {
205             recognized = ((UnrecognizedElementHandler) instance).parseUnrecognizedElement(
206                     element, props);
207         }
208         if (!recognized) {
209             LOGGER.warn("Unrecognized element {}", element.getNodeName());
210         }
211     }
212 
213     /**
214      * Delegates unrecognized content to created instance if
215      * it supports UnrecognizedElementParser and catches and
216      * logs any exception.
217      *
218      * @param instance instance, may be null.
219      * @param element  element, may not be null.
220      * @param props    properties
221      * @since 1.2.15
222      */
223     private void quietParseUnrecognizedElement(final Object instance,
224             final Element element,
225             final Properties props) {
226         try {
227             parseUnrecognizedElement(instance, element, props);
228         } catch (Exception ex) {
229             if (ex instanceof InterruptedException || ex instanceof InterruptedIOException) {
230                 Thread.currentThread().interrupt();
231             }
232             LOGGER.error("Error in extension content: ", ex);
233         }
234     }
235 
236     /**
237      * Substitutes property value for any references in expression.
238      *
239      * @param value value from configuration file, may contain
240      *              literal text, property references or both
241      * @param props properties.
242      * @return evaluated expression, may still contain expressions
243      * if unable to expand.
244      */
245     public String subst(final String value, final Properties props) {
246         try {
247             return OptionConverter.substVars(value, props);
248         } catch (IllegalArgumentException e) {
249             LOGGER.warn("Could not perform variable substitution.", e);
250             return value;
251         }
252     }
253 
254     /**
255      * Sets a parameter based from configuration file content.
256      *
257      * @param elem       param element, may not be null.
258      * @param propSetter property setter, may not be null.
259      * @param props      properties
260      * @since 1.2.15
261      */
262     public void setParameter(final Element elem, final PropertySetter propSetter, final Properties props) {
263         String name = subst(elem.getAttribute("name"), props);
264         String value = (elem.getAttribute("value"));
265         value = subst(OptionConverter.convertSpecialChars(value), props);
266         propSetter.setProperty(name, value);
267     }
268 
269     /**
270      * Creates an object and processes any nested param elements
271      * but does not call activateOptions.  If the class also supports
272      * UnrecognizedElementParser, the parseUnrecognizedElement method
273      * will be call for any child elements other than param.
274      *
275      * @param element       element, may not be null.
276      * @param props         properties
277      * @param expectedClass interface or class expected to be implemented
278      *                      by created class
279      * @return created class or null.
280      * @throws Exception thrown if the contain object should be abandoned.
281      * @since 1.2.15
282      */
283     public Object parseElement(final Element element, final Properties props,
284             @SuppressWarnings("rawtypes") final Class expectedClass) throws Exception {
285         String clazz = subst(element.getAttribute("class"), props);
286         Object instance = OptionConverter.instantiateByClassName(clazz,
287                 expectedClass, null);
288 
289         if (instance != null) {
290             PropertySetter propSetter = new PropertySetter(instance);
291             NodeList children = element.getChildNodes();
292             final int length = children.getLength();
293 
294             for (int loop = 0; loop < length; loop++) {
295                 Node currentNode = children.item(loop);
296                 if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
297                     Element currentElement = (Element) currentNode;
298                     String tagName = currentElement.getTagName();
299                     if (tagName.equals("param")) {
300                         setParameter(currentElement, propSetter, props);
301                     } else {
302                         parseUnrecognizedElement(instance, currentElement, props);
303                     }
304                 }
305             }
306             return instance;
307         }
308         return null;
309     }
310 
311     /**
312      * Used internally to parse appenders by IDREF name.
313      */
314     private Appender findAppenderByName(Document doc, String appenderName) {
315         Appender appender = appenderMap.get(appenderName);
316 
317         if (appender != null) {
318             return appender;
319         }
320         // Endre's hack:
321         Element element = null;
322         NodeList list = doc.getElementsByTagName("appender");
323         for (int t = 0; t < list.getLength(); t++) {
324             Node node = list.item(t);
325             NamedNodeMap map = node.getAttributes();
326             Node attrNode = map.getNamedItem("name");
327             if (appenderName.equals(attrNode.getNodeValue())) {
328                 element = (Element) node;
329                 break;
330             }
331         }
332         // Hack finished.
333 
334         if (element == null) {
335 
336             LOGGER.error("No appender named [{}] could be found.", appenderName);
337             return null;
338         }
339         appender = parseAppender(element);
340         if (appender != null) {
341             appenderMap.put(appenderName, appender);
342         }
343         return appender;
344     }
345 
346     /**
347      * Used internally to parse appenders by IDREF element.
348      */
349     public Appender findAppenderByReference(Element appenderRef) {
350         String appenderName = subst(appenderRef.getAttribute(REF_ATTR));
351         Document doc = appenderRef.getOwnerDocument();
352         return findAppenderByName(doc, appenderName);
353     }
354 
355     /**
356      * Used internally to parse an appender element.
357      */
358     public Appender parseAppender(Element appenderElement) {
359         String className = subst(appenderElement.getAttribute(CLASS_ATTR));
360         LOGGER.debug("Class name: [" + className + ']');
361         Appender appender = manager.parseAppender(className, appenderElement, this);
362         if (appender == null) {
363             appender = buildAppender(className, appenderElement);
364         }
365         return appender;
366     }
367 
368     private Appender buildAppender(String className, Element appenderElement) {
369         try {
370             Appender appender = LoaderUtil.newInstanceOf(className);
371             PropertySetter propSetter = new PropertySetter(appender);
372 
373             appender.setName(subst(appenderElement.getAttribute(NAME_ATTR)));
374             forEachElement(appenderElement.getChildNodes(), currentElement -> {
375                 // Parse appender parameters
376                 switch (currentElement.getTagName()) {
377                     case PARAM_TAG:
378                         setParameter(currentElement, propSetter);
379                         break;
380                     case LAYOUT_TAG:
381                         appender.setLayout(parseLayout(currentElement));
382                         break;
383                     case FILTER_TAG:
384                         Filter filter = parseFilters(currentElement);
385                         if (filter != null) {
386                             LOGGER.debug("Adding filter of type [{}] to appender named [{}]",
387                                     filter.getClass(), appender.getName());
388                             appender.addFilter(filter);
389                         }
390                         break;
391                     case ERROR_HANDLER_TAG:
392                         parseErrorHandler(currentElement, appender);
393                         break;
394                     case APPENDER_REF_TAG:
395                         String refName = subst(currentElement.getAttribute(REF_ATTR));
396                         if (appender instanceof AppenderAttachable) {
397                             AppenderAttachable aa = (AppenderAttachable) appender;
398                             Appender child = findAppenderByReference(currentElement);
399                             LOGGER.debug("Attaching appender named [{}] to appender named [{}].", refName,
400                                     appender.getName());
401                             aa.addAppender(child);
402                         } else {
403                             LOGGER.error("Requesting attachment of appender named [{}] to appender named [{}]"
404                                             + "which does not implement org.apache.log4j.spi.AppenderAttachable.",
405                                     refName, appender.getName());
406                         }
407                         break;
408                     default:
409                         try {
410                             parseUnrecognizedElement(appender, currentElement, props);
411                         } catch (Exception ex) {
412                             throw new ConsumerException(ex);
413                         }
414                 }
415             });
416             propSetter.activate();
417             return appender;
418         } catch (ConsumerException ex) {
419             Throwable t = ex.getCause();
420             if (t instanceof InterruptedException || t instanceof InterruptedIOException) {
421                 Thread.currentThread().interrupt();
422             }
423             LOGGER.error("Could not create an Appender. Reported error follows.", t);
424         } catch (Exception oops) {
425             if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
426                 Thread.currentThread().interrupt();
427             }
428             LOGGER.error("Could not create an Appender. Reported error follows.", oops);
429         }
430         return null;
431     }
432 
433     public RewritePolicy parseRewritePolicy(Element rewritePolicyElement) {
434         String className = subst(rewritePolicyElement.getAttribute(CLASS_ATTR));
435         LOGGER.debug("Class name: [" + className + ']');
436         RewritePolicy policy = manager.parseRewritePolicy(className, rewritePolicyElement, this);
437         if (policy == null) {
438             policy = buildRewritePolicy(className, rewritePolicyElement);
439         }
440         return policy;
441     }
442 
443     private RewritePolicy buildRewritePolicy(String className, Element element) {
444         try {
445             RewritePolicy policy = LoaderUtil.newInstanceOf(className);
446             PropertySetter propSetter = new PropertySetter(policy);
447 
448             forEachElement(element.getChildNodes(), currentElement -> {
449                 if (currentElement.getTagName().equalsIgnoreCase(PARAM_TAG)) {
450                     setParameter(currentElement, propSetter);
451                 }
452             });
453             propSetter.activate();
454             return policy;
455         } catch (ConsumerException ex) {
456             Throwable t = ex.getCause();
457             if (t instanceof InterruptedException || t instanceof InterruptedIOException) {
458                 Thread.currentThread().interrupt();
459             }
460             LOGGER.error("Could not create an RewritePolicy. Reported error follows.", t);
461         } catch (Exception oops) {
462             if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
463                 Thread.currentThread().interrupt();
464             }
465             LOGGER.error("Could not create an RewritePolicy. Reported error follows.", oops);
466         }
467         return null;
468     }
469 
470     /**
471      * Used internally to parse an {@link ErrorHandler} element.
472      */
473     private void parseErrorHandler(Element element, Appender appender) {
474         ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByClassName(
475                 subst(element.getAttribute(CLASS_ATTR)),
476                 ErrorHandler.class,
477                 null);
478 
479         if (eh != null) {
480             eh.setAppender(appender);
481 
482             PropertySetter propSetter = new PropertySetter(eh);
483             forEachElement(element.getChildNodes(), currentElement -> {
484                 String tagName = currentElement.getTagName();
485                 if (tagName.equals(PARAM_TAG)) {
486                     setParameter(currentElement, propSetter);
487                 }
488             });
489             propSetter.activate();
490             appender.setErrorHandler(eh);
491         }
492     }
493 
494     /**
495      * Used internally to parse a filter element.
496      */
497     public Filter parseFilters(Element filterElement) {
498         String className = subst(filterElement.getAttribute(CLASS_ATTR));
499         LOGGER.debug("Class name: [" + className + ']');
500         Filter filter = manager.parseFilter(className, filterElement, this);
501         if (filter == null) {
502             PropertySetter propSetter = new PropertySetter(filter);
503             forEachElement(filterElement.getChildNodes(), currentElement -> {
504                 String tagName = currentElement.getTagName();
505                 if (tagName.equals(PARAM_TAG)) {
506                     setParameter(currentElement, propSetter);
507                 } else {
508                     quietParseUnrecognizedElement(filter, currentElement, props);
509                 }
510             });
511             propSetter.activate();
512         }
513         return filter;
514     }
515 
516     /**
517      * Used internally to parse an category element.
518      */
519     private void parseCategory(Element loggerElement) {
520         // Create a new org.apache.log4j.Category object from the <category> element.
521         String catName = subst(loggerElement.getAttribute(NAME_ATTR));
522         boolean additivity = OptionConverter.toBoolean(subst(loggerElement.getAttribute(ADDITIVITY_ATTR)), true);
523         LoggerConfig loggerConfig = getLogger(catName);
524         if (loggerConfig == null) {
525             loggerConfig = new LoggerConfig(catName, org.apache.logging.log4j.Level.ERROR, additivity);
526             addLogger(catName, loggerConfig);
527         } else {
528             loggerConfig.setAdditive(additivity);
529         }
530         parseChildrenOfLoggerElement(loggerElement, loggerConfig, false);
531     }
532 
533     /**
534      * Used internally to parse the roor category element.
535      */
536     private void parseRoot(Element rootElement) {
537         LoggerConfig root = getRootLogger();
538         parseChildrenOfLoggerElement(rootElement, root, true);
539     }
540 
541     /**
542      * Used internally to parse the children of a LoggerConfig element.
543      */
544     private void parseChildrenOfLoggerElement(Element catElement, LoggerConfig loggerConfig, boolean isRoot) {
545 
546         final PropertySetter propSetter = new PropertySetter(loggerConfig);
547         loggerConfig.getAppenderRefs().clear();
548         forEachElement(catElement.getChildNodes(), currentElement -> {
549             switch (currentElement.getTagName()) {
550                 case APPENDER_REF_TAG: {
551                     Appender appender = findAppenderByReference(currentElement);
552                     String refName = subst(currentElement.getAttribute(REF_ATTR));
553                     if (appender != null) {
554                         LOGGER.debug("Adding appender named [{}] to loggerConfig [{}].", refName,
555                                 loggerConfig.getName());
556                         loggerConfig.addAppender(getAppender(refName), null, null);
557                     } else {
558                         LOGGER.debug("Appender named [{}] not found.", refName);
559                     }
560                     break;
561                 }
562                 case LEVEL_TAG: case PRIORITY_TAG: {
563                     parseLevel(currentElement, loggerConfig, isRoot);
564                     break;
565                 }
566                 case PARAM_TAG: {
567                     setParameter(currentElement, propSetter);
568                     break;
569                 }
570                 default: {
571                     quietParseUnrecognizedElement(loggerConfig, currentElement, props);
572                 }
573             }
574         });
575         propSetter.activate();
576     }
577 
578     /**
579      * Used internally to parse a layout element.
580      */
581     public Layout parseLayout(Element layoutElement) {
582         String className = subst(layoutElement.getAttribute(CLASS_ATTR));
583         LOGGER.debug("Parsing layout of class: \"{}\"", className);
584         Layout layout = manager.parseLayout(className, layoutElement, this);
585         if (layout == null) {
586             layout = buildLayout(className, layoutElement);
587         }
588         return layout;
589     }
590 
591     private Layout buildLayout(String className, Element layout_element) {
592         try {
593             Layout layout = LoaderUtil.newInstanceOf(className);
594             PropertySetter propSetter = new PropertySetter(layout);
595             forEachElement(layout_element.getChildNodes(), currentElement -> {
596                 String tagName = currentElement.getTagName();
597                 if (tagName.equals(PARAM_TAG)) {
598                     setParameter(currentElement, propSetter);
599                 } else {
600                     try {
601                         parseUnrecognizedElement(layout, currentElement, props);
602                     } catch (Exception ex) {
603                         throw new ConsumerException(ex);
604                     }
605                 }
606             });
607 
608             propSetter.activate();
609             return layout;
610         } catch (ConsumerException ce) {
611             Throwable cause = ce.getCause();
612             if (cause instanceof InterruptedException || cause instanceof InterruptedIOException) {
613                 Thread.currentThread().interrupt();
614             }
615             LOGGER.error("Could not create the Layout. Reported error follows.", cause);
616         } catch (Exception oops) {
617             if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
618                 Thread.currentThread().interrupt();
619             }
620             LOGGER.error("Could not create the Layout. Reported error follows.", oops);
621         }
622         return null;
623     }
624 
625     /**
626      * Used internally to parse a level  element.
627      */
628     private void parseLevel(Element element, LoggerConfig logger, boolean isRoot) {
629         String catName = logger.getName();
630         if (isRoot) {
631             catName = "root";
632         }
633 
634         String priStr = subst(element.getAttribute(VALUE_ATTR));
635         LOGGER.debug("Level value for {} is [{}].", catName, priStr);
636 
637         if (INHERITED.equalsIgnoreCase(priStr) || NULL.equalsIgnoreCase(priStr)) {
638             if (isRoot) {
639                 LOGGER.error("Root level cannot be inherited. Ignoring directive.");
640             } else {
641                 logger.setLevel(null);
642             }
643         } else {
644             String className = subst(element.getAttribute(CLASS_ATTR));
645             if (EMPTY_STR.equals(className)) {
646                 logger.setLevel(OptionConverter.convertLevel(priStr, org.apache.logging.log4j.Level.DEBUG));
647             } else {
648                 LOGGER.debug("Desired Level sub-class: [{}]", className);
649                 try {
650                     Class<?> clazz = LoaderUtil.loadClass(className);
651                     Method toLevelMethod = clazz.getMethod("toLevel", ONE_STRING_PARAM);
652                     Level pri = (Level) toLevelMethod.invoke(null, priStr);
653                     logger.setLevel(OptionConverter.convertLevel(pri));
654                 } catch (Exception e) {
655                     if (e instanceof InterruptedException || e instanceof InterruptedIOException) {
656                         Thread.currentThread().interrupt();
657                     }
658                     LOGGER.error("Could not create level [" + priStr +
659                             "]. Reported error follows.", e);
660                     return;
661                 }
662             }
663         }
664         LOGGER.debug("{} level set to {}", catName,  logger.getLevel());
665     }
666 
667     private void setParameter(Element elem, PropertySetter propSetter) {
668         String name = subst(elem.getAttribute(NAME_ATTR));
669         String value = (elem.getAttribute(VALUE_ATTR));
670         value = subst(OptionConverter.convertSpecialChars(value));
671         propSetter.setProperty(name, value);
672     }
673 
674     /**
675      * Used internally to configure the log4j framework by parsing a DOM
676      * tree of XML elements based on <a
677      * href="doc-files/log4j.dtd">log4j.dtd</a>.
678      */
679     private void parse(Element element) {
680         String rootElementName = element.getTagName();
681 
682         if (!rootElementName.equals(CONFIGURATION_TAG)) {
683             if (rootElementName.equals(OLD_CONFIGURATION_TAG)) {
684                 LOGGER.warn("The <" + OLD_CONFIGURATION_TAG +
685                         "> element has been deprecated.");
686                 LOGGER.warn("Use the <" + CONFIGURATION_TAG + "> element instead.");
687             } else {
688                 LOGGER.error("DOM element is - not a <" + CONFIGURATION_TAG + "> element.");
689                 return;
690             }
691         }
692 
693 
694         String debugAttrib = subst(element.getAttribute(INTERNAL_DEBUG_ATTR));
695 
696         LOGGER.debug("debug attribute= \"" + debugAttrib + "\".");
697         // if the log4j.dtd is not specified in the XML file, then the
698         // "debug" attribute is returned as the empty string.
699         String status = "error";
700         if (!debugAttrib.equals("") && !debugAttrib.equals("null")) {
701             status = OptionConverter.toBoolean(debugAttrib, true) ? "debug" : "error";
702 
703         } else {
704             LOGGER.debug("Ignoring " + INTERNAL_DEBUG_ATTR + " attribute.");
705         }
706 
707         String confDebug = subst(element.getAttribute(CONFIG_DEBUG_ATTR));
708         if (!confDebug.equals("") && !confDebug.equals("null")) {
709             LOGGER.warn("The \"" + CONFIG_DEBUG_ATTR + "\" attribute is deprecated.");
710             LOGGER.warn("Use the \"" + INTERNAL_DEBUG_ATTR + "\" attribute instead.");
711             status = OptionConverter.toBoolean(confDebug, true) ? "debug" : "error";
712         }
713 
714         final StatusConfiguration statusConfig = new StatusConfiguration().withStatus(status);
715         statusConfig.initialize();
716 
717         forEachElement(element.getChildNodes(), currentElement -> {
718             switch (currentElement.getTagName()) {
719                 case CATEGORY: 
720                 case LOGGER_ELEMENT:
721                     parseCategory(currentElement);
722                     break;
723                 case ROOT_TAG:
724                     parseRoot(currentElement);
725                     break;
726                 case RENDERER_TAG:
727                     LOGGER.warn("Renderers are not supported by Log4j 2 and will be ignored.");
728                     break;
729                 case THROWABLE_RENDERER_TAG:
730                     LOGGER.warn("Throwable Renderers are not supported by Log4j 2 and will be ignored.");
731                     break;
732                 case CATEGORY_FACTORY_TAG: 
733                 case LOGGER_FACTORY_TAG:
734                     LOGGER.warn("Log4j 1 Logger factories are not supported by Log4j 2 and will be ignored.");
735                     break;
736                 case APPENDER_TAG:
737                     Appender appender = parseAppender(currentElement);
738                     appenderMap.put(appender.getName(), appender);
739                     if (appender instanceof AppenderWrapper) {
740                         addAppender(((AppenderWrapper) appender).getAppender());
741                     } else {
742                         addAppender(new AppenderAdapter(appender).getAdapter());
743                     }
744                     break;
745                 default:
746                     quietParseUnrecognizedElement(null, currentElement, props);
747             }
748         });
749     }
750 
751     private String subst(final String value) {
752         return getStrSubstitutor().replace(value);
753     }
754 
755     public static void forEachElement(NodeList list, Consumer<Element> consumer) {
756         final int length = list.getLength();
757         for (int loop = 0; loop < length; loop++) {
758             Node currentNode = list.item(loop);
759 
760             if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
761                 Element currentElement = (Element) currentNode;
762                 consumer.accept(currentElement);
763             }
764         }
765     }
766 
767     private interface ParseAction {
768         Document parse(final DocumentBuilder parser) throws SAXException, IOException;
769     }
770 
771     private static class SAXErrorHandler implements org.xml.sax.ErrorHandler {
772         private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger();
773 
774         @Override
775         public void error(final SAXParseException ex) {
776             emitMessage("Continuable parsing error ", ex);
777         }
778 
779         @Override
780         public void fatalError(final SAXParseException ex) {
781             emitMessage("Fatal parsing error ", ex);
782         }
783 
784         @Override
785         public void warning(final SAXParseException ex) {
786             emitMessage("Parsing warning ", ex);
787         }
788 
789         private static void emitMessage(final String msg, final SAXParseException ex) {
790             LOGGER.warn("{} {} and column {}", msg, ex.getLineNumber(), ex.getColumnNumber());
791             LOGGER.warn(ex.getMessage(), ex.getException());
792         }
793     }
794 
795     private static class ConsumerException extends RuntimeException {
796 
797         ConsumerException(Exception ex) {
798             super(ex);
799         }
800     }
801 }