Index: api/src/main/java/org/apache/logging/log4j/simple/SimpleLogger.java =================================================================== --- api/src/main/java/org/apache/logging/log4j/simple/SimpleLogger.java (revision 1469363) +++ api/src/main/java/org/apache/logging/log4j/simple/SimpleLogger.java (working copy) @@ -89,6 +89,10 @@ public void setStream(final PrintStream stream) { this.stream = stream; } + + public Level getLevel() { + return level; + } public void setLevel(final Level level) { if (level != null) { Index: api/src/main/java/org/apache/logging/log4j/status/StatusData.java =================================================================== --- api/src/main/java/org/apache/logging/log4j/status/StatusData.java (revision 1469363) +++ api/src/main/java/org/apache/logging/log4j/status/StatusData.java (working copy) @@ -21,22 +21,20 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; +import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.Date; /** * The Status data. */ -public class StatusData { +public class StatusData implements Serializable { + private static final long serialVersionUID = -4341916115118014017L; private final long timestamp; - private final StackTraceElement caller; - private final Level level; - private final Message msg; - private final Throwable throwable; /** Index: api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java =================================================================== --- api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java (revision 1469363) +++ api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java (working copy) @@ -73,6 +73,10 @@ public static StatusLogger getLogger() { return STATUS_LOGGER; } + + public Level getLevel() { + return logger.getLevel(); + } public void setLevel(final Level level) { logger.setLevel(level); Index: core/pom.xml =================================================================== --- core/pom.xml (revision 1469363) +++ core/pom.xml (working copy) @@ -342,6 +342,53 @@ + + default-profile + + true + + ${java.home}/../lib/jconsole.jar + + + + ${java.home}/../lib/jconsole.jar + + + + + com.sun + tools + 1.6.0 + system + ${toolsjar} + + + com.sun + jconsole + 1.6.0 + system + ${java.home}/../lib/jconsole.jar + + + + + mac-profile + + false + + ${java.home}/../Classes/jconsole.jar + + + + + com.sun + jconsole + 1.6.0 + system + ${java.home}/../Classes/jconsole.jar + + + Index: core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java (revision 1469363) +++ core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java (working copy) @@ -16,12 +16,15 @@ */ package org.apache.logging.log4j.core; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.io.File; import java.net.URI; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -32,33 +35,35 @@ import org.apache.logging.log4j.core.config.NullConfiguration; import org.apache.logging.log4j.core.config.Reconfigurable; import org.apache.logging.log4j.core.helpers.NetUtils; +import org.apache.logging.log4j.core.jmx.Assert; import org.apache.logging.log4j.message.MessageFactory; import org.apache.logging.log4j.spi.AbstractLogger; import org.apache.logging.log4j.status.StatusLogger; /** - * The LoggerContext is the anchor for the logging system. It maintains a list of all the loggers requested by - * applications and a reference to the Configuration. The Configuration will contain the configured loggers, appenders, - * filters, etc and will be atomically updated whenever a reconfigure occurs. + * The LoggerContext is the anchor for the logging system. It maintains a list + * of all the loggers requested by applications and a reference to the + * Configuration. The Configuration will contain the configured loggers, + * appenders, filters, etc and will be atomically updated whenever a reconfigure + * occurs. */ public class LoggerContext implements org.apache.logging.log4j.spi.LoggerContext, ConfigurationListener, LifeCycle { + public static final String PROPERTY_CONFIG = "config"; private static final StatusLogger LOGGER = StatusLogger.getLogger(); private final ConcurrentMap loggers = new ConcurrentHashMap(); + private final CopyOnWriteArrayList propertyChangeListeners = new CopyOnWriteArrayList(); /** - * The Configuration is volatile to guarantee that initialization of the Configuration has completed before - * the reference is updated. + * The Configuration is volatile to guarantee that initialization of the + * Configuration has completed before the reference is updated. */ private volatile Configuration config = new DefaultConfiguration(); - private Object externalContext; - private final String name; + private URI configLocation; - private final URI configLocation; - private ShutdownThread shutdownThread = null; /** @@ -111,8 +116,9 @@ } /** - * Constructor taking a name external context and a configuration location String. The location - * must be resolvable to a File. + * Constructor taking a name external context and a configuration location + * String. The location must be resolvable to a File. + * * @param name The configuration location. * @param externalContext The external context. * @param configLocn The configuration location. @@ -216,8 +222,9 @@ /** * Obtain a Logger from the Context. * @param name The name of the Logger to return. - * @param messageFactory The message factory is used only when creating a logger, subsequent use does not change - * the logger but will log a warning if mismatched. + * @param messageFactory The message factory is used only when creating a + * logger, subsequent use does not change the logger but will log + * a warning if mismatched. * @return The Logger. */ public Logger getLogger(final String name, final MessageFactory messageFactory) { @@ -242,7 +249,9 @@ } /** - * Returns the current Configuration. The Configuration will be replaced when a reconfigure occurs. + * Returns the current Configuration. The Configuration will be replaced + * when a reconfigure occurs. + * * @return The Configuration. */ public Configuration getConfiguration() { @@ -288,22 +297,43 @@ prev.removeListener(this); prev.stop(); } + + // notify listeners + PropertyChangeEvent evt = new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config); + for (PropertyChangeListener listener : propertyChangeListeners) { + listener.propertyChange(evt); + } return prev; } + public void addPropertyChangeListener(PropertyChangeListener listener) { + propertyChangeListeners.add(Assert.isNotNull(listener, "listener")); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + propertyChangeListeners.remove(listener); + } + + public synchronized URI getConfigLocation() { + return configLocation; + } + + public synchronized void setConfigLocation(URI configLocation) { + this.configLocation = configLocation; + reconfigure(); + } + /** - * Reconfigure the context. + * Reconfigure the context. */ public synchronized void reconfigure() { LOGGER.debug("Reconfiguration started for context " + name); final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configLocation); setConfiguration(instance); - /*instance.start(); - Configuration old = setConfiguration(instance); - updateLoggers(); - if (old != null) { - old.stop(); - } */ + /* + * instance.start(); Configuration old = setConfiguration(instance); + * updateLoggers(); if (old != null) { old.stop(); } + */ LOGGER.debug("Reconfiguration completed"); } @@ -325,7 +355,9 @@ } /** - * Cause a reconfiguration to take place when the underlying configuration file changes. + * Cause a reconfiguration to take place when the underlying configuration + * file changes. + * * @param reconfigurable The Configuration that can be reconfigured. */ public synchronized void onChange(final Reconfigurable reconfigurable) { Index: core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java (revision 1469363) +++ core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java (working copy) @@ -16,17 +16,18 @@ */ package org.apache.logging.log4j.core.impl; +import java.net.URI; + import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.helpers.Constants; import org.apache.logging.log4j.core.helpers.Loader; +import org.apache.logging.log4j.core.jmx.Server; import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector; import org.apache.logging.log4j.core.selector.ContextSelector; +import org.apache.logging.log4j.spi.LoggerContextFactory; import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.spi.LoggerContextFactory; import org.apache.logging.log4j.util.PropertiesUtil; -import java.net.URI; - /** * Factory to locate a ContextSelector and then load a LoggerContext. */ @@ -43,17 +44,22 @@ final String sel = PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_CONTEXT_SELECTOR); if (sel != null) { try { - final Class clazz = Loader.loadClass(sel); + final Class clazz = Loader.loadClass(sel); if (clazz != null && ContextSelector.class.isAssignableFrom(clazz)) { selector = (ContextSelector) clazz.newInstance(); - return; } } catch (final Exception ex) { LOGGER.error("Unable to create context " + sel, ex); } - } - selector = new ClassLoaderContextSelector(); + if (selector == null) { + selector = new ClassLoaderContextSelector(); + } + try { + Server.registerMBeans(selector); + } catch (Exception ex) { + LOGGER.error("Could not start JMX", ex); + } } /** @@ -80,7 +86,6 @@ return ctx; } - /** * Load the LoggerContext using the ContextSelector. * @param fqcn The fully qualified class name of the caller. @@ -91,7 +96,7 @@ * @return The LoggerContext. */ public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext, - URI configLocation) { + URI configLocation) { final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext, configLocation); if (ctx.getStatus() == LoggerContext.Status.INITIALIZED) { ctx.start(); Index: core/src/main/java/org/apache/logging/log4j/core/jmx/AppenderAdmin.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/jmx/AppenderAdmin.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/jmx/AppenderAdmin.java (working copy) @@ -0,0 +1,77 @@ +/* + * 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.logging.log4j.core.jmx; + +import javax.management.ObjectName; + +import org.apache.logging.log4j.core.Appender; + +/** + * Implementation of the {@code AppenderAdminMBean} interface. + */ +public class AppenderAdmin implements AppenderAdminMBean { + + private final String contextName; + private final Appender appender; + private final ObjectName objectName; + + /** + * Constructs a new {@code AppenderAdmin} with the specified contextName + * and appender. + * + * @param contextName used in the {@code ObjectName} for this mbean + * @param appender the instrumented object + */ + public AppenderAdmin(String contextName, Appender appender) { + // super(executor); // no notifications for now + this.contextName = Assert.isNotNull(contextName, "contextName"); + this.appender = Assert.isNotNull(appender, "appender"); + try { + String ctxName = Server.escape(this.contextName); + String configName = Server.escape(appender.getName()); + String name = String.format(PATTERN, ctxName, configName); + objectName = new ObjectName(name); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + /** @see AppenderAdminMBean#PATTERN */ + public ObjectName getObjectName() { + return objectName; + } + + @Override + public String getName() { + return appender.getName(); + } + + @Override + public String getLayout() { + return String.valueOf(appender.getLayout()); + } + + @Override + public boolean isExceptionSuppressed() { + return appender.isExceptionSuppressed(); + } + + @Override + public String getErrorHandler() { + return String.valueOf(appender.getHandler()); + } +} Index: core/src/main/java/org/apache/logging/log4j/core/jmx/AppenderAdminMBean.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/jmx/AppenderAdminMBean.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/jmx/AppenderAdminMBean.java (working copy) @@ -0,0 +1,59 @@ +/* + * 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.logging.log4j.core.jmx; + +/** + * The MBean interface for monitoring and managing an {@code Appender}. + */ +public interface AppenderAdminMBean { + /** ObjectName pattern for AppenderAdmin MBeans. */ + String PATTERN = "org.apache.logging.log4j2:type=LoggerContext,ctx=%s,sub=Appender,name=%s"; + + /** + * Returns the name of the instrumented {@code Appender}. + * + * @return the name of the Appender + */ + String getName(); + + /** + * Returns the result of calling {@code toString} on the {@code Layout} + * object of the instrumented {@code Appender}. + * + * @return the {@code Layout} of the instrumented {@code Appender} as a + * string + */ + String getLayout(); + + /** + * Returns how exceptions thrown on the instrumented {@code Appender} are + * handled. + * + * @return {@code true} if any exceptions thrown by the Appender will be + * logged or {@code false} if such exceptions are re-thrown. + */ + boolean isExceptionSuppressed(); + + /** + * Returns the result of calling {@code toString} on the error handler of + * this appender, or {@code "null"} if no error handler was set. + * + * @return result of calling {@code toString} on the error handler of this + * appender, or {@code "null"} + */ + String getErrorHandler(); +} Index: core/src/main/java/org/apache/logging/log4j/core/jmx/Assert.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/jmx/Assert.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/jmx/Assert.java (working copy) @@ -0,0 +1,58 @@ +/* + * 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.logging.log4j.core.jmx; + +/** + * Utility class providing common validation logic. + */ +public class Assert { + private Assert() { + } + + /** + * Throws a {@code NullPointerException} if the specified parameter is + * {@code null}, otherwise returns the specified parameter. + *

+ * Usage: + * + *

+     * // earlier you would write this:
+     * public SomeConstructor(Object param) {
+     *     if (param == null) {
+     *         throw new NullPointerException(name + " is null");
+     *     }
+     *     this.field = param;
+     * }
+     * 
+     * // now you can do the same in one line:
+     * public SomeConstructor(Object param) {
+     *     this.field = Assert.isNotNull(param);
+     * }
+     * 
+ * + * @param checkMe the parameter to check + * @param name name of the parameter to use in the error message if + * {@code null} + * @return the specified parameter + */ + public static T isNotNull(T checkMe, String name) { + if (checkMe == null) { + throw new NullPointerException(name + " is null"); + } + return checkMe; + } +} Index: core/src/main/java/org/apache/logging/log4j/core/jmx/Client.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/jmx/Client.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/jmx/Client.java (working copy) @@ -0,0 +1,149 @@ +/* + * 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.logging.log4j.core.jmx; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import javax.management.JMX; +import javax.management.MBeanServerConnection; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; +import javax.management.remote.JMXConnector; + +/** + * This class allows client-side code to perform operations on remote + * (server-side) MBeans via proxies. + */ +public class Client { + private JMXConnector connector; + private MBeanServerConnection connection; + private StatusLoggerAdminMBean statusLoggerAdmin; + private ContextSelectorAdminMBean contextSelectorAdmin; + private List contextAdminList; + + /** + * Constructs a new {@code Client} object and creates proxies for all known + * remote MBeans. + * + * @param connector used to create the MBean server connection through which + * to communicate with the remote mbeans + * @throws MalformedObjectNameException if a problem occurred identifying + * one of the remote mbeans + * @throws IOException if the connection failed + */ + public Client(JMXConnector connector) throws MalformedObjectNameException, + IOException { + this.connector = Assert.isNotNull(connector, "JMXConnector"); + this.connector.connect(); + this.connection = connector.getMBeanServerConnection(); + init(); + } + + /** + * Constructs a new {@code Client} object and creates proxies for all known + * remote MBeans. + * + * @param mBeanServerConnection the MBean server connection through which to + * communicate with the remote mbeans + * @throws MalformedObjectNameException if a problem occurred identifying + * one of the remote mbeans + * @throws IOException if the connection failed + */ + public Client(MBeanServerConnection mBeanServerConnection) + throws MalformedObjectNameException, IOException { + this.connection = mBeanServerConnection; + init(); + } + + private void init() throws MalformedObjectNameException, IOException { + statusLoggerAdmin = JMX.newMBeanProxy(connection, // + new ObjectName(StatusLoggerAdminMBean.NAME), // + StatusLoggerAdminMBean.class, true); + + contextSelectorAdmin = JMX.newMBeanProxy(connection, // + new ObjectName(ContextSelectorAdminMBean.NAME), // + ContextSelectorAdminMBean.class, false); + + contextAdminList = new ArrayList(); + String pattern = String.format(LoggerContextAdminMBean.PATTERN, "*"); + ObjectName search = new ObjectName(pattern); + Set found = connection.queryNames(search, null); + for (ObjectName contextName : found) { + LoggerContextAdminMBean ctx = JMX.newMBeanProxy(connection, // + contextName, // + LoggerContextAdminMBean.class, false); + contextAdminList.add(ctx); + + // TODO Appenders, LoggerConfigs + } + } + + /** + * Returns a proxy that allows operations to be performed on the remote + * {@code ContextSelectorAdminMBean}. + * + * @return a proxy to the remote {@code ContextSelectorAdminMBean} + */ + public ContextSelectorAdminMBean getContextSelectorAdmin() { + return contextSelectorAdmin; + } + + /** + * Returns a list of proxies that allow operations to be performed on the + * remote {@code LoggerContextAdminMBean}s. + * + * @return a list of proxies to the remote {@code LoggerContextAdminMBean}s + */ + public List getLoggerContextAdmins() { + return new ArrayList(contextAdminList); + } + + /** + * Closes the client connection to its server. Any ongoing or new requests + * to the MBeanServerConnection will fail. + */ + public void close() { + try { + connector.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Returns the MBean server connection through which to communicate with the + * remote mbeans. + * + * @return the MBean server connection + */ + public MBeanServerConnection getConnection() { + return connection; + } + + /** + * Returns a proxy that allows operations to be performed on the remote + * {@code StatusLoggerAdminMBean}. + * + * @return a proxy to the remote {@code StatusLoggerAdminMBean} + */ + public StatusLoggerAdminMBean getStatusLoggerAdmin() { + return statusLoggerAdmin; + } +} Index: core/src/main/java/org/apache/logging/log4j/core/jmx/ClientEditConfigPanel.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/jmx/ClientEditConfigPanel.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/jmx/ClientEditConfigPanel.java (working copy) @@ -0,0 +1,149 @@ +/* + * 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.logging.log4j.core.jmx; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.io.PrintWriter; +import java.io.StringWriter; + +import javax.swing.AbstractAction; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; + +/** + * Panel for editing Log4J configurations. + */ +public class ClientEditConfigPanel extends JPanel { + private static final long serialVersionUID = -7544651740950723394L; + private static final int LOCATION_TEXT_COLS = 50; + private static final int CONFIG_TEXT_COLS = 60; + private static final int CONFIG_TEXT_ROWS = 20; + private static final int BUFFER_SIZE = 2048; + + private JTextField locationTextField; + private JLabel locationLabel; + private JButton buttonSendLocation; + private JButton buttonSendConfigText; + private JTextArea configTextArea; + private LoggerContextAdminMBean contextAdmin; + + private AbstractAction actionReconfigureFromLocation = new AbstractAction( + "Reconfigure from Location") { + private static final long serialVersionUID = 6995219797596745774L; + + @Override + public void actionPerformed(ActionEvent e) { + try { + contextAdmin.setConfigLocationURI(locationTextField.getText()); + populateWidgets(); + showConfirmation(); + } catch (Exception ex) { + populateWidgets(); + handle("Could not reconfigure from location", ex); + } + } + }; + private AbstractAction actionReconfigureFromText = new AbstractAction( + "Reconfigure with XML Below") { + private static final long serialVersionUID = -2846103707134292312L; + + @Override + public void actionPerformed(ActionEvent e) { + String encoding = System.getProperty("file.encoding"); + try { + contextAdmin.setConfigText(configTextArea.getText(), encoding); + populateWidgets(); + showConfirmation(); + } catch (Exception ex) { + populateWidgets(); + handle("Could not reconfigure from XML", ex); + } + } + }; + + private void handle(String msg, Exception ex) { + StringWriter sr = new StringWriter(BUFFER_SIZE); + PrintWriter pw = new PrintWriter(sr); + pw.println("Please check the StatusLogger tab for details"); + pw.println(); + ex.printStackTrace(pw); + JOptionPane.showMessageDialog(this, sr.toString(), msg, + JOptionPane.ERROR_MESSAGE); + } + + private void showConfirmation() { + JOptionPane.showMessageDialog(this, "Reconfiguration complete.", + "Reconfiguration complete", JOptionPane.INFORMATION_MESSAGE); + } + + public ClientEditConfigPanel(LoggerContextAdminMBean contextAdmin) { + this.contextAdmin = contextAdmin; + createWidgets(); + populateWidgets(); + } + + private void populateWidgets() { + try { + configTextArea.setText(contextAdmin.getConfigText()); + } catch (Exception ex) { + StringWriter sw = new StringWriter(2048); + ex.printStackTrace(new PrintWriter(sw)); + configTextArea.setText(sw.toString()); + } + String uri = contextAdmin.getConfigLocationURI(); + locationTextField.setText(uri); + } + + private void createWidgets() { + configTextArea = new JTextArea(CONFIG_TEXT_ROWS, CONFIG_TEXT_COLS); + // configTextArea.setEditable(false); + configTextArea.setBackground(Color.white); + configTextArea.setForeground(Color.black); + configTextArea.setFont(new Font("Monospaced", Font.PLAIN, + configTextArea.getFont().getSize())); + JScrollPane scrollConfig = new JScrollPane(configTextArea); + + locationTextField = new JTextField(LOCATION_TEXT_COLS); + locationLabel = new JLabel("Location: "); + locationLabel.setLabelFor(locationTextField); + buttonSendLocation = new JButton(actionReconfigureFromLocation); + buttonSendConfigText = new JButton(actionReconfigureFromText); + + JPanel north = new JPanel(); + north.setLayout(new BoxLayout(north, BoxLayout.LINE_AXIS)); + north.add(locationLabel); + north.add(locationTextField); + north.add(buttonSendLocation); + north.add(Box.createRigidArea(new Dimension(20, 0))); + north.add(buttonSendConfigText); + + this.setLayout(new BorderLayout()); + this.add(north, BorderLayout.NORTH); + this.add(scrollConfig, BorderLayout.CENTER); + } +} \ No newline at end of file Index: core/src/main/java/org/apache/logging/log4j/core/jmx/ClientGUI.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/jmx/ClientGUI.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/jmx/ClientGUI.java (working copy) @@ -0,0 +1,201 @@ +/* + * 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.logging.log4j.core.jmx; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Font; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +import javax.management.InstanceNotFoundException; +import javax.management.MalformedObjectNameException; +import javax.management.Notification; +import javax.management.NotificationFilterSupport; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXServiceURL; +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTabbedPane; +import javax.swing.JTextArea; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.UIManager.LookAndFeelInfo; + +/** + * GUI that connects to a Java process via JMX and allows the user to view and + * modify the log4j2 configuration, as well as monitor status logs. + * + * @see http + * ://docs.oracle.com/javase/6/docs/technotes/guides/management/jconsole + * .html + */ +public class ClientGUI extends JPanel implements NotificationListener { + private static final long serialVersionUID = -253621277232291174L; + private Client client; + private JTextArea statusLogTextArea; + private JTabbedPane tabbedPane; + + public ClientGUI(Client client) throws InstanceNotFoundException, + MalformedObjectNameException, IOException { + this.client = Assert.isNotNull(client, "client"); + createWidgets(); + populateWidgets(); + registerListeners(); + } + + private void createWidgets() { + statusLogTextArea = new JTextArea(); + statusLogTextArea.setEditable(false); + statusLogTextArea.setBackground(this.getBackground()); + statusLogTextArea.setForeground(Color.black); + statusLogTextArea.setFont(new Font("Monospaced", Font.PLAIN, + statusLogTextArea.getFont().getSize())); + JScrollPane scrollStatusLog = new JScrollPane(statusLogTextArea); + + tabbedPane = new JTabbedPane(); + tabbedPane.addTab("StatusLogger", scrollStatusLog); + + this.setLayout(new BorderLayout()); + this.add(tabbedPane, BorderLayout.CENTER); + } + + private void populateWidgets() { + + StatusLoggerAdminMBean statusAdmin = client.getStatusLoggerAdmin(); + String[] messages = statusAdmin.getStatusDataHistory(); + for (String message : messages) { + statusLogTextArea.append(message + "\n"); + } + + for (LoggerContextAdminMBean ctx : client.getLoggerContextAdmins()) { + ClientEditConfigPanel editor = new ClientEditConfigPanel(ctx); + tabbedPane.addTab("LoggerContext: " + ctx.getName(), editor); + } + } + + private void registerListeners() throws InstanceNotFoundException, + MalformedObjectNameException, IOException { + NotificationFilterSupport filter = new NotificationFilterSupport(); + filter.enableType(StatusLoggerAdminMBean.NOTIF_TYPE_MESSAGE); + ObjectName objName = new ObjectName(StatusLoggerAdminMBean.NAME); + client.getConnection().addNotificationListener(objName, this, filter, + null); + } + + @Override + public void handleNotification(Notification notif, Object paramObject) { + if (StatusLoggerAdminMBean.NOTIF_TYPE_MESSAGE.equals(notif.getType())) { + statusLogTextArea.append(notif.getMessage() + "\n"); + } + } + + /** + * Connects to the specified location and shows this panel in a window. + *

+ * Useful links: + * http://www.componative.com/content/controller/developer/insights + * /jconsole3/ + * + * @param args + * must have at least one parameter, which specifies the location + * to connect to. Must be of the form {@code host:port} or + * {@code service:jmx:rmi:///jndi/rmi://:/jmxrmi} or + * {@code service:jmx:rmi://:/jndi/rmi://:/jmxrmi} + */ + public static void main(String[] args) throws Exception { + if (args.length < 1) { + usage(); + return; + } + String serviceUrl = args[0]; + if (!serviceUrl.startsWith("service:jmx")) { + serviceUrl = "service:jmx:rmi:///jndi/rmi://" + args[0] + "/jmxrmi"; + } + JMXServiceURL url = new JMXServiceURL(serviceUrl); + Map paramMap = new HashMap(); + for (Object objKey : System.getProperties().keySet()) { + String key = (String) objKey; + paramMap.put(key, System.getProperties().getProperty(key)); + } + JMXConnector connector = JMXConnectorFactory.connect(url, paramMap); + final Client client = new Client(connector); + final String title = "Log4J2 JMX Client - " + url; + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + installLookAndFeel(); + try { + ClientGUI gui = new ClientGUI(client); + JFrame frame = new JFrame(title); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.getContentPane().add(gui, BorderLayout.CENTER); + frame.pack(); + frame.setVisible(true); + } catch (Exception ex) { + // if console is visible, print error so that + // the stack trace remains visible after error dialog is + // closed + ex.printStackTrace(); + + // show error in dialog: there may not be a console window + // visible + StringWriter sr = new StringWriter(); + ex.printStackTrace(new PrintWriter(sr)); + JOptionPane.showMessageDialog(null, sr.toString(), "Error", + JOptionPane.ERROR_MESSAGE); + } + } + }); + } + + private static void usage() { + String me = ClientGUI.class.getName(); + System.err.println("Usage: java " + me + " :"); + System.err.println(" or: java " + me + + " service:jmx:rmi:///jndi/rmi://:/jmxrmi"); + String longAdr = " service:jmx:rmi://:/jndi/rmi://:/jmxrmi"; + System.err.println(" or: java " + me + longAdr); + } + + private static void installLookAndFeel() { + try { + for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { + if ("Nimbus".equals(info.getName())) { + UIManager.setLookAndFeel(info.getClassName()); + return; + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + e.printStackTrace(); + } + } +} Index: core/src/main/java/org/apache/logging/log4j/core/jmx/ClientGUIJConsolePlugin.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/jmx/ClientGUIJConsolePlugin.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/jmx/ClientGUIJConsolePlugin.java (working copy) @@ -0,0 +1,49 @@ +/* + * 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.logging.log4j.core.jmx; + +import java.util.HashMap; +import java.util.Map; + +import javax.swing.JPanel; +import javax.swing.SwingWorker; + +import com.sun.tools.jconsole.JConsolePlugin; + +/** + * Adapts the {@code ClientGUI} to the {@code JConsolePlugin} API. + */ +public class ClientGUIJConsolePlugin extends JConsolePlugin { + + @Override + public Map getTabs() { + try { + Client client = new Client(getContext().getMBeanServerConnection()); + ClientGUI gui = new ClientGUI(client); + Map result = new HashMap(); + result.put("Log4j2", gui); + return result; + } catch (Throwable ex) { + throw new IllegalStateException(ex); + } + } + + @Override + public SwingWorker newSwingWorker() { + return null; + } +} \ No newline at end of file Index: core/src/main/java/org/apache/logging/log4j/core/jmx/ContextSelectorAdmin.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/jmx/ContextSelectorAdmin.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/jmx/ContextSelectorAdmin.java (working copy) @@ -0,0 +1,55 @@ +/* + * 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.logging.log4j.core.jmx; + +import javax.management.ObjectName; + +import org.apache.logging.log4j.core.selector.ContextSelector; + +/** + * Implementation of the {@code ContextSelectorAdminMBean} interface. + */ +public class ContextSelectorAdmin implements ContextSelectorAdminMBean { + + private final ObjectName objectName; + private final ContextSelector selector; + + /** + * Constructs a new {@code ContextSelectorAdmin}. + * + * @param selector the instrumented object + */ + public ContextSelectorAdmin(ContextSelector selector) { + super(); + this.selector = Assert.isNotNull(selector, "ContextSelector"); + try { + objectName = new ObjectName(NAME); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + /** @see ContextSelectorAdminMBean#NAME */ + public ObjectName getObjectName() { + return objectName; + } + + @Override + public String getImplementationClassName() { + return selector.getClass().getName(); + } +} Index: core/src/main/java/org/apache/logging/log4j/core/jmx/ContextSelectorAdminMBean.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/jmx/ContextSelectorAdminMBean.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/jmx/ContextSelectorAdminMBean.java (working copy) @@ -0,0 +1,33 @@ +/* + * 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.logging.log4j.core.jmx; + +/** + * The MBean interface for monitoring and managing the {@code ContextSelector}. + */ +public interface ContextSelectorAdminMBean { + /** Object name of this MBean. */ + String NAME = "org.apache.logging.log4j2:type=ContextSelector"; + + /** + * Returns the name of the class implementing the {@code ContextSelector} + * interface. + * + * @return the name of the {@code ContextSelector} implementation class. + */ + String getImplementationClassName(); +} Index: core/src/main/java/org/apache/logging/log4j/core/jmx/Log4jManager.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/jmx/Log4jManager.java (revision 1469363) +++ core/src/main/java/org/apache/logging/log4j/core/jmx/Log4jManager.java (working copy) @@ -1,44 +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.logging.log4j.core.jmx; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.core.impl.Log4jContextFactory; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.selector.ContextSelector; -import org.apache.logging.log4j.status.StatusData; -import org.apache.logging.log4j.status.StatusLogger; - -import java.util.List; - -/** - * Preliminary implementation for testing with JBoss. - */ -public class Log4jManager { - - private static final StatusLogger LOGGER = StatusLogger.getLogger(); - - public List getLoggerContexts() { - final Log4jContextFactory factory = (Log4jContextFactory) LogManager.getFactory(); - final ContextSelector selector = factory.getSelector(); - return selector.getLoggerContexts(); - } - - public List getStatusData() { - return LOGGER.getStatusData(); - } -} Index: core/src/main/java/org/apache/logging/log4j/core/jmx/Log4jManagerMBean.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/jmx/Log4jManagerMBean.java (revision 1469363) +++ core/src/main/java/org/apache/logging/log4j/core/jmx/Log4jManagerMBean.java (working copy) @@ -1,32 +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.logging.log4j.core.jmx; - -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.status.StatusData; - -import java.util.List; - -/** - * - */ -public interface Log4jManagerMBean { - - List getLoggerContexts(); - - List getStatusData(); -} Index: core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerConfigAdmin.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerConfigAdmin.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerConfigAdmin.java (working copy) @@ -0,0 +1,106 @@ +/* + * 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.logging.log4j.core.jmx; + +import java.util.List; + +import javax.management.ObjectName; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.config.AppenderRef; +import org.apache.logging.log4j.core.config.LoggerConfig; + +/** + * Implementation of the {@code LoggerConfigAdminMBean} interface. + */ +public class LoggerConfigAdmin implements LoggerConfigAdminMBean { + + private final String contextName; + private final LoggerConfig loggerConfig; + private final ObjectName objectName; + + /** + * Constructs a new {@code LoggerConfigAdmin} with the specified contextName + * and logger config. + * + * @param contextName used in the {@code ObjectName} for this mbean + * @param loggerConfig the instrumented object + */ + public LoggerConfigAdmin(String contextName, LoggerConfig loggerConfig) { + // super(executor); // no notifications for now + this.contextName = Assert.isNotNull(contextName, "contextName"); + this.loggerConfig = Assert.isNotNull(loggerConfig, "loggerConfig"); + try { + String ctxName = Server.escape(this.contextName); + String configName = Server.escape(loggerConfig.getName()); + String name = String.format(PATTERN, ctxName, configName); + objectName = new ObjectName(name); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + /** @see LoggerConfigAdminMBean#PATTERN */ + public ObjectName getObjectName() { + return objectName; + } + + @Override + public String getName() { + return loggerConfig.getName(); + } + + @Override + public String getLevel() { + return loggerConfig.getLevel().name(); + } + + @Override + public void setLevel(String level) { + loggerConfig.setLevel(Level.valueOf(level)); + } + + @Override + public boolean isAdditive() { + return loggerConfig.isAdditive(); + } + + @Override + public void setAdditive(boolean additive) { + loggerConfig.setAdditive(additive); + } + + @Override + public boolean isIncludeLocation() { + return loggerConfig.isIncludeLocation(); + } + + @Override + public String getFilter() { + return String.valueOf(loggerConfig.getFilter()); + } + + @Override + public String[] getAppenderRefs() { + List refs = loggerConfig.getAppenderRefs(); + String[] result = new String[refs.size()]; + for (int i = 0; i < result.length; i++) { + result[i] = refs.get(i).getRef(); + } + return result; + } +} Index: core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerConfigAdminMBean.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerConfigAdminMBean.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerConfigAdminMBean.java (working copy) @@ -0,0 +1,91 @@ +/* + * 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.logging.log4j.core.jmx; + +/** + * The MBean interface for monitoring and managing a {@code LoggerConfig}. + */ +public interface LoggerConfigAdminMBean { + /** ObjectName pattern for LoggerConfigAdmin MBeans. */ + String PATTERN = "org.apache.logging.log4j2:type=LoggerContext,ctx=%s,sub=LoggerConfig,name=%s"; + + /** + * Returns the name of the instrumented {@code LoggerConfig}. + * + * @return the name of the LoggerConfig + */ + String getName(); + + /** + * Returns the {@code LoggerConfig} level as a String. + * + * @return the {@code LoggerConfig} level. + */ + String getLevel(); + + /** + * Sets the {@code LoggerConfig} level to the specified value. + * + * @param level the new {@code LoggerConfig} level. + * @throws IllegalArgumentException if the specified level is not one of + * "OFF", "FATAL", "ERROR", "WARN", "INFO", "DEBUG", "TRACE", + * "ALL" + */ + void setLevel(String level); + + /** + * Returns whether the instrumented {@code LoggerConfig} is additive. + * + * @return {@code true} if the LoggerConfig is additive, {@code false} + * otherwise + */ + boolean isAdditive(); + + /** + * Sets whether the instrumented {@code LoggerConfig} should be additive. + * + * @param additive {@code true} if the instrumented LoggerConfig should be + * additive, {@code false} otherwise + */ + void setAdditive(boolean additive); + + /** + * Returns whether the instrumented {@code LoggerConfig} is configured to + * include location. + * + * @return whether location should be passed downstream + */ + boolean isIncludeLocation(); + + /** + * Returns a string description of all filters configured for the + * instrumented {@code LoggerConfig}. + * + * @return a string description of all configured filters for this + * LoggerConfig + */ + String getFilter(); + + /** + * Returns a String array with the appender refs configured for the + * instrumented {@code LoggerConfig}. + * + * @return the appender refs for the instrumented {@code LoggerConfig}. + */ + String[] getAppenderRefs(); + +} Index: core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerContextAdmin.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerContextAdmin.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerContextAdmin.java (working copy) @@ -0,0 +1,236 @@ +/* + * 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.logging.log4j.core.jmx; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicLong; + +import javax.management.MBeanNotificationInfo; +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.ObjectName; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.config.ConfigurationFactory.ConfigurationSource; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * Implementation of the {@code LoggerContextAdminMBean} interface. + */ +public class LoggerContextAdmin extends NotificationBroadcasterSupport + implements LoggerContextAdminMBean, PropertyChangeListener { + private static final int PAGE = 4 * 1024; + private static final int TEXT_BUFFER = 64 * 1024; + private static final int BUFFER_SIZE = 2048; + private static final StatusLogger LOGGER = StatusLogger.getLogger(); + + private final AtomicLong sequenceNo = new AtomicLong(); + private final ObjectName objectName; + private final LoggerContext loggerContext; + private String customConfigText; + + /** + * Constructs a new {@code LoggerContextAdmin} with the {@code Executor} to + * be used for sending {@code Notification}s asynchronously to listeners. + * + * @param executor + */ + public LoggerContextAdmin(LoggerContext loggerContext, Executor executor) { + super(executor, createNotificationInfo()); + this.loggerContext = Assert.isNotNull(loggerContext, "loggerContext"); + try { + String ctxName = Server.escape(loggerContext.getName()); + String name = String.format(PATTERN, ctxName); + objectName = new ObjectName(name); + } catch (Exception e) { + throw new IllegalStateException(e); + } + loggerContext.addPropertyChangeListener(this); + } + + private static MBeanNotificationInfo createNotificationInfo() { + String[] notifTypes = new String[] { NOTIF_TYPE_RECONFIGURED }; + String name = Notification.class.getName(); + String description = "Configuration reconfigured"; + return new MBeanNotificationInfo(notifTypes, name, description); + } + + @Override + public String getStatus() { + return loggerContext.getStatus().toString(); + } + + @Override + public String getName() { + return loggerContext.getName(); + } + + private Configuration getConfig() { + return loggerContext.getConfiguration(); + } + + @Override + public String getConfigLocationURI() { + if (loggerContext.getConfigLocation() != null) { + return String.valueOf(loggerContext.getConfigLocation()); + } + if (getConfigName() != null) { + return String.valueOf(new File(getConfigName()).toURI()); + } + return ""; + } + + @Override + public void setConfigLocationURI(String configLocation) + throws URISyntaxException, IOException { + LOGGER.debug("---------"); + LOGGER.debug("Remote request to reconfigure using location " + + configLocation); + URI uri = new URI(configLocation); + + // validate the location first: invalid location will result in + // default configuration being configured, try to avoid that... + uri.toURL().openStream().close(); + + loggerContext.setConfigLocation(uri); + LOGGER.debug("Completed remote request to reconfigure."); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (!LoggerContext.PROPERTY_CONFIG.equals(evt.getPropertyName())) { + return; + } + // erase custom text if new configuration was read from a location + if (loggerContext.getConfiguration().getName() != null) { + customConfigText = null; + } + Notification notif = new Notification(NOTIF_TYPE_RECONFIGURED, + getObjectName(), nextSeqNo(), now(), null); + sendNotification(notif); + } + + @Override + public String getConfigText() throws IOException { + if (customConfigText != null) { + return customConfigText; + } + try { + return readContents(new URI(getConfigLocationURI())); + } catch (Exception ex) { + StringWriter sw = new StringWriter(BUFFER_SIZE); + ex.printStackTrace(new PrintWriter(sw)); + return sw.toString(); + } + } + + @Override + public void setConfigText(String configText, String charsetName) { + String old = customConfigText; + customConfigText = Assert.isNotNull(configText, "configText"); + LOGGER.debug("---------"); + LOGGER.debug("Remote request to reconfigure from config text."); + + try { + InputStream in = new ByteArrayInputStream( + configText.getBytes(charsetName)); + ConfigurationSource source = new ConfigurationSource(in); + Configuration updated = ConfigurationFactory.getInstance() + .getConfiguration(source); + loggerContext.setConfiguration(updated); + LOGGER.debug("Completed remote request to reconfigure from config text."); + } catch (Exception ex) { + customConfigText = old; + String msg = "Could not reconfigure from config text"; + LOGGER.error(msg, ex); + throw new IllegalArgumentException(msg, ex); + } + } + + private String readContents(URI uri) throws IOException { + InputStream in = null; + try { + in = uri.toURL().openStream(); + Reader reader = new InputStreamReader(in); + StringBuilder result = new StringBuilder(TEXT_BUFFER); + char[] buff = new char[PAGE]; + int count = -1; + while ((count = reader.read(buff)) >= 0) { + result.append(buff, 0, count); + } + return result.toString(); + } finally { + try { + in.close(); + } catch (Exception ignored) { + } + } + } + + @Override + public String getConfigName() { + return getConfig().getName(); + } + + @Override + public String getConfigClassName() { + return getConfig().getClass().getName(); + } + + @Override + public String getConfigFilter() { + return String.valueOf(getConfig().getFilter()); + } + + @Override + public String getConfigMonitorClassName() { + return getConfig().getConfigurationMonitor().getClass().getName(); + } + + @Override + public Map getConfigProperties() { + return getConfig().getProperties(); + } + + /** @see LoggerContextAdminMBean#PATTERN */ + public ObjectName getObjectName() { + return objectName; + } + + private long nextSeqNo() { + return sequenceNo.getAndIncrement(); + } + + private long now() { + return System.currentTimeMillis(); + } +} \ No newline at end of file Index: core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerContextAdminMBean.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerContextAdminMBean.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerContextAdminMBean.java (working copy) @@ -0,0 +1,133 @@ +/* + * 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.logging.log4j.core.jmx; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; + +/** + * The MBean interface for monitoring and managing a {@code LoggerContext}. + */ +public interface LoggerContextAdminMBean { + /** ObjectName pattern for LoggerContextAdmin MBeans. */ + String PATTERN = "org.apache.logging.log4j2:type=LoggerContext,ctx=%s"; + + /** + * Notification that the {@code Configuration} of the instrumented + * {@code LoggerContext} has been reconfigured. Notifications of this type + * do not carry a message or user data. + */ + String NOTIF_TYPE_RECONFIGURED = "com.apache.logging.log4j.core.jmx.config.reconfigured"; + + /** + * Returns the status of the instrumented {@code LoggerContext}. + * + * @return the LoggerContext status. + */ + String getStatus(); + + /** + * Returns the name of the instrumented {@code LoggerContext}. + * + * @return the name of the instrumented {@code LoggerContext}. + */ + String getName(); + + /** + * Returns the configuration location URI as a String. + * + * @return the configuration location + */ + String getConfigLocationURI(); + + /** + * Sets the configuration location to the specified URI. This will cause the + * instrumented {@code LoggerContext} to reconfigure. + * + * @param configLocationURI location of the configuration file in + * {@link URI} format. + * @throws URISyntaxException if the format of the specified + * configLocationURI is incorrect + * @throws IOException if an error occurred reading the specified location + */ + void setConfigLocationURI(String configLocation) throws URISyntaxException, + IOException; + + /** + * Returns the configuration text, which may be the contents of the + * configuration file or the text that was last set with a call to + * {@code setConfigText}. + * + * @return the configuration text + */ + String getConfigText() throws IOException; + + /** + * Sets the configuration text. This does not replace the contents of the + * configuration file, but does cause the instrumented + * {@code LoggerContext} to be reconfigured with the specified text. + * + * @param configText the configuration text in XML or JSON format + * @param charsetName name of the {@code Charset} used to convert the + * specified configText to bytes + * @throws IllegalArgumentException if a problem occurs configuring from the + * specified text + */ + void setConfigText(String configText, String charsetName); + + /** + * Returns the name of the Configuration of the instrumented LoggerContext. + * + * @return the Configuration name + */ + String getConfigName(); + + /** + * Returns the class name of the {@code Configuration} of the instrumented + * LoggerContext. + * + * @return the class name of the {@code Configuration}. + */ + String getConfigClassName(); + + /** + * Returns a string description of all Filters configured in the + * {@code Configuration} of the instrumented LoggerContext. + * + * @return a string description of all Filters configured + */ + String getConfigFilter(); + + /** + * Returns the class name of the object that is monitoring the configuration + * file for modifications. + * + * @return the class name of the object that is monitoring the configuration + * file for modifications + */ + String getConfigMonitorClassName(); + + /** + * Returns a map with configured properties. + * + * @return a map with configured properties. + */ + Map getConfigProperties(); + +} Index: core/src/main/java/org/apache/logging/log4j/core/jmx/Server.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/jmx/Server.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/jmx/Server.java (working copy) @@ -0,0 +1,248 @@ +/* + * 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.logging.log4j.core.jmx; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.lang.management.ManagementFactory; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +import javax.management.InstanceAlreadyExistsException; +import javax.management.JMException; +import javax.management.MBeanRegistrationException; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.selector.ContextSelector; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * Creates MBeans to instrument various classes in the log4j class hierarchy. + *

+ * All instrumentation for Log4J2 classes can be disabled by setting system + * property {@code -Dlog4j2.disable.jmx=true}. + */ +public class Server { + + private static final String PROPERTY_DISABLE_JMX = "log4j2.disable.jmx"; + + /** + * Either returns the specified name as is, or returns a quoted value + * containing the specified name with the special characters (comma, equals, + * colon, quote, asterisk, or question mark) preceded with a backslash. + * + * @param name + * the name to escape so it can be used as a value in an + * {@link ObjectName}. + * @return the escaped name + */ + public static String escape(String name) { + StringBuilder sb = new StringBuilder(name.length() * 2); + boolean needsQuotes = false; + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + switch (c) { + case ',': + case '=': + case ':': + case '\\': + case '*': + case '?': + sb.append('\\'); + needsQuotes = true; + } + sb.append(c); + } + if (needsQuotes) { + sb.insert(0, '\"'); + sb.append('\"'); + } + return sb.toString(); + } + + /** + * Creates MBeans to instrument the specified selector and other classes in + * the log4j class hierarchy and registers the MBeans in the platform MBean + * server so they can be accessed by remote clients. + * + * @param selector + * starting point in the log4j class hierarchy + * @throws JMException + * if a problem occurs during registration + */ + public static void registerMBeans(ContextSelector selector) + throws JMException { + + // avoid creating Platform MBean Server if JMX disabled + if (Boolean.getBoolean(PROPERTY_DISABLE_JMX)) { + StatusLogger.getLogger().debug( + "JMX disabled for log4j2. Not registering MBeans."); + return; + } + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + registerMBeans(selector, mbs); + } + + /** + * Creates MBeans to instrument the specified selector and other classes in + * the log4j class hierarchy and registers the MBeans in the specified MBean + * server so they can be accessed by remote clients. + * + * @param selector + * starting point in the log4j class hierarchy + * @param mbs + * the MBean Server to register the instrumented objects in + * @throws JMException + * if a problem occurs during registration + */ + public static void registerMBeans(ContextSelector selector, + final MBeanServer mbs) throws JMException { + + if (Boolean.getBoolean(PROPERTY_DISABLE_JMX)) { + StatusLogger.getLogger().debug( + "JMX disabled for log4j2. Not registering MBeans."); + return; + } + final Executor executor = Executors.newFixedThreadPool(1); + registerStatusLogger(mbs, executor); + registerContextSelector(selector, mbs, executor); + + List contexts = selector.getLoggerContexts(); + registerContexts(contexts, mbs, executor); + + for (final LoggerContext context : contexts) { + context.addPropertyChangeListener(new PropertyChangeListener() { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (!LoggerContext.PROPERTY_CONFIG.equals(evt + .getPropertyName())) { + return; + } + // first unregister the MBeans that instrument the + // previous instrumented LoggerConfigs and Appenders + unregisterLoggerConfigs(context, mbs); + unregisterAppenders(context, mbs); + + // now provide instrumentation for the newly configured + // LoggerConfigs and Appenders + try { + registerLoggerConfigs(context, mbs, executor); + registerAppenders(context, mbs, executor); + } catch (Exception ex) { + StatusLogger.getLogger().error( + "Could not register mbeans", ex); + } + } + }); + } + } + + private static void registerStatusLogger(MBeanServer mbs, Executor executor) + throws MalformedObjectNameException, + InstanceAlreadyExistsException, MBeanRegistrationException, + NotCompliantMBeanException { + + StatusLoggerAdmin mbean = new StatusLoggerAdmin(executor); + mbs.registerMBean(mbean, mbean.getObjectName()); + } + + private static void registerContextSelector(ContextSelector selector, + MBeanServer mbs, Executor executor) + throws MalformedObjectNameException, + InstanceAlreadyExistsException, MBeanRegistrationException, + NotCompliantMBeanException { + + ContextSelectorAdmin mbean = new ContextSelectorAdmin(selector); + mbs.registerMBean(mbean, mbean.getObjectName()); + } + + private static void registerContexts(List contexts, + MBeanServer mbs, Executor executor) + throws MalformedObjectNameException, + InstanceAlreadyExistsException, MBeanRegistrationException, + NotCompliantMBeanException { + + for (LoggerContext ctx : contexts) { + LoggerContextAdmin mbean = new LoggerContextAdmin(ctx, executor); + mbs.registerMBean(mbean, mbean.getObjectName()); + } + } + + private static void unregisterLoggerConfigs(LoggerContext context, + MBeanServer mbs) { + String pattern = LoggerConfigAdminMBean.PATTERN; + String search = String.format(pattern, context.getName(), "*"); + unregisterAllMatching(search, mbs); + } + + private static void unregisterAppenders(LoggerContext context, + MBeanServer mbs) { + String pattern = AppenderAdminMBean.PATTERN; + String search = String.format(pattern, context.getName(), "*"); + unregisterAllMatching(search, mbs); + } + + private static void unregisterAllMatching(String search, MBeanServer mbs) { + try { + ObjectName pattern = new ObjectName(search); + Set found = mbs.queryNames(pattern, null); + for (ObjectName objectName : found) { + mbs.unregisterMBean(objectName); + } + } catch (Exception ex) { + StatusLogger.getLogger() + .error("Could not unregister " + search, ex); + } + } + + private static void registerLoggerConfigs(LoggerContext ctx, + MBeanServer mbs, Executor executor) + throws MalformedObjectNameException, + InstanceAlreadyExistsException, MBeanRegistrationException, + NotCompliantMBeanException { + + Map map = ctx.getConfiguration().getLoggers(); + for (String name : map.keySet()) { + LoggerConfig cfg = map.get(name); + LoggerConfigAdmin mbean = new LoggerConfigAdmin(ctx.getName(), cfg); + mbs.registerMBean(mbean, mbean.getObjectName()); + } + } + + private static void registerAppenders(LoggerContext ctx, MBeanServer mbs, + Executor executor) throws MalformedObjectNameException, + InstanceAlreadyExistsException, MBeanRegistrationException, + NotCompliantMBeanException { + + Map> map = ctx.getConfiguration().getAppenders(); + for (String name : map.keySet()) { + Appender appender = map.get(name); + AppenderAdmin mbean = new AppenderAdmin(ctx.getName(), appender); + mbs.registerMBean(mbean, mbean.getObjectName()); + } + } +} Index: core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdmin.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdmin.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdmin.java (working copy) @@ -0,0 +1,122 @@ +/* + * 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.logging.log4j.core.jmx; + +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicLong; + +import javax.management.MBeanNotificationInfo; +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.ObjectName; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.status.StatusListener; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * Implementation of the {@code StatusLoggerAdminMBean} interface. + */ +public class StatusLoggerAdmin extends NotificationBroadcasterSupport implements + StatusListener, StatusLoggerAdminMBean { + + private final AtomicLong sequenceNo = new AtomicLong(); + private final ObjectName objectName; + + /** + * Constructs a new {@code StatusLoggerAdmin} with the {@code Executor} to + * be used for sending {@code Notification}s asynchronously to listeners. + * + * @param executor + */ + public StatusLoggerAdmin(Executor executor) { + super(executor, createNotificationInfo()); + try { + objectName = new ObjectName(NAME); + } catch (Exception e) { + throw new IllegalStateException(e); + } + StatusLogger.getLogger().registerListener(this); + } + + private static MBeanNotificationInfo createNotificationInfo() { + String[] notifTypes = new String[] { NOTIF_TYPE_DATA, + NOTIF_TYPE_MESSAGE }; + String name = Notification.class.getName(); + String description = "StatusLogger has logged an event"; + return new MBeanNotificationInfo(notifTypes, name, description); + } + + @Override + public String[] getStatusDataHistory() { + List data = getStatusData(); + String[] result = new String[data.size()]; + for (int i = 0; i < result.length; i++) { + result[i] = data.get(i).getFormattedStatus(); + } + return result; + } + + @Override + public List getStatusData() { + return StatusLogger.getLogger().getStatusData(); + } + + @Override + public String getLevel() { + return StatusLogger.getLogger().getLevel().name(); + } + + @Override + public void setLevel(String level) { + StatusLogger.getLogger().setLevel(Level.valueOf(level)); + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.logging.log4j.status.StatusListener#log(org.apache.logging + * .log4j.status.StatusData) + */ + @Override + public void log(StatusData data) { + Notification notifMsg = new Notification(NOTIF_TYPE_MESSAGE, + getObjectName(), nextSeqNo(), now(), data.getFormattedStatus()); + sendNotification(notifMsg); + + Notification notifData = new Notification(NOTIF_TYPE_DATA, + getObjectName(), nextSeqNo(), now()); + notifData.setUserData(data); + sendNotification(notifData); + } + + /** @see StatusLoggerAdminMBean#NAME */ + public ObjectName getObjectName() { + return objectName; + } + + private long nextSeqNo() { + return sequenceNo.getAndIncrement(); + } + + private long now() { + return System.currentTimeMillis(); + } +} Index: core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdminMBean.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdminMBean.java (revision 0) +++ core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdminMBean.java (working copy) @@ -0,0 +1,84 @@ +/* + * 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.logging.log4j.core.jmx; + +import java.util.List; + +import org.apache.logging.log4j.status.StatusData; + +/** + * The MBean interface for monitoring and managing the {@code StatusLogger}. + */ +public interface StatusLoggerAdminMBean { + /** Object name of this MBean. */ + String NAME = "org.apache.logging.log4j2:type=StatusLogger"; + + /** + * Notifications with this type have a {@code StatusData} userData object + * and a {@code null} message. + */ + String NOTIF_TYPE_DATA = "com.apache.logging.log4j.core.jmx.statuslogger.data"; + + /** + * Notifications with this type have a formatted status data message string + * but no {@code StatusData} in their userData field. + */ + String NOTIF_TYPE_MESSAGE = "com.apache.logging.log4j.core.jmx.statuslogger.message"; + + /** + * Returns a list with the most recent {@code StatusData} objects in the + * status history. The list has up to 200 entries by default but the length + * can be configured with system property {@code "log4j2.status.entries"}. + *

+ * Note that the returned objects may contain {@code Throwable}s from + * external libraries. + * + * JMX clients calling this method must be prepared to deal with the errors + * that occur if they do not have the class definition for such + * {@code Throwable}s in their classpath. + * + * @return the most recent messages logged by the {@code StatusLogger}. + */ + List getStatusData(); + + /** + * Returns a string array with the most recent messages in the status + * history. The list has up to 200 entries by default but the length can be + * configured with system property {@code "log4j2.status.entries"}. + * + * @return the most recent messages logged by the {@code StatusLogger}. + */ + String[] getStatusDataHistory(); + + /** + * Returns the {@code StatusLogger} level as a String. + * + * @return the {@code StatusLogger} level. + */ + String getLevel(); + + /** + * Sets the {@code StatusLogger} level to the specified value. + * + * @param level the new {@code StatusLogger} level. + * @throws IllegalArgumentException if the specified level is not one of + * "OFF", "FATAL", "ERROR", "WARN", "INFO", "DEBUG", "TRACE", + * "ALL" + */ + void setLevel(String level); + +} Index: core/src/main/resources/META-INF/services/com.sun.tools.jconsole.JConsolePlugin =================================================================== --- core/src/main/resources/META-INF/services/com.sun.tools.jconsole.JConsolePlugin (revision 0) +++ core/src/main/resources/META-INF/services/com.sun.tools.jconsole.JConsolePlugin (working copy) @@ -0,0 +1 @@ +org.apache.logging.log4j.core.jmx.ClientGUIJConsolePlugin Index: src/site/resources/images/jmx-jconsole-editconfig.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Index: src/site/resources/images/jmx-jconsole-editconfig.png =================================================================== --- src/site/resources/images/jmx-jconsole-editconfig.png (revision 0) +++ src/site/resources/images/jmx-jconsole-editconfig.png (working copy) Property changes on: src/site/resources/images/jmx-jconsole-editconfig.png ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +application/octet-stream \ No newline at end of property Index: src/site/resources/images/jmx-jconsole-mbeans.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Index: src/site/resources/images/jmx-jconsole-mbeans.png =================================================================== --- src/site/resources/images/jmx-jconsole-mbeans.png (revision 0) +++ src/site/resources/images/jmx-jconsole-mbeans.png (working copy) Property changes on: src/site/resources/images/jmx-jconsole-mbeans.png ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +application/octet-stream \ No newline at end of property Index: src/site/resources/images/jmx-jconsole-statuslogger.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Index: src/site/resources/images/jmx-jconsole-statuslogger.png =================================================================== --- src/site/resources/images/jmx-jconsole-statuslogger.png (revision 0) +++ src/site/resources/images/jmx-jconsole-statuslogger.png (working copy) Property changes on: src/site/resources/images/jmx-jconsole-statuslogger.png ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +application/octet-stream \ No newline at end of property Index: src/site/resources/images/jmx-standalone-editconfig.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Index: src/site/resources/images/jmx-standalone-editconfig.png =================================================================== --- src/site/resources/images/jmx-standalone-editconfig.png (revision 0) +++ src/site/resources/images/jmx-standalone-editconfig.png (working copy) Property changes on: src/site/resources/images/jmx-standalone-editconfig.png ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +application/octet-stream \ No newline at end of property Index: src/site/resources/images/jmx-standalone-statuslogger.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Index: src/site/resources/images/jmx-standalone-statuslogger.png =================================================================== --- src/site/resources/images/jmx-standalone-statuslogger.png (revision 0) +++ src/site/resources/images/jmx-standalone-statuslogger.png (working copy) Property changes on: src/site/resources/images/jmx-standalone-statuslogger.png ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +application/octet-stream \ No newline at end of property Index: src/site/xdoc/manual/jmx.xml =================================================================== --- src/site/xdoc/manual/jmx.xml (revision 1469363) +++ src/site/xdoc/manual/jmx.xml (working copy) @@ -19,14 +19,142 @@ JMX - Ralph Goers + Remko Popma

- JMX support is incomplete at this time. Patches are welcome! + Log4J2 has built-in support for JMX. + The StatusLogger, ContextSelector, and all LoggerContexts, + LoggerConfigs and Appenders are instrumented with MBeans and can + be remotely monitored and controlled.

+

Also included is a simple client GUI that can be used to + monitor the StatusLogger output, as well as to remotely reconfigure + Log4J with a different configuration file, or to edit the + current configuration directly. +

+ +
+

JMX support is enabled by default. When Log4J initializes, + the StatusLogger, ContextSelector, and all LoggerContexts, + LoggerConfigs and Appenders are instrumented with MBeans. + To disable JMX completely, and prevent these MBeans from being created, + specify system property log4j2.disable.jmx=true when you start + the Java VM. +

+
+ +

To perform local monitoring you don't need to specify any system + properties. The JConsole tool that is included in the Java JDK can be + used to monitor your application. Start JConsole by typing + $JAVA_HOME/bin/jconsole in a command shell. + For more details, see Oracle's documentation on + how to use JConsole.

+ + + +

To enable monitoring and management from remote systems, set the following system property when starting the Java VM. +

+ com.sun.management.jmxremote.port=portNum +

+ In the property above, portNum is the port number through + which you want to enable JMX RMI connections. +

+ For more details, see Oracle's documentation on + Remote + Monitoring and Management.

+ +
+ +
+

The screenshot below shows the Log4J MBeans in JConsole.

+

+
+
+
+

Log4J includes a basic client GUI that can be used to + monitor the StatusLogger output and to remotely modify the Log4J + configuration. The client GUI can be run as a stand-alone application + or as a JConsole plug-in.

+ +

To run the Log4J JMX Client GUI as a JConsole Plug-in, + start JConsole with the following command: +

+

$JAVA_HOME/bin/jconsole -pluginpath /path/to/log4j-core-2.0.jar

+

If you execute the above command and connect to your application, + you will see an extra "Log4j2" tab in the JConsole window. + This tab contains the client GUI, with the StatusLogger selected. + The screenshot below shows the StatusLogger panel in JConsole. +

+

+
+ +

The client GUI also contains a simple editor that can be used + to remotely change the Log4J configuration. +

+ The screenshot below shows the configuration edit panel in JConsole. +

+

+

The configuration edit panel provides two ways to modify + the Log4J configuration: specifying a different configuration location + URI, or modifying the configuration XML directly in the editor panel.

+

If you specify a different configuration location URI and + click the "Reconfigure from Location" button, the specified file + or resource must exist and be readable by the application, + or an error will occur and the configuration will not change. + If an error occurred while processing the contents of the specified resource, + Log4J will keep its original configuration, but the editor panel + will show the contents of the file you specified.

+

+ The text area showing the contents of the configuration file is + editable, and you can directly modify the configuration in this + editor panel. Clicking the "Reconfigure with XML below" button will + send the configuration text to the remote application where it + will be used to reconfigure Log4J on the fly. + This will not overwrite any configuration file. + Reconfiguring with text from the editor happens in memory only and + the text is not permanently stored anywhere. +

+
+
+ +

To run the Log4J JMX Client GUI as a stand-alone application, + run the following command: +

+

$JAVA_HOME/bin/java -cp /path/to/log4j-core-2.0.jar org.apache.logging.log4j.core.jmx.ClientGUI <options>

+

Where options are one of the following:

+
    +
  • <host>:<port>
  • +
  • service:jmx:rmi:///jndi/rmi://<host>:<port>/jmxrmi
  • +
  • service:jmx:rmi://<host>:<port>/jndi/rmi://<host>:<port>/jmxrmi
  • +
+

The port number must be the same as the portNum specified when + you started the application you want to monitor. +

+

For example, if you started your application with these options:

+
com.sun.management.jmxremote.port=33445
+com.sun.management.jmxremote.authenticate=false
+com.sun.management.jmxremote.ssl=false
+

(Note that this disables all security so this is not recommended + for production environments. + Oracle's documentation on + Remote + Monitoring and Management provides details on how to configure + JMX more securely with password authentication and SSL.)

+

Then you can run the client with this command:

+

$JAVA_HOME/bin/java -cp /path/to/log4j-core-2.0.jar org.apache.logging.log4j.core.jmx.ClientGUI localhost:33445

+

The screenshot below shows the StatusLogger panel of the client + GUI when running as a stand-alone application.

+

+

The screenshot below shows the configuration editor panel of the + client GUI when running as a stand-alone application.

+

+ + +
+