1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.jmx.gui;
18
19 import java.awt.BorderLayout;
20 import java.awt.Color;
21 import java.awt.Component;
22 import java.awt.Font;
23 import java.awt.event.ActionEvent;
24 import java.io.IOException;
25 import java.io.PrintWriter;
26 import java.io.StringWriter;
27 import java.util.HashMap;
28 import java.util.Map;
29 import java.util.Objects;
30 import java.util.Properties;
31
32 import javax.management.InstanceNotFoundException;
33 import javax.management.JMException;
34 import javax.management.ListenerNotFoundException;
35 import javax.management.MBeanServerDelegate;
36 import javax.management.MBeanServerNotification;
37 import javax.management.MalformedObjectNameException;
38 import javax.management.Notification;
39 import javax.management.NotificationFilterSupport;
40 import javax.management.NotificationListener;
41 import javax.management.ObjectName;
42 import javax.management.remote.JMXConnector;
43 import javax.management.remote.JMXConnectorFactory;
44 import javax.management.remote.JMXServiceURL;
45 import javax.swing.AbstractAction;
46 import javax.swing.JFrame;
47 import javax.swing.JOptionPane;
48 import javax.swing.JPanel;
49 import javax.swing.JScrollPane;
50 import javax.swing.JTabbedPane;
51 import javax.swing.JTextArea;
52 import javax.swing.JToggleButton;
53 import javax.swing.ScrollPaneConstants;
54 import javax.swing.SwingUtilities;
55 import javax.swing.UIManager;
56 import javax.swing.UIManager.LookAndFeelInfo;
57 import javax.swing.WindowConstants;
58
59 import org.apache.logging.log4j.core.jmx.LoggerContextAdminMBean;
60 import org.apache.logging.log4j.core.jmx.Server;
61 import org.apache.logging.log4j.core.jmx.StatusLoggerAdminMBean;
62
63
64
65
66
67
68
69
70
71
72 public class ClientGui extends JPanel implements NotificationListener {
73 private static final long serialVersionUID = -253621277232291174L;
74 private static final int INITIAL_STRING_WRITER_SIZE = 1024;
75 private final Client client;
76 private final Map<ObjectName, Component> contextObjNameToTabbedPaneMap = new HashMap<>();
77 private final Map<ObjectName, JTextArea> statusLogTextAreaMap = new HashMap<>();
78 private JTabbedPane tabbedPaneContexts;
79
80 public ClientGui(final Client client) throws IOException, JMException {
81 this.client = Objects.requireNonNull(client, "client");
82 createWidgets();
83 populateWidgets();
84
85
86 final ObjectName addRemoveNotifs = MBeanServerDelegate.DELEGATE_NAME;
87 final NotificationFilterSupport filter = new NotificationFilterSupport();
88 filter.enableType(Server.DOMAIN);
89 client.getConnection().addNotificationListener(addRemoveNotifs, this, null, null);
90 }
91
92 private void createWidgets() {
93 tabbedPaneContexts = new JTabbedPane();
94 this.setLayout(new BorderLayout());
95 this.add(tabbedPaneContexts, BorderLayout.CENTER);
96 }
97
98 private void populateWidgets() throws IOException, JMException {
99 for (final LoggerContextAdminMBean ctx : client.getLoggerContextAdmins()) {
100 addWidgetForLoggerContext(ctx);
101 }
102 }
103
104 private void addWidgetForLoggerContext(final LoggerContextAdminMBean ctx) throws MalformedObjectNameException,
105 IOException, InstanceNotFoundException {
106 final JTabbedPane contextTabs = new JTabbedPane();
107 contextObjNameToTabbedPaneMap.put(ctx.getObjectName(), contextTabs);
108 tabbedPaneContexts.addTab("LoggerContext: " + ctx.getName(), contextTabs);
109
110 final String contextName = ctx.getName();
111 final StatusLoggerAdminMBean status = client.getStatusLoggerAdmin(contextName);
112 if (status != null) {
113 final JTextArea text = createTextArea();
114 final String[] messages = status.getStatusDataHistory();
115 for (final String message : messages) {
116 text.append(message + '\n');
117 }
118 statusLogTextAreaMap.put(ctx.getObjectName(), text);
119 registerListeners(status);
120 final JScrollPane scroll = scroll(text);
121 contextTabs.addTab("StatusLogger", scroll);
122 }
123
124 final ClientEditConfigPanel editor = new ClientEditConfigPanel(ctx);
125 contextTabs.addTab("Configuration", editor);
126 }
127
128 private void removeWidgetForLoggerContext(final ObjectName loggerContextObjName) throws JMException, IOException {
129 final Component tab = contextObjNameToTabbedPaneMap.get(loggerContextObjName);
130 if (tab != null) {
131 tabbedPaneContexts.remove(tab);
132 }
133 statusLogTextAreaMap.remove(loggerContextObjName);
134 final ObjectName objName = client.getStatusLoggerObjectName(loggerContextObjName);
135 try {
136
137 client.getConnection().removeNotificationListener(objName, this);
138 } catch (final ListenerNotFoundException ignored) {
139 }
140 }
141
142 private JTextArea createTextArea() {
143 final JTextArea result = new JTextArea();
144 result.setEditable(false);
145 result.setBackground(this.getBackground());
146 result.setForeground(Color.black);
147 result.setFont(new Font(Font.MONOSPACED, Font.PLAIN, result.getFont().getSize()));
148 result.setWrapStyleWord(true);
149 return result;
150 }
151
152 private JScrollPane scroll(final JTextArea text) {
153 final JToggleButton toggleButton = new JToggleButton();
154 toggleButton.setAction(new AbstractAction() {
155 private static final long serialVersionUID = -4214143754637722322L;
156
157 @Override
158 public void actionPerformed(final ActionEvent e) {
159 final boolean wrap = toggleButton.isSelected();
160 text.setLineWrap(wrap);
161 }
162 });
163 toggleButton.setToolTipText("Toggle line wrapping");
164 final JScrollPane scrollStatusLog = new JScrollPane(text,
165 ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
166 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
167 scrollStatusLog.setCorner(ScrollPaneConstants.LOWER_RIGHT_CORNER, toggleButton);
168 return scrollStatusLog;
169 }
170
171 private void registerListeners(final StatusLoggerAdminMBean status) throws InstanceNotFoundException,
172 MalformedObjectNameException, IOException {
173 final NotificationFilterSupport filter = new NotificationFilterSupport();
174 filter.enableType(StatusLoggerAdminMBean.NOTIF_TYPE_MESSAGE);
175 final ObjectName objName = status.getObjectName();
176
177 client.getConnection().addNotificationListener(objName, this, filter, status.getContextName());
178 }
179
180 @Override
181 public void handleNotification(final Notification notif, final Object paramObject) {
182 SwingUtilities.invokeLater(new Runnable() {
183 @Override
184 public void run() {
185 handleNotificationInAwtEventThread(notif, paramObject);
186 }
187 });
188 }
189
190 private void handleNotificationInAwtEventThread(final Notification notif, final Object paramObject) {
191 if (StatusLoggerAdminMBean.NOTIF_TYPE_MESSAGE.equals(notif.getType())) {
192 if (!(paramObject instanceof ObjectName)) {
193 handle("Invalid notification object type", new ClassCastException(paramObject.getClass().getName()));
194 return;
195 }
196 final ObjectName param = (ObjectName) paramObject;
197 final JTextArea text = statusLogTextAreaMap.get(param);
198 if (text != null) {
199 text.append(notif.getMessage() + '\n');
200 }
201 return;
202 }
203 if (notif instanceof MBeanServerNotification) {
204 final MBeanServerNotification mbsn = (MBeanServerNotification) notif;
205 final ObjectName mbeanName = mbsn.getMBeanName();
206 if (MBeanServerNotification.REGISTRATION_NOTIFICATION.equals(notif.getType())) {
207 onMBeanRegistered(mbeanName);
208 } else if (MBeanServerNotification.UNREGISTRATION_NOTIFICATION.equals(notif.getType())) {
209 onMBeanUnregistered(mbeanName);
210 }
211 }
212 }
213
214
215
216
217
218
219 private void onMBeanRegistered(final ObjectName mbeanName) {
220 if (client.isLoggerContext(mbeanName)) {
221 try {
222 final LoggerContextAdminMBean ctx = client.getLoggerContextAdmin(mbeanName);
223 addWidgetForLoggerContext(ctx);
224 } catch (final Exception ex) {
225 handle("Could not add tab for new MBean " + mbeanName, ex);
226 }
227 }
228 }
229
230
231
232
233
234
235 private void onMBeanUnregistered(final ObjectName mbeanName) {
236 if (client.isLoggerContext(mbeanName)) {
237 try {
238 removeWidgetForLoggerContext(mbeanName);
239 } catch (final Exception ex) {
240 handle("Could not remove tab for " + mbeanName, ex);
241 }
242 }
243 }
244
245 private void handle(final String msg, final Exception ex) {
246 System.err.println(msg);
247 ex.printStackTrace();
248
249 final StringWriter sw = new StringWriter(INITIAL_STRING_WRITER_SIZE);
250 ex.printStackTrace(new PrintWriter(sw));
251 JOptionPane.showMessageDialog(this, sw.toString(), msg, JOptionPane.ERROR_MESSAGE);
252 }
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268 public static void main(final String[] args) throws Exception {
269 if (args.length < 1) {
270 usage();
271 return;
272 }
273 String serviceUrl = args[0];
274 if (!serviceUrl.startsWith("service:jmx")) {
275 serviceUrl = "service:jmx:rmi:///jndi/rmi://" + args[0] + "/jmxrmi";
276 }
277 final JMXServiceURL url = new JMXServiceURL(serviceUrl);
278 final Properties props = System.getProperties();
279 final Map<String, String> paramMap = new HashMap<>(props.size());
280 for (final String key : props.stringPropertyNames()) {
281 paramMap.put(key, props.getProperty(key));
282 }
283 final JMXConnector connector = JMXConnectorFactory.connect(url, paramMap);
284 final Client client = new Client(connector);
285 final String title = "Log4j JMX Client - " + url;
286
287 SwingUtilities.invokeLater(new Runnable() {
288 @Override
289 public void run() {
290 installLookAndFeel();
291 try {
292 final ClientGui gui = new ClientGui(client);
293 final JFrame frame = new JFrame(title);
294 frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
295 frame.getContentPane().add(gui, BorderLayout.CENTER);
296 frame.pack();
297 frame.setVisible(true);
298 } catch (final Exception ex) {
299
300
301
302 ex.printStackTrace();
303
304
305
306 final StringWriter sr = new StringWriter();
307 ex.printStackTrace(new PrintWriter(sr));
308 JOptionPane.showMessageDialog(null, sr.toString(), "Error", JOptionPane.ERROR_MESSAGE);
309 }
310 }
311 });
312 }
313
314 private static void usage() {
315 final String me = ClientGui.class.getName();
316 System.err.println("Usage: java " + me + " <host>:<port>");
317 System.err.println(" or: java " + me + " service:jmx:rmi:///jndi/rmi://<host>:<port>/jmxrmi");
318 final String longAdr = " service:jmx:rmi://<host>:<port>/jndi/rmi://<host>:<port>/jmxrmi";
319 System.err.println(" or: java " + me + longAdr);
320 }
321
322 private static void installLookAndFeel() {
323 try {
324 for (final LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
325 if ("Nimbus".equals(info.getName())) {
326 UIManager.setLookAndFeel(info.getClassName());
327 return;
328 }
329 }
330 } catch (final Exception ex) {
331 ex.printStackTrace();
332 }
333 try {
334 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
335 } catch (final Exception e) {
336 e.printStackTrace();
337 }
338 }
339 }