Sunday, 26 October 2014

Dynamically Changing Logging Levels using JMX

This example shows you how you can use Java Management Extensions (JMX) to change your application's logging level dynamically.


Step 1: Create your management interface which consists of all the attributes which can be read or set and all the operations that can be invoked. In this case, we want to change the logging level of our application.

public  interface LoggingMBean {

//Dummy test method
public void sayLevel();
// a read-write attribute called level of type String
public String getLevel();
public void setLevel(String level);

}


Step 2: Create a class which implements the MBean interface.We are going to extend NotificationBroadcasterSupport for notifing on attribute change using notfication listener.


import javax.management.AttributeChangeNotification;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationListener;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

public class Logging extends NotificationBroadcasterSupport implements LoggingMBean {
public final static Logger l = Logger.getLogger(Logging.class.getName());

private long sequenceNumber = 1;
private String level = "info";

public Logging() {
addNotificationListener(new NotificationListener() {
public void handleNotification(Notification notification,Object handback) {
System.out.println("*** Handling new notification ***");
System.out.println("Level: " + notification.getMessage());
System.out.println("Seq: " + notification.getSequenceNumber());
System.out.println("*********************************");
}
}, null, null);
}

@Override
public String getLevel() {
this.level=Logger.getRootLogger().getLevel().toString() ;
return this.level;
}

@Override
public void sayLevel() {
System.out.println("Checking Level: "+level);
}

@Override
public void setLevel(String level) {
String oldLevel = this.level;

Level newLevel ;
    
    if(level.equalsIgnoreCase("debug")){
    newLevel = Level.toLevel(level, Level.DEBUG);
     
    }else if(level.equalsIgnoreCase("warn")){
    newLevel = Level.toLevel(level, Level.WARN); 
     
    }else{
    newLevel = Level.toLevel(level, Level.INFO); 
    }
    Logger.getRootLogger().setLevel(newLevel);


this.level = Logger.getRootLogger().getLevel().toString() ;
Notification n = new AttributeChangeNotification(this,
sequenceNumber++,

System.currentTimeMillis(), "Level changed", "Level",
"String",

oldLevel, this.level);
sendNotification(n);
}

}


Step 3: Create a dummy Application for using the log level testing

public class LogApp {

private String name;

public String getName() {

return name;
}

public void setName(String name) {

this.name = name;
}

public void go(){
if(Logging.l.isTraceEnabled()){
Logging.l.trace("Tracing");}
if(Logging.l.isDebugEnabled()){
Logging.l.debug("Debugging");}
if(Logging.l.isInfoEnabled()){
Logging.l.info("Infoing");}
}


}


Step 4: Register the MBean with the MBeanServer. Also register an HTMLAdaptorServer which allows us to manage an MBeanServer through a web browser.


import java.lang.management.ManagementFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import com.sun.jdmk.comm.HtmlAdaptorServer;

public class SendNotificationApp{

public static void main(String[] args) throws Exception {

String objectName = "com.test.dynamic.log:type=Logging";

MBeanServer server = ManagementFactory.getPlatformMBeanServer();
HtmlAdaptorServer adapter = new HtmlAdaptorServer(8000);
//invoking dummy Log application for testing
LogApp app=new LogApp();
// Construct the ObjectName for the Logging MBean we will register
ObjectName mbeanName = new ObjectName(objectName);

Logging mbean = new Logging();

server.registerMBean(mbean, mbeanName);
server.registerMBean(adapter, new ObjectName("Adaptor:name=html,port=8000"));
adapter.start(); 
// print the default level
String currentLevel = (String) server.getAttribute(mbeanName, "Level");
System.out.println("Current Level: " + currentLe);vel

while(true){


Thread.sleep(2000);
System.out.println("Log Level"+mbean.getLevel());
app.go();
}

// change the attribute value
// String newMessage = "info";
// server.setAttribute(mbeanName, new Attribute("Message", newMessage));

// print the new message
// currentMessage = (String) server.getAttribute(mbeanName, "Message");
//System.out.println("Current Message: " + currentMessage);

}
}


Step 5: Compile. Make sure you have log4j, jmxtools and a log4j properties file in your classpath.

# Root logger option
log4j.rootLogger=INFO, stdout

# Redirect log messages to console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

Step 6: Run SendNotificationApp. You need to add the following JVM properties(with Jdk 7 ,these property settings are not required):

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false


Step 7: Open browser [http://localhost:8000] and navigate to  the app type:Logging,Click on it.

Change the Logging Level from INFO to DEBUG and click Apply button.



Step 8: Initial Output

Log LevelINFO
2014-10-27 00:34:45 INFO  Logging:23 - Infoing
Log LevelINFO
2014-10-27 00:34:47 INFO  Logging:23 - Infoing
Log LevelINFO
2014-10-27 00:34:49 INFO  Logging:23 - Infoing

After Logging Level changed,you will find notification listener invocation(highlighted):

*** Handling new notification ***
Level: Level changed
Seq: 1
*********************************
Log LevelDEBUG
2014-10-27 00:35:27 DEBUG Logging:21 - Debugging
2014-10-27 00:35:27 INFO  Logging:23 - Infoing
Log LevelDEBUG
2014-10-27 00:35:29 DEBUG Logging:21 - Debugging
2014-10-27 00:35:29 INFO  Logging:23 - Infoing

Hope you enjoy it!!