View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.jmx.gui;
18  
19  import java.awt.BorderLayout;
20  import java.awt.Color;
21  import java.awt.Font;
22  import java.awt.event.ActionEvent;
23  import java.io.IOException;
24  import java.io.PrintWriter;
25  import java.io.StringWriter;
26  import java.util.HashMap;
27  import java.util.Map;
28  
29  import javax.management.InstanceNotFoundException;
30  import javax.management.MalformedObjectNameException;
31  import javax.management.Notification;
32  import javax.management.NotificationFilterSupport;
33  import javax.management.NotificationListener;
34  import javax.management.ObjectName;
35  import javax.management.remote.JMXConnector;
36  import javax.management.remote.JMXConnectorFactory;
37  import javax.management.remote.JMXServiceURL;
38  import javax.swing.AbstractAction;
39  import javax.swing.JFrame;
40  import javax.swing.JOptionPane;
41  import javax.swing.JPanel;
42  import javax.swing.JScrollPane;
43  import javax.swing.JTabbedPane;
44  import javax.swing.JTextArea;
45  import javax.swing.JToggleButton;
46  import javax.swing.SwingUtilities;
47  import javax.swing.UIManager;
48  import javax.swing.UIManager.LookAndFeelInfo;
49  
50  import org.apache.logging.log4j.core.helpers.Assert;
51  import org.apache.logging.log4j.core.jmx.LoggerContextAdminMBean;
52  import org.apache.logging.log4j.core.jmx.StatusLoggerAdminMBean;
53  
54  /**
55   * Swing GUI that connects to a Java process via JMX and allows the user to view and
56   * modify the Log4j 2 configuration, as well as monitor status logs.
57   * 
58   * @see <a href=
59   *      "http://docs.oracle.com/javase/6/docs/technotes/guides/management/jconsole.html"
60   *      >http://docs.oracle.com/javase/6/docs/technotes/guides/management/jconsole.html</a >
61   */
62  public class ClientGUI extends JPanel implements NotificationListener {
63      private static final long serialVersionUID = -253621277232291174L;
64      private final Client client;
65      private JTextArea statusLogTextArea;
66      private JTabbedPane tabbedPane;
67      private JToggleButton wrapLinesToggleButton;
68  
69      private final AbstractAction toggleWrapAction = new AbstractAction() {
70          private static final long serialVersionUID = -4214143754637722322L;
71  
72          @Override
73          public void actionPerformed(final ActionEvent e) {
74              final boolean wrap = wrapLinesToggleButton.isSelected();
75              statusLogTextArea.setLineWrap(wrap);
76          }
77      };
78  
79      public ClientGUI(final Client client) throws InstanceNotFoundException,
80              MalformedObjectNameException, IOException {
81          this.client = Assert.isNotNull(client, "client");
82          createWidgets();
83          populateWidgets();
84          registerListeners();
85      }
86  
87      private void createWidgets() {
88          statusLogTextArea = new JTextArea();
89          statusLogTextArea.setEditable(false);
90          statusLogTextArea.setBackground(this.getBackground());
91          statusLogTextArea.setForeground(Color.black);
92          statusLogTextArea.setFont(new Font("Monospaced", Font.PLAIN,
93                  statusLogTextArea.getFont().getSize()));
94          statusLogTextArea.setWrapStyleWord(true);
95  
96          wrapLinesToggleButton = new JToggleButton(toggleWrapAction);
97          wrapLinesToggleButton.setToolTipText("Toggle line wrapping");
98          final JScrollPane scrollStatusLog = new JScrollPane(statusLogTextArea, //
99                  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 }