org.apache.log4j.xml.DOMConfigurator Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.log4j.xml;
import org.apache.log4j.Appender;
import org.apache.log4j.Layout;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.config.PropertySetter;
import org.apache.log4j.helpers.FileWatchdog;
import org.apache.log4j.helpers.Loader;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.helpers.OptionConverter;
import org.apache.log4j.or.RendererMap;
import org.apache.log4j.spi.AppenderAttachable;
import org.apache.log4j.spi.Configurator;
import org.apache.log4j.spi.ErrorHandler;
import org.apache.log4j.spi.Filter;
import org.apache.log4j.spi.LoggerFactory;
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.spi.RendererSupport;
import org.apache.log4j.spi.ThrowableRenderer;
import org.apache.log4j.spi.ThrowableRendererSupport;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.Reader;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Hashtable;
import java.util.Properties;
// Contributors: Mark Womack
// Arun Katkere
/**
* Use this class to initialize the log4j environment using a DOM tree.
*
*
* The DTD is specified in log4j.dtd.
*
*
* Sometimes it is useful to see how log4j is reading configuration files. You
* can enable log4j internal logging by defining the log4j.debug variable
* on the java command line. Alternatively, set the debug
attribute
* in the log4j:configuration
element. As in
*
*
<log4j:configuration debug="true" xmlns:log4j="http://jakarta.apache.org/log4j/">
...
</log4j:configuration>
*
*
*
* There are sample XML files included in the package.
*
* @author Christopher Taylor
* @author Ceki Gülcü
* @author Anders Kristensen
*
* @since 0.8.3
*/
public class DOMConfigurator implements Configurator {
static final String CONFIGURATION_TAG = "log4j:configuration";
static final String OLD_CONFIGURATION_TAG = "configuration";
static final String RENDERER_TAG = "renderer";
private static final String THROWABLE_RENDERER_TAG = "throwableRenderer";
static final String APPENDER_TAG = "appender";
static final String APPENDER_REF_TAG = "appender-ref";
static final String PARAM_TAG = "param";
static final String LAYOUT_TAG = "layout";
static final String CATEGORY = "category";
static final String LOGGER = "logger";
static final String LOGGER_REF = "logger-ref";
static final String CATEGORY_FACTORY_TAG = "categoryFactory";
static final String LOGGER_FACTORY_TAG = "loggerFactory";
static final String NAME_ATTR = "name";
static final String CLASS_ATTR = "class";
static final String VALUE_ATTR = "value";
static final String ROOT_TAG = "root";
static final String ROOT_REF = "root-ref";
static final String LEVEL_TAG = "level";
static final String PRIORITY_TAG = "priority";
static final String FILTER_TAG = "filter";
static final String ERROR_HANDLER_TAG = "errorHandler";
static final String REF_ATTR = "ref";
static final String ADDITIVITY_ATTR = "additivity";
static final String THRESHOLD_ATTR = "threshold";
static final String CONFIG_DEBUG_ATTR = "configDebug";
static final String INTERNAL_DEBUG_ATTR = "debug";
private static final String RESET_ATTR = "reset";
static final String RENDERING_CLASS_ATTR = "renderingClass";
static final String RENDERED_CLASS_ATTR = "renderedClass";
static final String EMPTY_STR = "";
static final Class[] ONE_STRING_PARAM = new Class[] { String.class };
final static String dbfKey = "javax.xml.parsers.DocumentBuilderFactory";
// key: appenderName, value: appender
Hashtable appenderBag;
Properties props;
LoggerRepository repository;
protected LoggerFactory catFactory = null;
/**
* No argument constructor.
*/
public DOMConfigurator() {
appenderBag = new Hashtable();
}
/**
* Used internally to parse appenders by IDREF name.
*/
protected Appender findAppenderByName(Document doc, String appenderName) {
Appender appender = (Appender) appenderBag.get(appenderName);
if (appender != null) {
return appender;
} else {
// Doesn't work on DOM Level 1 :
// Element element = doc.getElementById(appenderName);
// Endre's hack:
Element element = null;
NodeList list = doc.getElementsByTagName("appender");
for (int t = 0; t < list.getLength(); t++) {
Node node = list.item(t);
NamedNodeMap map = node.getAttributes();
Node attrNode = map.getNamedItem("name");
if (appenderName.equals(attrNode.getNodeValue())) {
element = (Element) node;
break;
}
}
// Hack finished.
if (element == null) {
LogLog.error("No appender named [" + appenderName + "] could be found.");
return null;
} else {
appender = parseAppender(element);
if (appender != null) {
appenderBag.put(appenderName, appender);
}
return appender;
}
}
}
/**
* Used internally to parse appenders by IDREF element.
*/
protected Appender findAppenderByReference(Element appenderRef) {
String appenderName = subst(appenderRef.getAttribute(REF_ATTR));
Document doc = appenderRef.getOwnerDocument();
return findAppenderByName(doc, appenderName);
}
/**
* Delegates unrecognized content to created instance if it supports
* UnrecognizedElementParser.
*
* @since 1.2.15
* @param instance instance, may be null.
* @param element element, may not be null.
* @param props properties
* @throws IOException thrown if configuration of owner object should be
* abandoned.
*/
private static void parseUnrecognizedElement(final Object instance, final Element element, final Properties props)
throws Exception {
boolean recognized = false;
if (instance instanceof UnrecognizedElementHandler) {
recognized = ((UnrecognizedElementHandler) instance).parseUnrecognizedElement(element, props);
}
if (!recognized) {
LogLog.warn("Unrecognized element " + element.getNodeName());
}
}
/**
* Delegates unrecognized content to created instance if it supports
* UnrecognizedElementParser and catches and logs any exception.
*
* @since 1.2.15
* @param instance instance, may be null.
* @param element element, may not be null.
* @param props properties
*/
private static void quietParseUnrecognizedElement(final Object instance, final Element element,
final Properties props) {
try {
parseUnrecognizedElement(instance, element, props);
} catch (Exception ex) {
if (ex instanceof InterruptedException || ex instanceof InterruptedIOException) {
Thread.currentThread().interrupt();
}
LogLog.error("Error in extension content: ", ex);
}
}
/**
* Used internally to parse an appender element.
*/
protected Appender parseAppender(Element appenderElement) {
String className = subst(appenderElement.getAttribute(CLASS_ATTR));
LogLog.debug("Class name: [" + className + ']');
try {
Object instance = Loader.loadClass(className).newInstance();
Appender appender = (Appender) instance;
PropertySetter propSetter = new PropertySetter(appender);
appender.setName(subst(appenderElement.getAttribute(NAME_ATTR)));
NodeList children = appenderElement.getChildNodes();
final int length = children.getLength();
for (int loop = 0; loop < length; loop++) {
Node currentNode = children.item(loop);
/* We're only interested in Elements */
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
Element currentElement = (Element) currentNode;
// Parse appender parameters
if (currentElement.getTagName().equals(PARAM_TAG)) {
setParameter(currentElement, propSetter);
}
// Set appender layout
else if (currentElement.getTagName().equals(LAYOUT_TAG)) {
appender.setLayout(parseLayout(currentElement));
}
// Add filters
else if (currentElement.getTagName().equals(FILTER_TAG)) {
parseFilters(currentElement, appender);
} else if (currentElement.getTagName().equals(ERROR_HANDLER_TAG)) {
parseErrorHandler(currentElement, appender);
} else if (currentElement.getTagName().equals(APPENDER_REF_TAG)) {
String refName = subst(currentElement.getAttribute(REF_ATTR));
if (appender instanceof AppenderAttachable) {
AppenderAttachable aa = (AppenderAttachable) appender;
LogLog.debug("Attaching appender named [" + refName + "] to appender named ["
+ appender.getName() + "].");
aa.addAppender(findAppenderByReference(currentElement));
} else {
LogLog.error("Requesting attachment of appender named [" + refName + "] to appender named ["
+ appender.getName()
+ "] which does not implement org.apache.log4j.spi.AppenderAttachable.");
}
} else {
parseUnrecognizedElement(instance, currentElement, props);
}
}
}
propSetter.activate();
return appender;
}
/*
* Yes, it's ugly. But all of these exceptions point to the same problem: we
* can't create an Appender
*/
catch (Exception oops) {
if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
Thread.currentThread().interrupt();
}
LogLog.error("Could not create an Appender. Reported error follows.", oops);
return null;
}
}
/**
* Used internally to parse an {@link ErrorHandler} element.
*/
protected void parseErrorHandler(Element element, Appender appender) {
ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByClassName(subst(element.getAttribute(CLASS_ATTR)),
org.apache.log4j.spi.ErrorHandler.class, null);
if (eh != null) {
eh.setAppender(appender);
PropertySetter propSetter = new PropertySetter(eh);
NodeList children = element.getChildNodes();
final int length = children.getLength();
for (int loop = 0; loop < length; loop++) {
Node currentNode = children.item(loop);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
Element currentElement = (Element) currentNode;
String tagName = currentElement.getTagName();
if (tagName.equals(PARAM_TAG)) {
setParameter(currentElement, propSetter);
} else if (tagName.equals(APPENDER_REF_TAG)) {
eh.setBackupAppender(findAppenderByReference(currentElement));
} else if (tagName.equals(LOGGER_REF)) {
String loggerName = currentElement.getAttribute(REF_ATTR);
Logger logger = (catFactory == null) ? repository.getLogger(loggerName)
: repository.getLogger(loggerName, catFactory);
eh.setLogger(logger);
} else if (tagName.equals(ROOT_REF)) {
Logger root = repository.getRootLogger();
eh.setLogger(root);
} else {
quietParseUnrecognizedElement(eh, currentElement, props);
}
}
}
propSetter.activate();
appender.setErrorHandler(eh);
}
}
/**
* Used internally to parse a filter element.
*/
protected void parseFilters(Element element, Appender appender) {
String clazz = subst(element.getAttribute(CLASS_ATTR));
Filter filter = (Filter) OptionConverter.instantiateByClassName(clazz, Filter.class, null);
if (filter != null) {
PropertySetter propSetter = new PropertySetter(filter);
NodeList children = element.getChildNodes();
final int length = children.getLength();
for (int loop = 0; loop < length; loop++) {
Node currentNode = children.item(loop);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
Element currentElement = (Element) currentNode;
String tagName = currentElement.getTagName();
if (tagName.equals(PARAM_TAG)) {
setParameter(currentElement, propSetter);
} else {
quietParseUnrecognizedElement(filter, currentElement, props);
}
}
}
propSetter.activate();
LogLog.debug("Adding filter of type [" + filter.getClass() + "] to appender named [" + appender.getName()
+ "].");
appender.addFilter(filter);
}
}
/**
* Used internally to parse an category element.
*/
protected void parseCategory(Element loggerElement) {
// Create a new org.apache.log4j.Category object from the element.
String catName = subst(loggerElement.getAttribute(NAME_ATTR));
Logger cat;
String className = subst(loggerElement.getAttribute(CLASS_ATTR));
if (EMPTY_STR.equals(className)) {
LogLog.debug("Retreiving an instance of org.apache.log4j.Logger.");
cat = (catFactory == null) ? repository.getLogger(catName) : repository.getLogger(catName, catFactory);
} else {
LogLog.debug("Desired logger sub-class: [" + className + ']');
try {
Class clazz = Loader.loadClass(className);
Method getInstanceMethod = clazz.getMethod("getLogger", ONE_STRING_PARAM);
cat = (Logger) getInstanceMethod.invoke(null, new Object[] { catName });
} catch (InvocationTargetException oops) {
if (oops.getTargetException() instanceof InterruptedException
|| oops.getTargetException() instanceof InterruptedIOException) {
Thread.currentThread().interrupt();
}
LogLog.error("Could not retrieve category [" + catName + "]. Reported error follows.", oops);
return;
} catch (Exception oops) {
LogLog.error("Could not retrieve category [" + catName + "]. Reported error follows.", oops);
return;
}
}
// Setting up a category needs to be an atomic operation, in order
// to protect potential log operations while category
// configuration is in progress.
synchronized (cat) {
boolean additivity = OptionConverter.toBoolean(subst(loggerElement.getAttribute(ADDITIVITY_ATTR)), true);
LogLog.debug("Setting [" + cat.getName() + "] additivity to [" + additivity + "].");
cat.setAdditivity(additivity);
parseChildrenOfLoggerElement(loggerElement, cat, false);
}
}
/**
* Used internally to parse the category factory element.
*/
protected void parseCategoryFactory(Element factoryElement) {
String className = subst(factoryElement.getAttribute(CLASS_ATTR));
if (EMPTY_STR.equals(className)) {
LogLog.error("Category Factory tag " + CLASS_ATTR + " attribute not found.");
LogLog.debug("No Category Factory configured.");
} else {
LogLog.debug("Desired category factory: [" + className + ']');
Object factory = OptionConverter.instantiateByClassName(className, LoggerFactory.class, null);
if (factory instanceof LoggerFactory) {
catFactory = (LoggerFactory) factory;
} else {
LogLog.error(
"Category Factory class " + className + " does not implement org.apache.log4j.LoggerFactory");
}
PropertySetter propSetter = new PropertySetter(factory);
Element currentElement = null;
Node currentNode = null;
NodeList children = factoryElement.getChildNodes();
final int length = children.getLength();
for (int loop = 0; loop < length; loop++) {
currentNode = children.item(loop);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
currentElement = (Element) currentNode;
if (currentElement.getTagName().equals(PARAM_TAG)) {
setParameter(currentElement, propSetter);
} else {
quietParseUnrecognizedElement(factory, currentElement, props);
}
}
}
}
}
/**
* Used internally to parse the roor category element.
*/
protected void parseRoot(Element rootElement) {
Logger root = repository.getRootLogger();
// category configuration needs to be atomic
synchronized (root) {
parseChildrenOfLoggerElement(rootElement, root, true);
}
}
/**
* Used internally to parse the children of a category element.
*/
protected void parseChildrenOfLoggerElement(Element catElement, Logger cat, boolean isRoot) {
PropertySetter propSetter = new PropertySetter(cat);
// Remove all existing appenders from cat. They will be
// reconstructed if need be.
cat.removeAllAppenders();
NodeList children = catElement.getChildNodes();
final int length = children.getLength();
for (int loop = 0; loop < length; loop++) {
Node currentNode = children.item(loop);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
Element currentElement = (Element) currentNode;
String tagName = currentElement.getTagName();
if (tagName.equals(APPENDER_REF_TAG)) {
Element appenderRef = (Element) currentNode;
Appender appender = findAppenderByReference(appenderRef);
String refName = subst(appenderRef.getAttribute(REF_ATTR));
if (appender != null)
LogLog.debug("Adding appender named [" + refName + "] to category [" + cat.getName() + "].");
else
LogLog.debug("Appender named [" + refName + "] not found.");
cat.addAppender(appender);
} else if (tagName.equals(LEVEL_TAG)) {
parseLevel(currentElement, cat, isRoot);
} else if (tagName.equals(PRIORITY_TAG)) {
parseLevel(currentElement, cat, isRoot);
} else if (tagName.equals(PARAM_TAG)) {
setParameter(currentElement, propSetter);
} else {
quietParseUnrecognizedElement(cat, currentElement, props);
}
}
}
propSetter.activate();
}
/**
* Used internally to parse a layout element.
*/
protected Layout parseLayout(Element layout_element) {
String className = subst(layout_element.getAttribute(CLASS_ATTR));
LogLog.debug("Parsing layout of class: \"" + className + "\"");
try {
Object instance = Loader.loadClass(className).newInstance();
Layout layout = (Layout) instance;
PropertySetter propSetter = new PropertySetter(layout);
NodeList params = layout_element.getChildNodes();
final int length = params.getLength();
for (int loop = 0; loop < length; loop++) {
Node currentNode = (Node) params.item(loop);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
Element currentElement = (Element) currentNode;
String tagName = currentElement.getTagName();
if (tagName.equals(PARAM_TAG)) {
setParameter(currentElement, propSetter);
} else {
parseUnrecognizedElement(instance, currentElement, props);
}
}
}
propSetter.activate();
return layout;
} catch (Exception oops) {
if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
Thread.currentThread().interrupt();
}
LogLog.error("Could not create the Layout. Reported error follows.", oops);
return null;
}
}
protected void parseRenderer(Element element) {
String renderingClass = subst(element.getAttribute(RENDERING_CLASS_ATTR));
String renderedClass = subst(element.getAttribute(RENDERED_CLASS_ATTR));
if (repository instanceof RendererSupport) {
RendererMap.addRenderer((RendererSupport) repository, renderedClass, renderingClass);
}
}
/**
* Parses throwable renderer.
*
* @param element throwableRenderer element.
* @return configured throwable renderer.
* @since 1.2.16.
*/
protected ThrowableRenderer parseThrowableRenderer(final Element element) {
String className = subst(element.getAttribute(CLASS_ATTR));
LogLog.debug("Parsing throwableRenderer of class: \"" + className + "\"");
try {
Object instance = Loader.loadClass(className).newInstance();
ThrowableRenderer tr = (ThrowableRenderer) instance;
PropertySetter propSetter = new PropertySetter(tr);
NodeList params = element.getChildNodes();
final int length = params.getLength();
for (int loop = 0; loop < length; loop++) {
Node currentNode = (Node) params.item(loop);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
Element currentElement = (Element) currentNode;
String tagName = currentElement.getTagName();
if (tagName.equals(PARAM_TAG)) {
setParameter(currentElement, propSetter);
} else {
parseUnrecognizedElement(instance, currentElement, props);
}
}
}
propSetter.activate();
return tr;
} catch (Exception oops) {
if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
Thread.currentThread().interrupt();
}
LogLog.error("Could not create the ThrowableRenderer. Reported error follows.", oops);
return null;
}
}
/**
* Used internally to parse a level element.
*/
protected void parseLevel(Element element, Logger logger, boolean isRoot) {
String catName = logger.getName();
if (isRoot) {
catName = "root";
}
String priStr = subst(element.getAttribute(VALUE_ATTR));
LogLog.debug("Level value for " + catName + " is [" + priStr + "].");
if (INHERITED.equalsIgnoreCase(priStr) || NULL.equalsIgnoreCase(priStr)) {
if (isRoot) {
LogLog.error("Root level cannot be inherited. Ignoring directive.");
} else {
logger.setLevel(null);
}
} else {
String className = subst(element.getAttribute(CLASS_ATTR));
if (EMPTY_STR.equals(className)) {
logger.setLevel(OptionConverter.toLevel(priStr, Level.DEBUG));
} else {
LogLog.debug("Desired Level sub-class: [" + className + ']');
try {
Class clazz = Loader.loadClass(className);
Method toLevelMethod = clazz.getMethod("toLevel", ONE_STRING_PARAM);
Level pri = (Level) toLevelMethod.invoke(null, new Object[] { priStr });
logger.setLevel(pri);
} catch (Exception oops) {
if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
Thread.currentThread().interrupt();
}
LogLog.error("Could not create level [" + priStr + "]. Reported error follows.", oops);
return;
}
}
}
LogLog.debug(catName + " level set to " + logger.getLevel());
}
protected void setParameter(Element elem, PropertySetter propSetter) {
String name = subst(elem.getAttribute(NAME_ATTR));
String value = (elem.getAttribute(VALUE_ATTR));
value = subst(OptionConverter.convertSpecialChars(value));
propSetter.setProperty(name, value);
}
/**
* Configure log4j using a configuration
element as defined in the
* log4j.dtd.
*
*/
static public void configure(Element element) {
DOMConfigurator configurator = new DOMConfigurator();
configurator.doConfigure(element, LogManager.getLoggerRepository());
}
/**
* Like {@link #configureAndWatch(String, long)} except that the default delay
* as defined by {@link FileWatchdog#DEFAULT_DELAY} is used.
*
* @param configFilename A log4j configuration file in XML format.
*
*/
static public void configureAndWatch(String configFilename) {
configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY);
}
/**
* Read the configuration file configFilename
if it exists.
* Moreover, a thread will be created that will periodically check if
* configFilename
has been created or modified. The period is
* determined by the delay
argument. If a change or file creation
* is detected, then configFilename
is read to configure log4j.
*
* @param configFilename A log4j configuration file in XML format.
* @param delay The delay in milliseconds to wait between each check.
*/
static public void configureAndWatch(String configFilename, long delay) {
XMLWatchdog xdog = new XMLWatchdog(configFilename);
xdog.setDelay(delay);
xdog.start();
}
private interface ParseAction {
Document parse(final DocumentBuilder parser) throws SAXException, IOException;
}
public void doConfigure(final String filename, LoggerRepository repository) {
ParseAction action = new ParseAction() {
public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
return parser.parse(new File(filename));
}
public String toString() {
return "file [" + filename + "]";
}
};
doConfigure(action, repository);
}
public void doConfigure(final URL url, LoggerRepository repository) {
ParseAction action = new ParseAction() {
public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
URLConnection uConn = url.openConnection();
uConn.setUseCaches(false);
InputStream stream = uConn.getInputStream();
try {
InputSource src = new InputSource(stream);
src.setSystemId(url.toString());
return parser.parse(src);
} finally {
stream.close();
}
}
public String toString() {
return "url [" + url.toString() + "]";
}
};
doConfigure(action, repository);
}
/**
* Configure log4j by reading in a log4j.dtd compliant XML configuration file.
*
*/
public void doConfigure(final InputStream inputStream, LoggerRepository repository)
throws FactoryConfigurationError {
ParseAction action = new ParseAction() {
public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
InputSource inputSource = new InputSource(inputStream);
inputSource.setSystemId("dummy://log4j.dtd");
return parser.parse(inputSource);
}
public String toString() {
return "input stream [" + inputStream.toString() + "]";
}
};
doConfigure(action, repository);
}
/**
* Configure log4j by reading in a log4j.dtd compliant XML configuration file.
*
*/
public void doConfigure(final Reader reader, LoggerRepository repository) throws FactoryConfigurationError {
ParseAction action = new ParseAction() {
public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
InputSource inputSource = new InputSource(reader);
inputSource.setSystemId("dummy://log4j.dtd");
return parser.parse(inputSource);
}
public String toString() {
return "reader [" + reader.toString() + "]";
}
};
doConfigure(action, repository);
}
/**
* Configure log4j by reading in a log4j.dtd compliant XML configuration file.
*
*/
protected void doConfigure(final InputSource inputSource, LoggerRepository repository)
throws FactoryConfigurationError {
if (inputSource.getSystemId() == null) {
inputSource.setSystemId("dummy://log4j.dtd");
}
ParseAction action = new ParseAction() {
public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
return parser.parse(inputSource);
}
public String toString() {
return "input source [" + inputSource.toString() + "]";
}
};
doConfigure(action, repository);
}
private final void doConfigure(final ParseAction action, final LoggerRepository repository)
throws FactoryConfigurationError {
DocumentBuilderFactory dbf = null;
this.repository = repository;
try {
LogLog.debug("System property is :" + OptionConverter.getSystemProperty(dbfKey, null));
dbf = DocumentBuilderFactory.newInstance();
LogLog.debug("Standard DocumentBuilderFactory search succeded.");
LogLog.debug("DocumentBuilderFactory is: " + dbf.getClass().getName());
} catch (FactoryConfigurationError fce) {
Exception e = fce.getException();
LogLog.debug("Could not instantiate a DocumentBuilderFactory.", e);
throw fce;
}
try {
dbf.setValidating(true);
// prevent XXE attacks
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
DocumentBuilder docBuilder = dbf.newDocumentBuilder();
docBuilder.setErrorHandler(new SAXErrorHandler());
docBuilder.setEntityResolver(new Log4jEntityResolver());
Document doc = action.parse(docBuilder);
parse(doc.getDocumentElement());
} catch (Exception e) {
if (e instanceof InterruptedException || e instanceof InterruptedIOException) {
Thread.currentThread().interrupt();
}
LogLog.error("Could not parse " + action.toString() + ".", e);
} catch (AbstractMethodError e) {
LogLog.error("Failed to parse XML file. Missing DocumentBuilderFactory.setFeature() method?", e);
throw e;
}
}
/**
* Configure by taking in an DOM element.
*/
public void doConfigure(Element element, LoggerRepository repository) {
this.repository = repository;
parse(element);
}
/**
* A static version of {@link #doConfigure(String, LoggerRepository)}.
*/
static public void configure(String filename) throws FactoryConfigurationError {
new DOMConfigurator().doConfigure(filename, LogManager.getLoggerRepository());
}
/**
* A static version of {@link #doConfigure(URL, LoggerRepository)}.
*/
static public void configure(URL url) throws FactoryConfigurationError {
new DOMConfigurator().doConfigure(url, LogManager.getLoggerRepository());
}
/**
* Used internally to configure the log4j framework by parsing a DOM tree of XML
* elements based on log4j.dtd.
*
*/
protected void parse(Element element) {
String rootElementName = element.getTagName();
if (!rootElementName.equals(CONFIGURATION_TAG)) {
if (rootElementName.equals(OLD_CONFIGURATION_TAG)) {
LogLog.warn("The <" + OLD_CONFIGURATION_TAG + "> element has been deprecated.");
LogLog.warn("Use the <" + CONFIGURATION_TAG + "> element instead.");
} else {
LogLog.error("DOM element is - not a <" + CONFIGURATION_TAG + "> element.");
return;
}
}
String debugAttrib = subst(element.getAttribute(INTERNAL_DEBUG_ATTR));
LogLog.debug("debug attribute= \"" + debugAttrib + "\".");
// if the log4j.dtd is not specified in the XML file, then the
// "debug" attribute is returned as the empty string.
if (!debugAttrib.equals("") && !debugAttrib.equals("null")) {
LogLog.setInternalDebugging(OptionConverter.toBoolean(debugAttrib, true));
} else {
LogLog.debug("Ignoring " + INTERNAL_DEBUG_ATTR + " attribute.");
}
//
// reset repository before configuration if reset="true"
// on configuration element.
//
String resetAttrib = subst(element.getAttribute(RESET_ATTR));
LogLog.debug("reset attribute= \"" + resetAttrib + "\".");
if (!("".equals(resetAttrib))) {
if (OptionConverter.toBoolean(resetAttrib, false)) {
repository.resetConfiguration();
}
}
String confDebug = subst(element.getAttribute(CONFIG_DEBUG_ATTR));
if (!confDebug.equals("") && !confDebug.equals("null")) {
LogLog.warn("The \"" + CONFIG_DEBUG_ATTR + "\" attribute is deprecated.");
LogLog.warn("Use the \"" + INTERNAL_DEBUG_ATTR + "\" attribute instead.");
LogLog.setInternalDebugging(OptionConverter.toBoolean(confDebug, true));
}
String thresholdStr = subst(element.getAttribute(THRESHOLD_ATTR));
LogLog.debug("Threshold =\"" + thresholdStr + "\".");
if (!"".equals(thresholdStr) && !"null".equals(thresholdStr)) {
repository.setThreshold(thresholdStr);
}
// Hashtable appenderBag = new Hashtable(11);
/*
* Building Appender objects, placing them in a local namespace for future
* reference
*/
// First configure each category factory under the root element.
// Category factories need to be configured before any of
// categories they support.
//
String tagName = null;
Element currentElement = null;
Node currentNode = null;
NodeList children = element.getChildNodes();
final int length = children.getLength();
for (int loop = 0; loop < length; loop++) {
currentNode = children.item(loop);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
currentElement = (Element) currentNode;
tagName = currentElement.getTagName();
if (tagName.equals(CATEGORY_FACTORY_TAG) || tagName.equals(LOGGER_FACTORY_TAG)) {
parseCategoryFactory(currentElement);
}
}
}
for (int loop = 0; loop < length; loop++) {
currentNode = children.item(loop);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
currentElement = (Element) currentNode;
tagName = currentElement.getTagName();
if (tagName.equals(CATEGORY) || tagName.equals(LOGGER)) {
parseCategory(currentElement);
} else if (tagName.equals(ROOT_TAG)) {
parseRoot(currentElement);
} else if (tagName.equals(RENDERER_TAG)) {
parseRenderer(currentElement);
} else if (tagName.equals(THROWABLE_RENDERER_TAG)) {
if (repository instanceof ThrowableRendererSupport) {
ThrowableRenderer tr = parseThrowableRenderer(currentElement);
if (tr != null) {
((ThrowableRendererSupport) repository).setThrowableRenderer(tr);
}
}
} else if (!(tagName.equals(APPENDER_TAG) || tagName.equals(CATEGORY_FACTORY_TAG)
|| tagName.equals(LOGGER_FACTORY_TAG))) {
quietParseUnrecognizedElement(repository, currentElement, props);
}
}
}
}
protected String subst(final String value) {
return subst(value, props);
}
/**
* Substitutes property value for any references in expression.
*
* @param value value from configuration file, may contain literal text,
* property references or both
* @param props properties.
* @return evaluated expression, may still contain expressions if unable to
* expand.
* @since 1.2.15
*/
public static String subst(final String value, final Properties props) {
try {
return OptionConverter.substVars(value, props);
} catch (IllegalArgumentException e) {
LogLog.warn("Could not perform variable substitution.", e);
return value;
}
}
/**
* Sets a parameter based from configuration file content.
*
* @param elem param element, may not be null.
* @param propSetter property setter, may not be null.
* @param props properties
* @since 1.2.15
*/
public static void setParameter(final Element elem, final PropertySetter propSetter, final Properties props) {
String name = subst(elem.getAttribute("name"), props);
String value = (elem.getAttribute("value"));
value = subst(OptionConverter.convertSpecialChars(value), props);
propSetter.setProperty(name, value);
}
/**
* Creates an object and processes any nested param elements but does not call
* activateOptions. If the class also supports UnrecognizedElementParser, the
* parseUnrecognizedElement method will be call for any child elements other
* than param.
*
* @param element element, may not be null.
* @param props properties
* @param expectedClass interface or class expected to be implemented by created
* class
* @return created class or null.
* @throws Exception thrown if the contain object should be abandoned.
* @since 1.2.15
*/
public static Object parseElement(final Element element, final Properties props, final Class expectedClass)
throws Exception {
String clazz = subst(element.getAttribute("class"), props);
Object instance = OptionConverter.instantiateByClassName(clazz, expectedClass, null);
if (instance != null) {
PropertySetter propSetter = new PropertySetter(instance);
NodeList children = element.getChildNodes();
final int length = children.getLength();
for (int loop = 0; loop < length; loop++) {
Node currentNode = children.item(loop);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
Element currentElement = (Element) currentNode;
String tagName = currentElement.getTagName();
if (tagName.equals("param")) {
setParameter(currentElement, propSetter, props);
} else {
parseUnrecognizedElement(instance, currentElement, props);
}
}
}
return instance;
}
return null;
}
}
class XMLWatchdog extends FileWatchdog {
XMLWatchdog(String filename) {
super(filename);
}
/**
* Call {@link DOMConfigurator#configure(String)} with the filename
* to reconfigure log4j.
*/
public void doOnChange() {
new DOMConfigurator().doConfigure(filename, LogManager.getLoggerRepository());
}
}