Change Java Logger level without application restart

In any application development logging is fundamental activity which helps developer to debug application issues during development & in production.

Java provides built in Logger API for logging messages to report error or any additional information about the running application.These messages can be of SEVERE ,WARING,INFO,CONFIG,FINE,FINER and FINEST Level. Usually application is configured to log message only of type WARNING and above ( which include SEVERE ) to avoid performance issues of logging lot of messages to log destination ( FILE or Socket ) at other levels .

If you want to change the log level to detail ( such as FINE and above ) , it requires changing the logging.properties file and application restart.

Many times we want to enable FINE logging of running application without application restart so that we can debug issues which are difficult to reproduce.

Using Java Management Extension API & JVM tool interface it is possible to change the running application Logger level  without application restart. This technique is used in JConsole application distributed with JDK.

But often it will be useful to have command line based interface so that it will be faster and does not need UI toolkit on production machine(headless system ).

Following sample code shows , how to change the logger level without application restart.

Note: This solution requires JDK tools ( does not work with JRE ) as it depends on tools.jar distributed with JDK. It is tested using                JDK 1.6.0_26.

This utility needs process id of the running application ( which can be got from jps utility ), name of the logger and level to change.

Steps:

1. Connect to running Java Application  using ‘Virtual Machine’ attach API

2. Get the JMX connector URL, if JMX argent is not loaded load JMX agent.

3. Connect to Management Bean server on the remote Java Virtual Machine.

4. Get access to LoggingMXBean instance .

5. Using MXBean instance change the logger level of the given logger.

For other logging toolkits(log4net … ) you can use either built-in facility ( if it exists ) or polling for configuration file change , if you are using JDK 7  you can also use directory changed notification API to monitor configuration change and reload the configuration for the toolkit.

import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.logging.LogManager;
import java.util.logging.LoggingMXBean;

import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;

/**
 * Sample requires JDK installation,because it uses 
 * VirtualMachine attach api present in tools.jar.
 * Following examples shows how to use this api. For example ,
 * you have java application which using java logger to log
 * information at various logger levels.By default application 
 * is shipped to log at certain level. 
 * Assuming that it is WARING level. If you want to change the
 * logger level to INFO ,without application restart do the 
 * following steps
 * 1.Find tha process id of the running Virtual machine. 
 * 	You can use jps.exe -lvm ( ships with jdk ). 
 * 2.Run following command 
 * ChangeLogLevel -cp $JAVA_HOME\lib\tools.jar:. <process_id> <logger_name> <logger_level>
 * ChangeLogLevel -cp $JAVA_HOME\lib\tools.jar:. 1234 xyz.perf.log FINE
 */
public class ChangeLogLevel {

	private static final String JMX_CONNECTOR_ADDRESS = 
			"com.sun.management.jmxremote.localConnectorAddress";

	
	 // List the existing Java VM on this machine.
	 
	public static void listExistingVMs() {
		try {
			for (VirtualMachineDescriptor vd : VirtualMachine.list()) {
				System.out.println(vd.displayName() + " - " + vd.id());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	
	 // Get JMX URL for given process id
	 
	private static JMXServiceURL getURLForPid(String pid) 
			throws Exception {

		// attach to the target application
		VirtualMachine vm = VirtualMachine.attach(pid);

		// get the connector address
		String connectorAddress = vm.getAgentProperties().getProperty(
				JMX_CONNECTOR_ADDRESS);

		// no connector address, start the JMX agent
		if (connectorAddress == null) {
			String agent = vm.getSystemProperties().
					getProperty("java.home")
					+ File.separator + "lib" + File.separator
					+ "management-agent.jar";
			vm.loadAgent(agent);
			// agent is started, get the connector address
			connectorAddress = vm.getAgentProperties().getProperty(
					JMX_CONNECTOR_ADDRESS);
			if (connectorAddress == null) {
				throw new Exception("Fail to get jmx address");
			}
		}
		return new JMXServiceURL(connectorAddress);
	}

	
	 // Changes the log level of the given logger
	 
	public static void changeLevel(MBeanServerConnection remoteConnection,
			String loggerName, String loggerLevel) throws IOException {

		LoggingMXBean logBean = ManagementFactory.newPlatformMXBeanProxy(
				remoteConnection, LogManager.LOGGING_MXBEAN_NAME,
				LoggingMXBean.class);

		String currentLogLevel = logBean.getLoggerLevel(loggerName);
		System.out.println("Current log level for " + loggerName + " is "
				+ currentLogLevel);
		logBean.setLoggerLevel(loggerName, loggerLevel);
		String changedLogLevel = logBean.getLoggerLevel(loggerName);
		System.out.println("After the change,log level for " + loggerName
				+ " is " + changedLogLevel);
	}

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

		System.out.println("List of Running Java VMs");
		ChangeLogLevel.listExistingVMs();

		if (args.length < 3) {
			System.out
					.println("Usage:ChangeLogLevel <pid> <logger_name> <log_level>");
			System.out
					.println("Example:ChangeLogLevel 2121 com.test.xyz INFO");
			System.out.println("Example : ");
			System.exit(0);
		}

		JMXServiceURL url = null;
		try {
			url = getURLForPid(args[0]);
		} catch (Exception e) {
			e.printStackTrace();
		}

		// Connect to target vm
		JMXConnector connector = null;
		try {
			connector = JMXConnectorFactory.connect(url);

			// Get an MBeanServerConnection on the remote VM.
			MBeanServerConnection remote = connector.
					getMBeanServerConnection();
			changeLevel(remote, args[1], args[2]);

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (connector != null) {
				connector.close();
			}
		}

	}

}

Advertisements

One thought on “Change Java Logger level without application restart

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s