001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017 package org.apache.logging.log4j.jmx.gui; 018 019 import java.awt.BorderLayout; 020 import java.awt.Color; 021 import java.awt.Font; 022 import java.awt.event.ActionEvent; 023 import java.io.IOException; 024 import java.io.PrintWriter; 025 import java.io.StringWriter; 026 import java.util.HashMap; 027 import java.util.Map; 028 029 import javax.management.InstanceNotFoundException; 030 import javax.management.MalformedObjectNameException; 031 import javax.management.Notification; 032 import javax.management.NotificationFilterSupport; 033 import javax.management.NotificationListener; 034 import javax.management.ObjectName; 035 import javax.management.remote.JMXConnector; 036 import javax.management.remote.JMXConnectorFactory; 037 import javax.management.remote.JMXServiceURL; 038 import javax.swing.AbstractAction; 039 import javax.swing.JFrame; 040 import javax.swing.JOptionPane; 041 import javax.swing.JPanel; 042 import javax.swing.JScrollPane; 043 import javax.swing.JTabbedPane; 044 import javax.swing.JTextArea; 045 import javax.swing.JToggleButton; 046 import javax.swing.SwingUtilities; 047 import javax.swing.UIManager; 048 import javax.swing.UIManager.LookAndFeelInfo; 049 050 import org.apache.logging.log4j.core.helpers.Assert; 051 import org.apache.logging.log4j.core.jmx.LoggerContextAdminMBean; 052 import org.apache.logging.log4j.core.jmx.StatusLoggerAdminMBean; 053 054 /** 055 * Swing GUI that connects to a Java process via JMX and allows the user to view and 056 * modify the Log4j 2 configuration, as well as monitor status logs. 057 * 058 * @see <a href= 059 * "http://docs.oracle.com/javase/6/docs/technotes/guides/management/jconsole.html" 060 * >http://docs.oracle.com/javase/6/docs/technotes/guides/management/jconsole.html</a > 061 */ 062 public class ClientGUI extends JPanel implements NotificationListener { 063 private static final long serialVersionUID = -253621277232291174L; 064 private final Client client; 065 private JTextArea statusLogTextArea; 066 private JTabbedPane tabbedPane; 067 private JToggleButton wrapLinesToggleButton; 068 069 private final AbstractAction toggleWrapAction = new AbstractAction() { 070 private static final long serialVersionUID = -4214143754637722322L; 071 072 @Override 073 public void actionPerformed(final ActionEvent e) { 074 final boolean wrap = wrapLinesToggleButton.isSelected(); 075 statusLogTextArea.setLineWrap(wrap); 076 } 077 }; 078 079 public ClientGUI(final Client client) throws InstanceNotFoundException, 080 MalformedObjectNameException, IOException { 081 this.client = Assert.isNotNull(client, "client"); 082 createWidgets(); 083 populateWidgets(); 084 registerListeners(); 085 } 086 087 private void createWidgets() { 088 statusLogTextArea = new JTextArea(); 089 statusLogTextArea.setEditable(false); 090 statusLogTextArea.setBackground(this.getBackground()); 091 statusLogTextArea.setForeground(Color.black); 092 statusLogTextArea.setFont(new Font("Monospaced", Font.PLAIN, 093 statusLogTextArea.getFont().getSize())); 094 statusLogTextArea.setWrapStyleWord(true); 095 096 wrapLinesToggleButton = new JToggleButton(toggleWrapAction); 097 wrapLinesToggleButton.setToolTipText("Toggle line wrapping"); 098 final JScrollPane scrollStatusLog = new JScrollPane(statusLogTextArea, // 099 JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, // 100 JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 101 scrollStatusLog.setCorner(JScrollPane.LOWER_RIGHT_CORNER, wrapLinesToggleButton); 102 103 tabbedPane = new JTabbedPane(); 104 tabbedPane.addTab("StatusLogger", scrollStatusLog); 105 106 this.setLayout(new BorderLayout()); 107 this.add(tabbedPane, BorderLayout.CENTER); 108 } 109 110 private void populateWidgets() { 111 112 final StatusLoggerAdminMBean statusAdmin = client.getStatusLoggerAdmin(); 113 final String[] messages = statusAdmin.getStatusDataHistory(); 114 for (final String message : messages) { 115 statusLogTextArea.append(message + "\n"); 116 } 117 118 for (final LoggerContextAdminMBean ctx : client.getLoggerContextAdmins()) { 119 final ClientEditConfigPanel editor = new ClientEditConfigPanel(ctx); 120 tabbedPane.addTab("LoggerContext: " + ctx.getName(), editor); 121 } 122 } 123 124 private void registerListeners() throws InstanceNotFoundException, 125 MalformedObjectNameException, IOException { 126 final NotificationFilterSupport filter = new NotificationFilterSupport(); 127 filter.enableType(StatusLoggerAdminMBean.NOTIF_TYPE_MESSAGE); 128 final ObjectName objName = new ObjectName(StatusLoggerAdminMBean.NAME); 129 client.getConnection().addNotificationListener(objName, this, filter, 130 null); 131 } 132 133 @Override 134 public void handleNotification(final Notification notif, final Object paramObject) { 135 if (StatusLoggerAdminMBean.NOTIF_TYPE_MESSAGE.equals(notif.getType())) { 136 statusLogTextArea.append(notif.getMessage() + "\n"); 137 } 138 } 139 140 /** 141 * Connects to the specified location and shows this panel in a window. 142 * <p> 143 * Useful links: 144 * http://www.componative.com/content/controller/developer/insights 145 * /jconsole3/ 146 * 147 * @param args must have at least one parameter, which specifies the 148 * location to connect to. Must be of the form {@code host:port} 149 * or {@code service:jmx:rmi:///jndi/rmi://<host>:<port>/jmxrmi} 150 * or 151 * {@code service:jmx:rmi://<host>:<port>/jndi/rmi://<host>:<port>/jmxrmi} 152 * @throws Exception if anything goes wrong 153 */ 154 public static void main(final String[] args) throws Exception { 155 if (args.length < 1) { 156 usage(); 157 return; 158 } 159 String serviceUrl = args[0]; 160 if (!serviceUrl.startsWith("service:jmx")) { 161 serviceUrl = "service:jmx:rmi:///jndi/rmi://" + args[0] + "/jmxrmi"; 162 } 163 final JMXServiceURL url = new JMXServiceURL(serviceUrl); 164 final Map<String, String> paramMap = new HashMap<String, String>(); 165 for (final Object objKey : System.getProperties().keySet()) { 166 final String key = (String) objKey; 167 paramMap.put(key, System.getProperties().getProperty(key)); 168 } 169 final JMXConnector connector = JMXConnectorFactory.connect(url, paramMap); 170 final Client client = new Client(connector); 171 final String title = "Log4j JMX Client - " + url; 172 173 SwingUtilities.invokeLater(new Runnable() { 174 @Override 175 public void run() { 176 installLookAndFeel(); 177 try { 178 final ClientGUI gui = new ClientGUI(client); 179 final JFrame frame = new JFrame(title); 180 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 181 frame.getContentPane().add(gui, BorderLayout.CENTER); 182 frame.pack(); 183 frame.setVisible(true); 184 } catch (final Exception ex) { 185 // if console is visible, print error so that 186 // the stack trace remains visible after error dialog is 187 // closed 188 ex.printStackTrace(); 189 190 // show error in dialog: there may not be a console window 191 // visible 192 final StringWriter sr = new StringWriter(); 193 ex.printStackTrace(new PrintWriter(sr)); 194 JOptionPane.showMessageDialog(null, sr.toString(), "Error", 195 JOptionPane.ERROR_MESSAGE); 196 } 197 } 198 }); 199 } 200 201 private static void usage() { 202 final String me = ClientGUI.class.getName(); 203 System.err.println("Usage: java " + me + " <host>:<port>"); 204 System.err.println(" or: java " + me 205 + " service:jmx:rmi:///jndi/rmi://<host>:<port>/jmxrmi"); 206 final String longAdr = " service:jmx:rmi://<host>:<port>/jndi/rmi://<host>:<port>/jmxrmi"; 207 System.err.println(" or: java " + me + longAdr); 208 } 209 210 private static void installLookAndFeel() { 211 try { 212 for (final LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { 213 if ("Nimbus".equals(info.getName())) { 214 UIManager.setLookAndFeel(info.getClassName()); 215 return; 216 } 217 } 218 } catch (final Exception ex) { 219 ex.printStackTrace(); 220 } 221 try { 222 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 223 } catch (final Exception e) { 224 e.printStackTrace(); 225 } 226 } 227 }