(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.
+ 
+
+
+
+