You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by re...@apache.org on 2009/06/01 13:27:52 UTC
svn commit: r780620 - in /cocoon/cocoon3/trunk:
cocoon-docs/src/docbkx/reference/
cocoon-monitoring/src/main/java/org/apache/cocoon/monitoring/
cocoon-sample/rcl-config/WEB-INF/
Author: reinhard
Date: Mon Jun 1 11:27:51 2009
New Revision: 780620
URL: http://svn.apache.org/viewvc?rev=780620&view=rev
Log:
COCOON3-33
Support loading new configuration files via JMX operations
Documentation
Modified:
cocoon/cocoon3/trunk/cocoon-docs/src/docbkx/reference/web-applications.xml
cocoon/cocoon3/trunk/cocoon-monitoring/src/main/java/org/apache/cocoon/monitoring/Log4JReconfigurator.java
cocoon/cocoon3/trunk/cocoon-monitoring/src/main/java/org/apache/cocoon/monitoring/LoggingConfigurationResetter.java
cocoon/cocoon3/trunk/cocoon-sample/rcl-config/WEB-INF/log4j.xml
Modified: cocoon/cocoon3/trunk/cocoon-docs/src/docbkx/reference/web-applications.xml
URL: http://svn.apache.org/viewvc/cocoon/cocoon3/trunk/cocoon-docs/src/docbkx/reference/web-applications.xml?rev=780620&r1=780619&r2=780620&view=diff
==============================================================================
--- cocoon/cocoon3/trunk/cocoon-docs/src/docbkx/reference/web-applications.xml (original)
+++ cocoon/cocoon3/trunk/cocoon-docs/src/docbkx/reference/web-applications.xml Mon Jun 1 11:27:51 2009
@@ -309,6 +309,79 @@
<title>Testing</title>
<para>TBW: Integeration tests</para>
</section>
+
+ <section id="webapps.monitoring">
+ <title>Monitoring support</title>
+ <para>
+ This module gives you the possibility of monitoring Cocoon 3 applications. It expose a simple API
+ via <literal>Spring JMX MBeans</literal> and it can be user via <literal>jconsole</literal>
+ or other <literal>JMX</literal> console.
+ </para>
+ <section id="webapps.monitoring.configuration">
+ <title>Module configuration</title>
+ <para>
+ All module configuration is defined in <literal>META-INF/cocoon/spring/cocoon-monitoring.xml.</literal>
+ Each part of module can be enabled or disabled by commenting (or removing) appropriate
+ entry in configuration file, by default all parts are enabled.
+ </para>
+ </section>
+ <section id="webapps.monitoring.parts">
+ <title>Available parts in module</title>
+ <section id="webapps.monitoring.reconfiguration">
+ <title>Inspect logging settings and reconfigure them</title>
+ <section>
+ <title>Inspect all configured loggers</title>
+ <para>
+ The operation <code>getLoggers()</code> returns a list of all configured Log4j loggers in the
+ application, it consists of pairs: <literal><class or package name></literal> (category)
+ => <literal><logging level></literal>
+ </para>
+ </section>
+ <section>
+ <title>Permanent change of a logging level</title>
+ <para>
+ For doing permanent changes of logging level for particular class or package use the operation
+ <code>setLoggingLevel(String category, String newLogLevel)</code>. Where
+ <literal>Category</literal> is name of <literal>class</literal> or <literal>
+ package</literal> with you want to change logging level and <literal>newLogLevel
+ </literal> is one of logging level: <literal>OFF</literal>, <literal>INFO</literal>,
+ <literal>WARN</literal>, <literal>ERROR</literal>, <literal>FATAL</literal>,
+ <literal>TRACE</literal>, <literal>DEBUG</literal>, <literal>ALL</literal> (this parameter
+ isn't case sensitive, so you can also use lower case names).
+ </para>
+ </section>
+ <section>
+ <title>Temporal change of a logging level</title>
+ <para>
+ For doing temporal changes of logging level for particular class or package use the operation
+ <code>setLoggingTempoporalLevel(String category, String temporalLogLevel, String
+ timeOut)</code>. First two parameters are same as in <code>setLoggingLevel()</code>,
+ last one determinate how long this logging level should be used (after that amount of
+ time logging level would be set back to old level). <literal>timeOut</literal> parameter
+ should match regular expression: <literal>^[0-9.]+[dhm]?$</literal>, for example if value
+ of <literal>timeOut</literal> is set for <code>1,5h</code> that means that new logging level
+ would be active for one and half hour.
+ </para>
+ </section>
+ <section>
+ <title>Load a new configuration file</title>
+ <para>
+ For loading a completely new configuration file use the operation <code>loadNewConfigurationFile(String path)</code>.
+ This method is capable for both <literal>XML</literal> and <literal>properties</literal> configuration
+ files. There is only one parameter <literal>path</literal> that should contain absolute path to new
+ configuration file located locally on the server. Before performning any action that file is validated. First of
+ all the file extension (there are only two appropriate extensions: <literal>*.xml</literal>
+ and <literal>*.properties</literal>) is checked. The next validation step is different for both files, for
+ <literal>XML</literal> files its content is validated against the Log4j <literal>DTD</literal> or
+ <literal>schema</literal> then all output log files are checked that they exist and they are writable.
+ <literal>Properties</literal> configuration files are validated that they contain at least one
+ appender and each output file directory exists and is writable. These validations are done to
+ prevent Log4j to get into an inconsistent state.
+ </para>
+ </section>
+ </section>
+ </section>
+ </section>
<section id="webapps.tutorial">
<title>Tutorial</title>
Modified: cocoon/cocoon3/trunk/cocoon-monitoring/src/main/java/org/apache/cocoon/monitoring/Log4JReconfigurator.java
URL: http://svn.apache.org/viewvc/cocoon/cocoon3/trunk/cocoon-monitoring/src/main/java/org/apache/cocoon/monitoring/Log4JReconfigurator.java?rev=780620&r1=780619&r2=780620&view=diff
==============================================================================
--- cocoon/cocoon3/trunk/cocoon-monitoring/src/main/java/org/apache/cocoon/monitoring/Log4JReconfigurator.java (original)
+++ cocoon/cocoon3/trunk/cocoon-monitoring/src/main/java/org/apache/cocoon/monitoring/Log4JReconfigurator.java Mon Jun 1 11:27:51 2009
@@ -16,31 +16,78 @@
*/
package org.apache.cocoon.monitoring;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
+import java.util.Scanner;
+import javax.naming.ConfigurationException;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.cocoon.configuration.PropertyHelper;
+import org.apache.cocoon.configuration.Settings;
+import org.apache.commons.io.FilenameUtils;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
+import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.xml.DOMConfigurator;
+import org.apache.log4j.xml.Log4jEntityResolver;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedOperationParameters;
import org.springframework.jmx.export.annotation.ManagedResource;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.helpers.DefaultHandler;
+/**
+ * This is a JMX MBean class that expose methods for log4j configuration.
+ */
@ManagedResource
public class Log4JReconfigurator {
- private LoggerRepository loggerRepository;
+ private static final String[] EXTENSIONS = new String[] { "xml", "properties" };
+
+ private final Logger logger;
+ private final LoggerRepository loggerRepository;
+ private final DocumentBuilder docBuilder;
+
+ private Settings settings;
public Log4JReconfigurator() {
this.loggerRepository = LogManager.getLoggerRepository();
+ this.logger = Logger.getLogger(Log4JReconfigurator.class);
+
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ dbf.setValidating(true);
+
+ try {
+ this.docBuilder = dbf.newDocumentBuilder();
+ } catch (ParserConfigurationException e) {
+ this.logger.fatal(e.getMessage(), e);
+ throw new RuntimeException(e);
+ }
+ this.docBuilder.setErrorHandler(new DefaultHandler());
+ this.docBuilder.setEntityResolver(new Log4jEntityResolver());
}
+ /**
+ * Find all configured loggers and returns is as a array of <code>String</code>s
+ *
+ * @return list of all configured loggers with their level.
+ */
@ManagedAttribute(description = "Return a list of all configured loggers with their level.")
- public String[] getLoggers() {
+ public final String[] getLoggers() {
List<String> result = new ArrayList<String>();
@SuppressWarnings("unchecked")
@@ -55,11 +102,24 @@
return result.toArray(new String[] {});
}
- @ManagedOperation(description = "Sets logging level for a paticular package or a class. Returns true if operation was succesful.")
+ /**
+ * Sets logging level for a particular package or a class.
+ *
+ * @param category name of the log category (usually a package or class name) whose log level
+ * should be changed.
+ * @param newLogLevel new log level for that category. Available log levels are:
+ * <code>OFF</code>, <code>INFO</code>, <code>WARN</code>, <code>ERROR</code>,
+ * <code>FATAL</code>, <code>TRACE</code>, <code>DEBUG</code>, <code>ALL</code>
+ * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
+ */
+ @ManagedOperation(description = "Sets logging level for a particular package or a class. Returns "
+ + "true if operation was successful.")
@ManagedOperationParameters(value = {
- @ManagedOperationParameter(name = "category", description = "Name of the log category (usually a package or class name) whose log level should be changed."),
- @ManagedOperationParameter(name = "newLevel", description = "New log level for that category. Avaliable log levels are: OFF, INFO, WARN, ERROR, FATAL, TRACE, DEBUG, ALL") })
- public boolean setLoggingLevel(String category, String newLogLevel) {
+ @ManagedOperationParameter(name = "category", description = "Name of the log category (usually "
+ + "a package or class name) whose log level should be changed."),
+ @ManagedOperationParameter(name = "newLevel", description = "New log level for that category. "
+ + "Available log levels are: OFF, INFO, WARN, ERROR, FATAL, TRACE, DEBUG, ALL") })
+ public final boolean setLoggingLevel(final String category, final String newLogLevel) {
boolean result = false;
Logger logger = this.loggerRepository.getLogger(category);
@@ -71,14 +131,32 @@
return result;
}
- @ManagedOperation(description = "Sets new logging level for amout of time. After timeout log level is set back to old value.")
+ /**
+ * Sets new logging level for amount of time. After timeout log level is set back to old value.
+ *
+ * @param category name of the log category (usually a package or class name) whose log level
+ * should be changed.
+ * @param temporalLogLevel temporal log level for that category that should be set for specified
+ * amount of time.
+ * @param timeOut amount of time that temporalLevel should be active. Value of timeOut should
+ * match regular expression: ^[0-9.]+[dhm]?$ where <code>d</code> means day,
+ * <code>h</code> hours and <code>m</code> minutes
+ * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
+ */
+ @ManagedOperation(description = "Sets new logging level for amount of time. After timeout log level is set"
+ + " back to old value.")
@ManagedOperationParameters(value = {
- @ManagedOperationParameter(name = "category", description = "Name of the log category (usually a package or class name) whose log level should be changed."),
- @ManagedOperationParameter(name = "temporalLevel", description = "Temporal log level for that category that should be set for specified amout of time."),
- @ManagedOperationParameter(name = "timeOut", description = "Amount of time that temporalLevel should be active. Value of timeOut should match regex: ^[0-9.]+[dhm]?$ where 'd' means day, 'h' hours and 'm' minutes") })
- public boolean setLoggingTempoporalLevel(String category, String temporalLogLevel, String timeOut) {
+ @ManagedOperationParameter(name = "category", description = "Name of the log category (usually"
+ + " a package or class name) whose log level should be changed."),
+ @ManagedOperationParameter(name = "temporalLevel", description = "Temporal log level for that "
+ + "category that should be set for specified amount of time."),
+ @ManagedOperationParameter(name = "timeOut", description = "Amount of time that temporalLevel "
+ + "should be active. Value of timeOut should match regular expression: ^[0-9.]+[dhm]?$ "
+ + "where 'd' means day, 'h' hours and 'm' minutes") })
+ public final boolean setLoggingTempoporalLevel(final String category, final String temporalLogLevel,
+ final String timeOut) {
if (!timeOut.matches("^[0-9.]+[dhm]?$")) {
- throw new UnsupportedOperationException("Unsupported time out format: " + timeOut);
+ throw new UnsupportedOperationException("Unsupported time-out format: " + timeOut);
}
boolean result = false;
@@ -94,4 +172,178 @@
return result;
}
+
+ /**
+ * Allows to change configuration of log4j on the fly. This function support both XML and
+ * properties configuration files. Before reloading the configuration it checks that the new
+ * config file contains at least one appender and all output files are accessible (for XML
+ * configs it also validate XML syntax using schema or DTD)
+ *
+ * @param path absolute path to configuration file located on the server.
+ * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
+ * @throws Exception if something unusual happens
+ */
+ @ManagedOperation(description = "Allows to change configuration of log4j on the fly. This function support "
+ + "both XML and properties configuration files. Before reloading configuration it checks that the new "
+ + "config file contains at least one appender and all output files are accessible (for XML configs it"
+ + "also validate XML syntax using schema or DTD)")
+ @ManagedOperationParameters(value = { @ManagedOperationParameter(name = "configFilePath", description = "Absolute path to configuration file.") })
+ public final boolean loadNewConfigurationFile(String path) throws Exception {
+ path = path.trim();
+
+ // check extension
+ if (FilenameUtils.isExtension(path, EXTENSIONS)) {
+ File newConfig = new File(path);
+
+ if (!newConfig.exists()) {
+ this.logger.fatal("Cannot find file: " + path);
+ throw new FileNotFoundException("Cannot find file: " + path);
+ } else if (!newConfig.canRead()) {
+ this.logAndThrowIOException("Cannot read file: " + path);
+ }
+
+ if (FilenameUtils.isExtension(path, "xml")) {
+ this.loadNewXMLConfigurationFile(path);
+ } else {
+ this.loadNewPropertiesConfigurationFile(path);
+ }
+ } else {
+ String message = "Unsupported file format: " + FilenameUtils.getExtension(path);
+ this.logger.fatal(message);
+ throw new ConfigurationException(message);
+ }
+
+ return true;
+ }
+
+ /**
+ * Inject the settings object.
+ *
+ * @param s The settings bean.
+ */
+ public final void setSettings(final Settings s) {
+ this.settings = s;
+ }
+
+ /**
+ * Loads XML config file specified by <code>path</code> parameter. Then this file is validate
+ * against DTD or schema, after that every appender is checked that it output file is
+ * accessible.
+ *
+ * @param path absolute path to XML configuration file.
+ * @throws Exception if something unusual happens
+ */
+ private void loadNewXMLConfigurationFile(final String path) throws Exception {
+ Document doc;
+ try { // validate XML config file
+ doc = this.docBuilder.parse(path);
+ } catch (Exception e) {
+ this.logger.fatal(e.getMessage(), e);
+ throw new IOException("Config file parse exception: " + e.getMessage());
+ }
+
+ // check access for all log files
+ NodeList appenderList = doc.getElementsByTagName("appender");
+ for (int i = 0; i < appenderList.getLength(); i++) {
+ NodeList childNodes = appenderList.item(i).getChildNodes();
+ for (int j = 0; j < childNodes.getLength(); j++) {
+ Node item = childNodes.item(j);
+ this.extractLogFilespathAndValidate(item);
+ }
+ }
+
+ // apply new settings
+ DOMConfigurator.configure(path);
+ }
+
+ /**
+ * Extracts path to log file from XML configuration node and checks that it exist and is
+ * writable.
+ *
+ * @param item
+ * @throws IOException if directory doesn't exist or isn't writable
+ */
+ private void extractLogFilespathAndValidate(final Node item) throws IOException {
+ if (!item.getNodeName().equalsIgnoreCase("param")) {
+ return;
+ }
+
+ NamedNodeMap itemAttributes = item.getAttributes();
+ if (itemAttributes == null) {
+ return;
+ }
+
+ Node paramName = itemAttributes.getNamedItem("name");
+ if (paramName != null && paramName.getNodeValue().equalsIgnoreCase("file")) {
+ Node paramValue = itemAttributes.getNamedItem("value");
+ if (paramValue != null) {
+ String logpath = paramValue.getNodeValue();
+ this.validateLogFile(logpath);
+ }
+ }
+ }
+
+ /**
+ * Loads properties config file specified by <code>path</code> parameter. Then it validate that
+ * file contains configuration for at least one appender and validate all configured log files
+ * that they path exist and it is writable.
+ *
+ * @param path absolute path to properties configuration file.
+ * @throws Exception if something unusual happens
+ */
+ private void loadNewPropertiesConfigurationFile(final String path) throws ConfigurationException, IOException {
+ boolean result = false;
+ File file = new File(path);
+
+ // search for appender configuration
+ Scanner fileScanner = new Scanner(file);
+ while (fileScanner.hasNext()) {
+ String line = fileScanner.nextLine();
+ if (!result && line.toLowerCase().matches("^log4j\\.appender\\.[\\w\\.]+=[\\w\\.]+$")) {
+ PropertyConfigurator.configure(file.getPath());
+ result = true; // we got our "at least one appender" ;)
+ }
+
+ if (line.toLowerCase().matches("^log4j\\.appender\\.[\\w]+\\.file=[\\w\\.\\{\\}\\$/\\:]+$")) {
+ String[] logFile = line.split("=");
+ this.validateLogFile(logFile[1]);
+ }
+ }
+
+ if (!result) {
+ throw new ConfigurationException(
+ "No configured appenders, there should be at least one appender confgured.");
+ }
+ }
+
+ /**
+ * Helper method that logs exception and throws IOException.
+ *
+ * @param message that should be logged and passed to exception
+ * @throws IOException always throws this exception
+ */
+ private void logAndThrowIOException(final String message) throws IOException {
+ this.logger.fatal(message);
+ throw new IOException(message);
+ }
+
+ /**
+ * Validate log file path that it exists and is writable.
+ *
+ * @param logPath path to log file
+ * @throws IOException if log file directory doesn't exist or is read only
+ */
+ private void validateLogFile(String logPath) throws IOException {
+ String logDirpath = FilenameUtils.getFullPath(PropertyHelper.replace(logPath, this.settings));
+
+ if (logDirpath.length() == 0) { // if logDirpath is empty
+ logDirpath = "."; // current directory is log directory
+ }
+
+ if (!new File(logDirpath).exists()) {
+ this.logAndThrowIOException("Log directory: " + logDirpath + " does not exist.");
+ } else if (!new File(logPath).canWrite()) {
+ this.logAndThrowIOException("Log file: " + logPath + " is read only.");
+ }
+ }
}
Modified: cocoon/cocoon3/trunk/cocoon-monitoring/src/main/java/org/apache/cocoon/monitoring/LoggingConfigurationResetter.java
URL: http://svn.apache.org/viewvc/cocoon/cocoon3/trunk/cocoon-monitoring/src/main/java/org/apache/cocoon/monitoring/LoggingConfigurationResetter.java?rev=780620&r1=780619&r2=780620&view=diff
==============================================================================
--- cocoon/cocoon3/trunk/cocoon-monitoring/src/main/java/org/apache/cocoon/monitoring/LoggingConfigurationResetter.java (original)
+++ cocoon/cocoon3/trunk/cocoon-monitoring/src/main/java/org/apache/cocoon/monitoring/LoggingConfigurationResetter.java Mon Jun 1 11:27:51 2009
@@ -22,13 +22,24 @@
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
+/**
+ * This class starts a new thread that restores the logging level for a particular class or package
+ * (after a defined amount of time).
+ */
public class LoggingConfigurationResetter {
- private long delay;
- private Logger logger;
- private Level oldLogLevel;
-
- public LoggingConfigurationResetter(final Logger logger, final Level oldLevel, String timeout) {
+ private final long delay;
+ private final Logger logger;
+ private final Level oldLogLevel;
+
+ /**
+ * @param logger instance of logger that logging level should changed
+ * @param oldLevel logging level value that should be set after timeout
+ * @param timeout value of timeout. Is should match regular expression: ^[0-9.]+[dhm]?$
+ * where <code>d</code> means day, <code>h</code> hours and <code>m</code>
+ * minutes
+ */
+ public LoggingConfigurationResetter(final Logger logger, Level oldLevel, String timeout) {
this.logger = logger;
this.oldLogLevel = oldLevel;
@@ -36,6 +47,9 @@
long factor;
char unit = timeout.charAt(timeout.length() - 1); // get last char, it should be our unit
switch (unit) {
+ case 's': // second
+ factor = 1 * 1000;
+ break;
case 'm': // minute
factor = 60 * 1000;
break;
@@ -46,16 +60,20 @@
factor = 24 * 60 * 60 * 1000;
break;
default:
- throw new UnsupportedOperationException("Unsupporterd unit: " + unit);
+ String message = "Unsupported unit: " + unit;
+ throw new UnsupportedOperationException(message);
}
float multipler = Float.parseFloat(timeout.substring(0, timeout.length() - 1));
this.delay = Math.round(multipler * factor);
-
}
+ /**
+ * Starts thread
+ */
public void start() {
TimerTask task = new TimerTask() {
+
@Override
public void run() {
LoggingConfigurationResetter.this.logger.setLevel(LoggingConfigurationResetter.this.oldLogLevel);
Modified: cocoon/cocoon3/trunk/cocoon-sample/rcl-config/WEB-INF/log4j.xml
URL: http://svn.apache.org/viewvc/cocoon/cocoon3/trunk/cocoon-sample/rcl-config/WEB-INF/log4j.xml?rev=780620&r1=780619&r2=780620&view=diff
==============================================================================
--- cocoon/cocoon3/trunk/cocoon-sample/rcl-config/WEB-INF/log4j.xml (original)
+++ cocoon/cocoon3/trunk/cocoon-sample/rcl-config/WEB-INF/log4j.xml Mon Jun 1 11:27:51 2009
@@ -19,7 +19,6 @@
-->
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
-
<appender name="COCOON_DEFAULT" class="org.apache.log4j.FileAppender">
<param name="File" value="./target/work/log/cocoon.log"/>
<param name="Append" value="true"/>
@@ -36,5 +35,4 @@
<priority value="WARN"/>
<appender-ref ref="COCOON_DEFAULT"/>
</root>
-
</log4j:configuration>