diff options
| author | Keith Wall <kwall@apache.org> | 2012-07-05 09:40:06 +0000 |
|---|---|---|
| committer | Keith Wall <kwall@apache.org> | 2012-07-05 09:40:06 +0000 |
| commit | 8b555d057f483874d9384c15bd989c975d18e0b0 (patch) | |
| tree | b5fdf00f4a10c465da038df03d6cdeb357501881 /qpid/java/broker | |
| parent | 179f46270e539569e7c57e763ccd8a49ccf09a84 (diff) | |
| download | qpid-python-8b555d057f483874d9384c15bd989c975d18e0b0.tar.gz | |
QPID-4109: Re-enable LoggingManagement MBean
* Re-wire up LoggingManagementMBean.
* Centralise log4j specific functionality into LoggingFacade class (moving implementation from LoggingManagementMBean and QpidLog4JConfigurator
together).
* Implement unit-tests for MBean and Facade levels. Reenforce units tests with system tests testing logging management end to end.
* Changed QpidBrokerTestCase so that log4j.configuration is _always_ used to obtain the log4j config file regardless of whether test type
is spawned or internal (previously log4j.configuration was respected only for internal tests). This was require to be able to
write a logging management system test that could safely change the contents of the log4j config without running the risk of
effecting other tests.
git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk@1357528 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'qpid/java/broker')
7 files changed, 916 insertions, 250 deletions
diff --git a/qpid/java/broker/src/main/java/org/apache/log4j/xml/QpidLog4JConfigurator.java b/qpid/java/broker/src/main/java/org/apache/log4j/xml/QpidLog4JConfigurator.java deleted file mode 100644 index 79bedb2a7e..0000000000 --- a/qpid/java/broker/src/main/java/org/apache/log4j/xml/QpidLog4JConfigurator.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * - * 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.Level; -import org.apache.log4j.Logger; -import org.xml.sax.ErrorHandler; -import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import java.io.File; -import java.io.IOException; -import java.util.Map; -import java.util.concurrent.locks.ReentrantLock; - -/** - * Substitute for the Log4J XMLWatchdog (as used by DOMConfigurator.configureAndWatch) - * - * Extends the default behaviour with a strict parser check on the XML file before allowing the reconfiguration to proceed, - * ensuring that any parser error or warning prevents initiation of a configuration update by Log4J, which aborts mid-update - * upon fatal errors from the parser and proceeds in the event of 'regular' parser errors and warnings, in all cases allowing - * startup to proceed with whatever half-baked configuration then exists. - */ -public class QpidLog4JConfigurator -{ - //lock to protect access to the configuration file - //shared with LoggingManagementMBean - public static final ReentrantLock LOCK = new ReentrantLock(); - private static Logger _logger; - private static DOMConfigurator domConfig = new DOMConfigurator(); - - private QpidLog4JConfigurator() - { - //no instances - } - - public static void configure(String filename) throws IOException, ParserConfigurationException, - SAXException, IllegalLoggerLevelException - { - try - { - LOCK.lock(); - - parseXMLConfigFile(filename); - - DOMConfigurator.configure(filename); - - if(_logger == null) - { - _logger = Logger.getLogger(QpidLog4JConfigurator.class); - } - } - finally - { - LOCK.unlock(); - } - } - - public static void configureAndWatch(String filename, long delay) throws IOException, ParserConfigurationException, - SAXException, IllegalLoggerLevelException - { - parseXMLConfigFile(filename); - - QpidLog4JXMLWatchdog watchdog = new QpidLog4JXMLWatchdog(filename); - watchdog.setDelay(delay); - watchdog.start(); - } - - private static void parseXMLConfigFile(String fileName) throws IOException, SAXException, - ParserConfigurationException - { - try - { - LOCK.lock(); - - //check file was specified, exists, and is readable - if(fileName == null) - { - throw new IOException("Provided log4j XML configuration filename was null"); - } - - File configFile = new File(fileName); - - if (!configFile.exists()) - { - throw new IOException("The log4j XML configuration file does not exist: " + fileName); - } - else if (!configFile.canRead()) - { - throw new IOException("The log4j XML configuration file is not readable: " + fileName); - } - - //parse it - DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder docBuilder; - - ErrorHandler errHandler = new QpidLog4JSaxErrorHandler(); - - docFactory.setValidating(true); - docBuilder = docFactory.newDocumentBuilder(); - docBuilder.setErrorHandler(errHandler); - docBuilder.setEntityResolver(new Log4jEntityResolver()); - docBuilder.parse(fileName); - } - finally - { - LOCK.unlock(); - } - } - - public static class QpidLog4JSaxErrorHandler implements ErrorHandler - { - public void error(SAXParseException e) throws SAXException - { - if(_logger != null) - { - _logger.warn(constructMessage("Error parsing XML file", e)); - } - else - { - System.err.println(constructMessage("Error parsing XML file", e)); - } - } - - public void fatalError(SAXParseException e) throws SAXException - { - throw new SAXException(constructMessage("Fatal error parsing XML file", e)); - } - - public void warning(SAXParseException e) throws SAXException - { - if(_logger != null) - { - _logger.warn(constructMessage("Warning parsing XML file", e)); - } - else - { - System.err.println(constructMessage("Warning parsing XML file", e)); - } - } - - private static String constructMessage(final String msg, final SAXParseException ex) - { - return msg + ": Line " + ex.getLineNumber()+" column " +ex.getColumnNumber() + ": " + ex.getMessage(); - } - } - - private static class QpidLog4JXMLWatchdog extends XMLWatchdog - { - public QpidLog4JXMLWatchdog(String filename) - { - super(filename); - } - - public void doOnChange() - { - try - { - LOCK.lock(); - - try - { - parseXMLConfigFile(filename); - } - catch (Exception e) - { - //logger will be instantiated following first configuration success, which has been pre-validated - //and so the null check should never actually be required. - if(_logger != null) - { - _logger.warn("Parsing the log4j XML configuration file generated errors/warnings. " + - "The new configuration was not applied. Correct the issues to prompt " + - "another update attempt: " + e.getMessage()); - } - return; - } - - //everything checked was ok, let the normal update process proceed - super.doOnChange(); - - //a configuration has now been applied, enable logging for future attempts - if(_logger == null) - { - _logger = Logger.getLogger(QpidLog4JConfigurator.class); - } - - _logger.info("Applied log4j configuration from: " + filename); - } - finally - { - LOCK.unlock(); - } - - } - } - - private static void checkLevel(String loggerName, String levelString) throws IllegalLoggerLevelException - { - if("null".equalsIgnoreCase(levelString) || "inherited".equalsIgnoreCase(levelString)) - { - //the string "null" signals to inherit from a parent logger - return; - } - - Level level = Level.toLevel(levelString); - - //above Level.toLevel call returns a DEBUG Level if the request fails. Check the result. - if (level.equals(Level.DEBUG) && !(levelString.equalsIgnoreCase("debug"))) - { - //received DEBUG but we did not ask for it, the Level request failed. - throw new IllegalLoggerLevelException("Level '" + levelString + "' specified for Logger '" + loggerName + "' is invalid"); - } - } - - public static class IllegalLoggerLevelException extends Exception - { - private static final long serialVersionUID = 1L; - - public IllegalLoggerLevelException(String msg) - { - super(msg); - } - } -} - diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/Broker.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/Broker.java index c843ce6a23..d58a0d5bb4 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/Broker.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/Broker.java @@ -29,13 +29,13 @@ import java.util.*; import javax.net.ssl.SSLContext; import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; -import org.apache.log4j.xml.QpidLog4JConfigurator; import org.apache.qpid.server.configuration.ServerConfiguration; import org.apache.qpid.server.configuration.ServerNetworkTransportConfiguration; import org.apache.qpid.server.logging.SystemOutMessageLogger; import org.apache.qpid.server.logging.actors.BrokerActor; import org.apache.qpid.server.logging.actors.CurrentActor; import org.apache.qpid.server.logging.actors.GenericActor; +import org.apache.qpid.server.logging.log4j.LoggingFacade; import org.apache.qpid.server.logging.messages.BrokerMessages; import org.apache.qpid.server.protocol.AmqpProtocolVersion; import org.apache.qpid.server.protocol.MultiVersionProtocolEngineFactory; @@ -430,7 +430,7 @@ public class Broker } } - private void configureLogging(File logConfigFile, long logWatchTime) throws InitException, IOException + private void configureLogging(File logConfigFile, int logWatchTime) throws InitException, IOException { if (logConfigFile.exists() && logConfigFile.canRead()) { @@ -443,7 +443,7 @@ public class Broker // log4j expects the watch interval in milliseconds try { - QpidLog4JConfigurator.configureAndWatch(logConfigFile.getPath(), logWatchTime * 1000); + LoggingFacade.configureAndWatch(logConfigFile.getPath(), logWatchTime * 1000); } catch (Exception e) { @@ -454,7 +454,7 @@ public class Broker { try { - QpidLog4JConfigurator.configure(logConfigFile.getPath()); + LoggingFacade.configure(logConfigFile.getPath()); } catch (Exception e) { diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/log4j/LoggingFacade.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/log4j/LoggingFacade.java new file mode 100644 index 0000000000..931171b175 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/log4j/LoggingFacade.java @@ -0,0 +1,579 @@ +/* + * + * 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.qpid.server.logging.log4j; + +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.xml.DOMConfigurator; +import org.apache.log4j.xml.Log4jEntityResolver; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +/** + * A facade over log4j that allows both the control of the runtime logging behaviour (that is, the ability to + * turn {@link Logger} on, off and control their {@link Level}, and the manipulation and reload + * of the log4j configuration file. + */ +public class LoggingFacade +{ + private static Logger LOGGER; + private static transient LoggingFacade _instance; + private final String _filename; + private final int _delay; + + public static LoggingFacade configure(String filename) throws LoggingFacadeException + { + _instance = new LoggingFacade(filename); + return _instance; + } + + public static LoggingFacade configureAndWatch(String filename, int delay) throws LoggingFacadeException + { + _instance = new LoggingFacade(filename, delay); + return _instance; + } + + public static LoggingFacade getCurrentInstance() + { + return _instance; + } + + private LoggingFacade(String filename) + { + DOMConfigurator.configure(filename); + + if(LOGGER == null) + { + LOGGER = Logger.getLogger(LoggingFacade.class); + } + _filename = filename; + _delay = 0; + } + + private LoggingFacade(String filename, int delay) + { + DOMConfigurator.configureAndWatch(filename, delay); + + if(LOGGER == null) + { + LOGGER = Logger.getLogger(LoggingFacade.class); + } + + _filename = filename; + _delay = delay; + } + + public int getLog4jLogWatchInterval() + { + return _delay; + } + + public synchronized void reload() throws LoggingFacadeException + { + DOMConfigurator.configure(_filename); + } + + /** The log4j XML configuration file DTD defines three possible element + * combinations for specifying optional logger+level settings. + * Must account for the following: + * + * <category name="x"> <priority value="y"/> </category> OR + * <category name="x"> <level value="y"/> </category> OR + * <logger name="x"> <level value="y"/> </logger> + * + * Noting also that the level/priority child element is optional too, + * and not the only possible child element. + */ + public synchronized Map<String,String> retrieveConfigFileLoggersLevels() throws LoggingFacadeException + { + try + { + Map<String,String> loggerLevelList = new HashMap<String,String>(); + LOGGER.info("Getting logger levels from log4j configuration file"); + + Document doc = parseConfigFile(_filename); + List<Element> categoryOrLoggerElements = buildListOfCategoryOrLoggerElements(doc); + + for (Element categoryOrLogger : categoryOrLoggerElements) + { + + Element priorityOrLevelElement; + try + { + priorityOrLevelElement = getPriorityOrLevelElement(categoryOrLogger); + } + catch (LoggingFacadeException lfe) + { + //there is no exiting priority or level to view, move onto next category/logger + continue; + } + + String categoryName = categoryOrLogger.getAttribute("name"); + String priorityOrLevelValue = priorityOrLevelElement.getAttribute("value"); + loggerLevelList.put(categoryName, priorityOrLevelValue); + } + + return loggerLevelList; + } + catch (IOException e) + { + throw new LoggingFacadeException(e); + } + } + + /** + * The log4j XML configuration file DTD defines 2 possible element + * combinations for specifying the optional root logger level settings + * Must account for the following: + * + * <root> <priority value="y"/> </root> OR + * <root> <level value="y"/> </root> + * + * Noting also that the level/priority child element is optional too, + * and not the only possible child element. + */ + public synchronized String retrieveConfigFileRootLoggerLevel() throws LoggingFacadeException + { + try + { + Document doc = parseConfigFile(_filename); + + //retrieve the optional 'root' element node + NodeList rootElements = doc.getElementsByTagName("root"); + + if (rootElements.getLength() == 0) + { + //there is no root logger definition + return "N/A"; + } + + Element rootElement = (Element) rootElements.item(0); + Element levelElement = getPriorityOrLevelElement(rootElement); + + if(levelElement != null) + { + return levelElement.getAttribute("value"); + } + else + { + return "N/A"; + } + } + catch (IOException e) + { + throw new LoggingFacadeException(e); + } + } + + public synchronized void setConfigFileLoggerLevel(String logger, String level) throws LoggingFacadeException + { + LOGGER.info("Setting level to " + level + " for logger '" + logger + + "' in log4j xml configuration file: " + _filename); + + try + { + Document doc = parseConfigFile(_filename); + + List<Element> logElements = buildListOfCategoryOrLoggerElements(doc); + + //try to locate the specified logger/category in the elements retrieved + Element logElement = null; + for (Element e : logElements) + { + if (e.getAttribute("name").equals(logger)) + { + logElement = e; + break; + } + } + + if (logElement == null) + { + throw new LoggingFacadeException("Can't find logger " + logger); + } + + Element levelElement = getPriorityOrLevelElement(logElement); + + //update the element with the new level/priority + levelElement.setAttribute("value", level); + + //output the new file + writeUpdatedConfigFile(_filename, doc); + } + catch (IOException ioe) + { + throw new LoggingFacadeException(ioe); + } + catch (TransformerConfigurationException e) + { + throw new LoggingFacadeException(e); + } + } + + public synchronized void setConfigFileRootLoggerLevel(String level) throws LoggingFacadeException + { + try + { + LOGGER.info("Setting level to " + level + " for the Root logger in " + + "log4j xml configuration file: " + _filename); + + Document doc = parseConfigFile(_filename); + + //retrieve the optional 'root' element node + NodeList rootElements = doc.getElementsByTagName("root"); + + if (rootElements.getLength() == 0) + { + throw new LoggingFacadeException("Configuration contains no root element"); + } + + Element rootElement = (Element) rootElements.item(0); + Element levelElement = getPriorityOrLevelElement(rootElement); + + //update the element with the new level/priority + levelElement.setAttribute("value", level); + + //output the new file + writeUpdatedConfigFile(_filename, doc); + } + catch (IOException e) + { + throw new LoggingFacadeException(e); + } + catch (TransformerConfigurationException e) + { + throw new LoggingFacadeException(e); + } + } + + public List<String> getAvailableLoggerLevels() + { + return new ArrayList<String>() + {{ + add(Level.ALL.toString()); + add(Level.TRACE.toString()); + add(Level.DEBUG.toString()); + add(Level.INFO.toString()); + add(Level.WARN.toString()); + add(Level.ERROR.toString()); + add(Level.FATAL.toString()); + add(Level.OFF.toString()); + }}; + } + + public String retrieveRuntimeRootLoggerLevel() + { + Logger rootLogger = Logger.getRootLogger(); + return rootLogger.getLevel().toString(); + } + + public void setRuntimeRootLoggerLevel(String level) + { + Level newLevel = Level.toLevel(level); + + LOGGER.info("Setting RootLogger level to " + level); + + Logger log = Logger.getRootLogger(); + log.setLevel(newLevel); + } + + public void setRuntimeLoggerLevel(String loggerName, String level) throws LoggingFacadeException + { + Level newLevel = level == null ? null : Level.toLevel(level); + + Logger targetLogger = findRuntimeLogger(loggerName); + + if(targetLogger == null) + { + throw new LoggingFacadeException("Can't find logger " + loggerName); + } + + LOGGER.info("Setting level to " + newLevel + " for logger '" + targetLogger.getName() + "'"); + + targetLogger.setLevel(newLevel); + } + + public Map<String,String> retrieveRuntimeLoggersLevels() + { + LOGGER.info("Getting levels for currently active log4j loggers"); + + Map<String, String> levels = new HashMap<String, String>(); + @SuppressWarnings("unchecked") + Enumeration<Logger> loggers = LogManager.getCurrentLoggers(); + + while (loggers.hasMoreElements()) + { + Logger logger = loggers.nextElement(); + levels.put(logger.getName(), logger.getEffectiveLevel().toString()); + } + + return levels; + } + + private void writeUpdatedConfigFile(String log4jConfigFileName, Document doc) throws IOException, TransformerConfigurationException + { + File log4jConfigFile = new File(log4jConfigFileName); + + if (!log4jConfigFile.canWrite()) + { + LOGGER.warn("Specified log4j XML configuration file is not writable: " + log4jConfigFile); + throw new IOException("Specified log4j XML configuration file is not writable"); + } + + Transformer transformer = null; + transformer = TransformerFactory.newInstance().newTransformer(); + + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "log4j.dtd"); + DOMSource source = new DOMSource(doc); + + File tmp; + Random r = new Random(); + + do + { + tmp = new File(log4jConfigFile.getAbsolutePath() + r.nextInt() + ".tmp"); + } + while(tmp.exists()); + + tmp.deleteOnExit(); + + try + { + StreamResult result = new StreamResult(new FileOutputStream(tmp)); + transformer.transform(source, result); + } + catch (TransformerException e) + { + LOGGER.warn("Could not transform the XML into new file: ", e); + throw new IOException("Could not transform the XML into new file: ", e); + } + + // Swap temp file in to replace existing configuration file. + File old = new File(log4jConfigFile.getAbsoluteFile() + ".old"); + if (old.exists()) + { + old.delete(); + } + + if(!log4jConfigFile.renameTo(old)) + { + //unable to rename the existing file to the backup name + LOGGER.error("Could not backup the existing log4j XML file"); + throw new IOException("Could not backup the existing log4j XML file"); + } + + if(!tmp.renameTo(log4jConfigFile)) + { + //failed to rename the new file to the required filename + + if(!old.renameTo(log4jConfigFile)) + { + //unable to return the backup to required filename + LOGGER.error("Could not rename the new log4j configuration file into place, and unable to restore original file"); + throw new IOException("Could not rename the new log4j configuration file into place, and unable to restore original file"); + } + + LOGGER.error("Could not rename the new log4j configuration file into place"); + throw new IOException("Could not rename the new log4j configuration file into place"); + } + } + + //method to parse the XML configuration file, validating it in the process, and returning a DOM Document of the content. + private static Document parseConfigFile(String fileName) throws IOException + { + //check file was specified, exists, and is readable + if(fileName == null) + { + LOGGER.warn("Provided log4j XML configuration filename is null"); + throw new IOException("Provided log4j XML configuration filename is null"); + } + + File configFile = new File(fileName); + + if (!configFile.exists()) + { + LOGGER.warn("The log4j XML configuration file could not be found: " + fileName); + throw new IOException("The log4j XML configuration file could not be found"); + } + else if (!configFile.canRead()) + { + LOGGER.warn("The log4j XML configuration file is not readable: " + fileName); + throw new IOException("The log4j XML configuration file is not readable"); + } + + //parse it + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder; + Document doc; + + ErrorHandler errHandler = new QpidLog4JSaxErrorHandler(); + try + { + docFactory.setValidating(true); + docBuilder = docFactory.newDocumentBuilder(); + docBuilder.setErrorHandler(errHandler); + docBuilder.setEntityResolver(new Log4jEntityResolver()); + doc = docBuilder.parse(fileName); + } + catch (ParserConfigurationException e) + { + LOGGER.warn("Unable to parse the log4j XML file due to possible configuration error: ", e); + throw new IOException("Unable to parse the log4j XML file due to possible configuration error: ", e); + } + catch (SAXException e) + { + LOGGER.warn("The specified log4j XML file is invalid: ", e); + throw new IOException("The specified log4j XML file is invalid: ", e); + } + catch (IOException e) + { + LOGGER.warn("Unable to parse the specified log4j XML file", e); + throw new IOException("Unable to parse the specified log4j XML file: ", e); + } + + return doc; + } + + private Logger findRuntimeLogger(String loggerName) + { + Logger targetLogger = null; + @SuppressWarnings("unchecked") + Enumeration<Logger> loggers = LogManager.getCurrentLoggers(); + while(loggers.hasMoreElements()) + { + targetLogger = loggers.nextElement(); + if (targetLogger.getName().equals(loggerName)) + { + return targetLogger; + } + } + return null; + } + + private List<Element> buildListOfCategoryOrLoggerElements(Document doc) + { + //retrieve the 'category' and 'logger' element nodes + NodeList categoryElements = doc.getElementsByTagName("category"); + NodeList loggerElements = doc.getElementsByTagName("logger"); + + //collect them into a single elements list + List<Element> logElements = new ArrayList<Element>(); + + for (int i = 0; i < categoryElements.getLength(); i++) + { + logElements.add((Element) categoryElements.item(i)); + } + for (int i = 0; i < loggerElements.getLength(); i++) + { + logElements.add((Element) loggerElements.item(i)); + } + return logElements; + } + + private Element getPriorityOrLevelElement(Element categoryOrLogger) throws LoggingFacadeException + { + //retrieve the optional 'priority' or 'level' sub-element value. + //It may not be the only child node, so request by tag name. + NodeList priorityElements = categoryOrLogger.getElementsByTagName("priority"); + NodeList levelElements = categoryOrLogger.getElementsByTagName("level"); + + Element levelElement = null; + if (priorityElements.getLength() != 0) + { + levelElement = (Element) priorityElements.item(0); + } + else if (levelElements.getLength() != 0) + { + levelElement = (Element) levelElements.item(0); + } + else + { + throw new LoggingFacadeException("Configuration " + categoryOrLogger.getNodeName() + + " element contains neither priority nor level child"); + } + return levelElement; + } + + private static class QpidLog4JSaxErrorHandler implements ErrorHandler + { + public void error(SAXParseException e) throws SAXException + { + if(LOGGER != null) + { + LOGGER.warn(constructMessage("Error parsing XML file", e)); + } + else + { + System.err.println(constructMessage("Error parsing XML file", e)); + } + } + + public void fatalError(SAXParseException e) throws SAXException + { + throw new SAXException(constructMessage("Fatal error parsing XML file", e)); + } + + public void warning(SAXParseException e) throws SAXException + { + if(LOGGER != null) + { + LOGGER.warn(constructMessage("Warning parsing XML file", e)); + } + else + { + System.err.println(constructMessage("Warning parsing XML file", e)); + } + } + + private static String constructMessage(final String msg, final SAXParseException ex) + { + return msg + ": Line " + ex.getLineNumber()+" column " +ex.getColumnNumber() + ": " + ex.getMessage(); + } + } +} + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/log4j/LoggingFacadeException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/log4j/LoggingFacadeException.java new file mode 100644 index 0000000000..468b06be34 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/log4j/LoggingFacadeException.java @@ -0,0 +1,45 @@ +/* + * 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.qpid.server.logging.log4j; + +public class LoggingFacadeException extends Exception +{ + + public LoggingFacadeException() + { + super(); + } + + public LoggingFacadeException(String message, Throwable cause) + { + super(message, cause); + } + + public LoggingFacadeException(String message) + { + super(message); + } + + public LoggingFacadeException(Throwable cause) + { + super(cause); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/OsgiSystemPackages.properties b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/OsgiSystemPackages.properties index 4677d4b9e6..48c33821ee 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/OsgiSystemPackages.properties +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/OsgiSystemPackages.properties @@ -105,6 +105,7 @@ org.apache.qpid.server.configuration.management=0.0.0 org.apache.qpid.server.connection=0.0.0 org.apache.qpid.server.exchange=0.0.0 org.apache.qpid.server.logging=0.0.0 +org.apache.qpid.server.logging.log4j=0.0.0 org.apache.qpid.server.logging.actors=0.0.0 org.apache.qpid.server.logging.messages=0.0.0 org.apache.qpid.server.logging.subjects=0.0.0 @@ -127,3 +128,4 @@ org.apache.qpid.server.virtualhost.plugins=0.0.0 org.apache.qpid.util=0.0.0 org.apache.qpid.server.store.berkeleydb=0.0.0 + diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/log4j/LoggingFacadeTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/log4j/LoggingFacadeTest.java new file mode 100644 index 0000000000..f871baffe6 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/log4j/LoggingFacadeTest.java @@ -0,0 +1,245 @@ +/* + * 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.qpid.server.logging.log4j; + +import java.io.File; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.Level; +import org.apache.qpid.util.FileUtils; + +import junit.framework.TestCase; + +public class LoggingFacadeTest extends TestCase +{ + private LoggingFacade _loggingFacade; + private String _log4jXmlFile; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + _log4jXmlFile = createTestLog4jXml(); + _loggingFacade = LoggingFacade.configure(_log4jXmlFile); + } + + public void testGetAvailableLoggerLevels() throws Exception + { + List<String> levels = _loggingFacade.getAvailableLoggerLevels(); + assertTrue(levels.contains("ALL")); + assertTrue(levels.contains("TRACE")); + assertTrue(levels.contains("DEBUG")); + assertTrue(levels.contains("INFO")); + assertTrue(levels.contains("WARN")); + assertTrue(levels.contains("ERROR")); + assertTrue(levels.contains("FATAL")); + assertTrue(levels.contains("OFF")); + assertEquals(8, levels.size()); + } + + public void testRetrieveConfigFileRootLoggerLevel() throws Exception + { + String level = _loggingFacade.retrieveConfigFileRootLoggerLevel(); + assertEquals(Level.WARN.toString(), level); + } + + public void testSetConfigFileRootLoggerLevel() throws Exception + { + String oldLevel = _loggingFacade.retrieveConfigFileRootLoggerLevel(); + assertEquals("WARN", oldLevel); + + _loggingFacade.setConfigFileRootLoggerLevel("INFO"); + + String level = _loggingFacade.retrieveConfigFileRootLoggerLevel(); + assertEquals("INFO", level); + } + + public void testRetrieveConfigFileLoggerLevels() throws Exception + { + Map<String, String> levels = _loggingFacade.retrieveConfigFileLoggersLevels(); + assertEquals(3, levels.size()); + String abcLevel = levels.get("a.b.c"); + String abc1Level = levels.get("a.b.c.1"); + String abc2Level = levels.get("a.b.c.2"); + assertEquals("INFO", abcLevel); + assertEquals("DEBUG", abc1Level); + assertEquals("TRACE", abc2Level); + } + + public void testSetConfigFileLoggerLevels() throws Exception + { + final String loggerName = "a.b.c"; + + assertConfigFileLoggingLevel(loggerName, "INFO"); + + _loggingFacade.setConfigFileLoggerLevel(loggerName, "WARN"); + + Map<String, String> levels = _loggingFacade.retrieveConfigFileLoggersLevels(); + String abcLevel = levels.get(loggerName); + assertEquals("WARN", abcLevel); + } + + public void testSetConfigFileLoggerLevelsWhereLoggerDoesNotExist() throws Exception + { + try + { + _loggingFacade.setConfigFileLoggerLevel("does.not.exist", "WARN"); + fail("Exception not thrown"); + } + catch (LoggingFacadeException lfe) + { + // PASS + assertEquals("Can't find logger does.not.exist", lfe.getMessage()); + } + } + + public void testRetrieveRuntimeRootLoggerLevel() throws Exception + { + String level = _loggingFacade.retrieveRuntimeRootLoggerLevel(); + assertEquals(Level.WARN.toString(), level); + } + + public void testSetRuntimeRootLoggerLevel() throws Exception + { + String oldLevel = _loggingFacade.retrieveRuntimeRootLoggerLevel(); + assertEquals("WARN", oldLevel); + + _loggingFacade.setRuntimeRootLoggerLevel("INFO"); + + String level = _loggingFacade.retrieveRuntimeRootLoggerLevel(); + assertEquals("INFO", level); + } + + public void testRetrieveRuntimeLoggersLevels() throws Exception + { + Map<String, String> levels = _loggingFacade.retrieveRuntimeLoggersLevels(); + // Don't assert size as implementation itself uses logging and we'd count its loggers too + String abcLevel = levels.get("a.b.c"); + String abc1Level = levels.get("a.b.c.1"); + String abc2Level = levels.get("a.b.c.2"); + assertEquals("INFO", abcLevel); + assertEquals("DEBUG", abc1Level); + assertEquals("TRACE", abc2Level); + } + + public void testSetRuntimeLoggerLevel() throws Exception + { + final String loggerName = "a.b.c"; + + assertRuntimeLoggingLevel(loggerName, "INFO"); + + _loggingFacade.setRuntimeLoggerLevel(loggerName, "WARN"); + + assertRuntimeLoggingLevel(loggerName, "WARN"); + } + + public void testSetRuntimeLoggerToInheritFromParent() throws Exception + { + final String parentLoggerName = "a.b.c"; + final String childLoggerName = "a.b.c.1"; + + assertRuntimeLoggingLevel(parentLoggerName, "INFO"); + assertRuntimeLoggingLevel(childLoggerName, "DEBUG"); + + _loggingFacade.setRuntimeLoggerLevel(childLoggerName, null); + + assertRuntimeLoggingLevel(parentLoggerName, "INFO"); + assertRuntimeLoggingLevel(childLoggerName, "INFO"); + } + + public void testSetRuntimeLoggerLevelsWhereLoggerDoesNotExist() throws Exception + { + final String loggerName = "does.not.exist2"; + + Map<String, String> oldLevels = _loggingFacade.retrieveRuntimeLoggersLevels(); + assertFalse(oldLevels.containsKey(loggerName)); + + try + { + _loggingFacade.setRuntimeLoggerLevel(loggerName, "WARN"); + fail("Exception not thrown"); + } + catch (LoggingFacadeException lfe) + { + // PASS + assertEquals("Can't find logger " + loggerName, lfe.getMessage()); + } + + Map<String, String> levels = _loggingFacade.retrieveRuntimeLoggersLevels(); + assertFalse(levels.containsKey(loggerName)); + } + + public void testReloadOfChangedLog4JFileUpdatesRuntimeLogLevel() throws Exception + { + final String loggerName = "a.b.c"; + + assertRuntimeLoggingLevel(loggerName, "INFO"); + assertConfigFileLoggingLevel(loggerName, "INFO"); + + _loggingFacade.setConfigFileLoggerLevel(loggerName, "WARN"); + + assertRuntimeLoggingLevel(loggerName, "INFO"); + + _loggingFacade.reload(); + + assertRuntimeLoggingLevel(loggerName, "WARN"); + } + + + public void testReloadOfLog4JFileRevertsRuntimeChanges() throws Exception + { + final String loggerName = "a.b.c"; + + assertRuntimeLoggingLevel(loggerName, "INFO"); + assertConfigFileLoggingLevel(loggerName, "INFO"); + + _loggingFacade.setRuntimeLoggerLevel(loggerName, "WARN"); + + assertRuntimeLoggingLevel(loggerName, "WARN"); + + _loggingFacade.reload(); + + assertRuntimeLoggingLevel(loggerName, "INFO"); + } + + private void assertConfigFileLoggingLevel(final String loggerName, String expectedLevel) throws Exception + { + Map<String, String> levels = _loggingFacade.retrieveConfigFileLoggersLevels(); + String actualLevel = levels.get(loggerName); + assertEquals(expectedLevel, actualLevel); + } + + private void assertRuntimeLoggingLevel(final String loggerName, String expectedLevel) throws Exception + { + Map<String, String> levels = _loggingFacade.retrieveRuntimeLoggersLevels(); + String actualLevel = levels.get(loggerName); + assertEquals(expectedLevel, actualLevel); + } + + private String createTestLog4jXml() throws Exception + { + File dst = File.createTempFile("log4j." + getName(), "xml"); + File filename = new File(getClass().getResource("LoggingFacadeTest.log4j.xml").toURI()); + FileUtils.copy(filename, dst); + dst.deleteOnExit(); + return dst.getAbsolutePath(); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/log4j/LoggingFacadeTest.log4j.xml b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/log4j/LoggingFacadeTest.log4j.xml new file mode 100644 index 0000000000..62ec877d3d --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/log4j/LoggingFacadeTest.log4j.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + - + - 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. + - + --><!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> + +<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="null" threshold="null"> + + <category additivity="true" name="a.b.c"> + <priority value="INFO"/> + </category> + + <logger additivity="true" name="a.b.c.1"> + <level value="DEBUG"/> + </logger> + + <logger additivity="true" name="a.b.c.2"> + <level value="TRACE"/> + </logger> + + <root> + <priority value="WARN"/> + </root> + +</log4j:configuration> |
