With WildFly 11 you can now listen to JMX notifications on a server lifecycle events.
A server will now send AttributeChangeNotifications for the attributes RuntimeConfigurationState and RunningState on the object named "jboss.root:type=state".
What does this mean to me ?
There are 2 kinds of events on a WildFly server lifecyle :
* those that concern the state of the model configuration compared to the running configuration : restart-required, reload-required, starting, stopping, stopped and ok.
* those that concern the running state of a server : stopped, starting, pre-suspended, suspended, stopping, suspending, admin_only and normal. Please note that you can only suspend and resume standalone or managed servers instance.
So now you can simply register a javax.management.NotificationListener on "jboss.root:type=state" and get all those notifications.
You can filter on the attribute name to get only those that you are interested in.
Some code
Let's create a simple javax.management.NotificationListener
public class StateNotificationListener implements NotificationListener { public static final String RUNTIME_CONFIGURATION_FILENAME = "runtime-configuration-notifications.txt"; public static final String RUNNING_FILENAME = "running-notifications.txt"; private final Path targetFile; public ControlledStateNotificationListener() { this.targetFile = Paths.get("notifications/data").toAbsolutePath(); init(targetFile); } protected Path getRuntimeConfigurationTargetFile() { return this.targetFile.resolve(RUNTIME_CONFIGURATION_FILENAME); } protected Path getRunningConfigurationTargetFile() { return this.targetFile.resolve(RUNNING_FILENAME); } protected final void init(Path targetFile) { if (!Files.exists(targetFile)) { try { Files.createDirectories(targetFile); if (!Files.exists(targetFile.resolve(RUNTIME_CONFIGURATION_FILENAME))) { Files.createFile(targetFile.resolve(RUNTIME_CONFIGURATION_FILENAME)); } if (!Files.exists(targetFile.resolve(RUNNING_FILENAME))) { Files.createFile(targetFile.resolve(RUNNING_FILENAME)); } } catch (IOException ex) { Logger.getLogger(StateNotificationListener.class).error("Problem handling JMX Notification", ex); } } } @Override public void handleNotification(Notification notification, Object handback) { AttributeChangeNotification attributeChangeNotification = (AttributeChangeNotification) notification; if ("RuntimeConfigurationState".equals(attributeChangeNotification.getAttributeName())) { writeNotification(attributeChangeNotification, getRuntimeConfigurationTargetFile()); } else { writeNotification(attributeChangeNotification, getRunningConfigurationTargetFile()); } } private void writeNotification(AttributeChangeNotification notification, Path path) { try (BufferedWriter in = Files.newBufferedWriter(path, StandardCharsets.UTF_8, StandardOpenOption.APPEND)) { in.write(String.format("%s %s %s %s", notification.getType(), notification.getSequenceNumber(), notification.getSource().toString(), notification.getMessage())); in.newLine(); in.flush(); } catch (IOException ex) { Logger.getLogger(StateNotificationListener.class).error("Problem handling JMX Notification", ex); } } }
Now we can just deploy it using a SAR and registering our listener :
mbeanServer.addNotificationListener(new ObjectName("jboss.root:type=state"), new StateNotificationListener(), null, null);
and here we are, ready to listen to all those events.
Using a Java Agent
Another way to register such a listener is to use a Java agent. This requires us to write such an agent like this ( you may find the whole agent source code attached) :
public class WildFlyStateAgent { public static void premain(String agentArgs) { try { final Path runningTargetFile = new File("notifications/data/running-states.txt").toPath(); final Path runtimeConfigurationTargetFile = new File("notifications/data/runtime-configuration-states.txt").toPath(); Files.createDirectories(runningTargetFile.getParent()); Files.deleteIfExists(runningTargetFile); Files.createFile(runningTargetFile); Files.deleteIfExists(runtimeConfigurationTargetFile); Files.createFile(runtimeConfigurationTargetFile); final ObjectName name = new ObjectName("jboss.root:type=state"); final MBeanServer server = ManagementFactory.getPlatformMBeanServer(); MBeanServerNotificationFilter filter = new MBeanServerNotificationFilter(); filter.enableAllObjectNames(); server.addNotificationListener(MBeanServerDelegate.DELEGATE_NAME, new NotificationListener() { @Override public void handleNotification(Notification notification, Object handback) { MBeanServerNotification mbs = (MBeanServerNotification) notification; if (MBeanServerNotification.REGISTRATION_NOTIFICATION.equals(mbs.getType()) && name.equals(mbs.getMBeanName())) { try { server.addNotificationListener(name, new NotificationListener() { @Override public void handleNotification(Notification notification, Object handback) { AttributeChangeNotification attributeChangeNotification = (AttributeChangeNotification) notification; if ("RunningState".equals(attributeChangeNotification.getAttributeName())) { try (BufferedWriter in = Files.newBufferedWriter(runningTargetFile, StandardCharsets.UTF_8, StandardOpenOption.APPEND)) { in.write(String.format("%s %s %s %s", notification.getType(), notification.getSequenceNumber(), notification.getSource().toString(), notification.getMessage())); in.newLine(); in.flush(); } catch (IOException ex) { throw new RuntimeException(ex); } } else if ("RuntimeConfigurationState".equals(attributeChangeNotification.getAttributeName())) { try (BufferedWriter in = Files.newBufferedWriter(runtimeConfigurationTargetFile, StandardCharsets.UTF_8, StandardOpenOption.APPEND)) { in.write(String.format("%s %s %s %s", notification.getType(), notification.getSequenceNumber(), notification.getSource().toString(), notification.getMessage())); in.newLine(); in.flush(); } catch (IOException ex) { throw new RuntimeException(ex); } } } }, null, null); } catch (InstanceNotFoundException ex) { throw new RuntimeException(ex); } } } }, filter, null); } catch (MalformedObjectNameException | InstanceNotFoundException | IOException ex) { throw new RuntimeException(ex); } } }
Now we just have to edit the standalone.conf to start our agent when starting the WildFly server instance by changing the $JBOSS_MODULES_SYSTEM_PKGS and adding some $JAVA_OPTS like this :
if [ "x$JBOSS_MODULES_SYSTEM_PKGS" = "x" ]; then JBOSS_MODULES_SYSTEM_PKGS="org.jboss.byteman,org.jboss.logmanager,org.wildfly.sample.jmxagent" fi JAVA_OPTS="$JAVA_OPTS -Djava.util.logging.manager=org.jboss.logmanager.LogManager -Xbootclasspath/p:$JBOSS_HOME/modules/system/layers/base/org/jboss/logmanager/main/jboss-logmanager-2.0.4.Final.jar -javaagent:/home/ehsavoie/NetBeansProjects/jmxagent/target/jmxagent.jar"
Comments