In my previous log4j blog I had used JMX to expose management interfaces to change log4j levels dynamically. If you look at the JBoss JMX console in that blog you will see that the parameter names are named as p1 and p2. Not very helpful. By default Spring uses reflection to expose the public methods of the MBean. Parameter names get thrown away once classes are compiled to byte code. No use of it further. Therefore there is no metadata available to print friendlier names in the JMX console.
I could use commons modeler project and externalize my MBean information completely to an XML file OR I can continue to use Spring and use Spring provided commons attribute annotations. Lets get straight to an example.
Note: The use of commons attributes is to attach metadata to classes or methods and have them available at runtime. If you are using Java 5 then commons attributes is not the best approach. Use Java 5 annotations since that is the standard now.Commons attributes is useful if you are still in JDK 1.4 world. Spring has Java 5 annotation equivalents for the same stuff described below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
1: package com.aver.jmx; 2: import org.apache.commons.lang.StringUtils; 3: import org.apache.log4j.Level; 4: import org.apache.log4j.Logger; 5: /** 6: * @@org.springframework.jmx.export.metadata.ManagedResource 7: * (description="Manage Log4j settings.", objectName="myapp:name=Log4jLevelChanger") 8: */ 9: public class Log4jLevelChanger { 10: /** 11: * @@org.springframework.jmx.export.metadata.ManagedOperation (description="Change the log level for named logger.") 12: * @@org.springframework.jmx.export.metadata.ManagedOperationParameter(index=0,name="loggerName",description="Logger name") 13: * @@org.springframework.jmx.export.metadata.ManagedOperationParameter(index=1,name="level",description="Log4j level") 14: * 15: * Sets the new log level for the logger and returns the updated level. 16: * 17: * @param loggerName logger name (like com.aver) 18: * @param level level such as debug, info, error, fatal, warn or trave 19: * @return current log level for the named logger 20: */ 21: public String changeLogLevel(String loggerName, String level) { 22: // validate logger name 23: if (StringUtils.isEmpty(loggerName)) { 24: return "Invalid logger name '" + loggerName + "' was specified."; 25: } 26: // validate level 27: if (!isLevelValid(level)) { 28: return "Invalid log level " + level + " was specified."; 29: } 30: // change level 31: switch (Level.toLevel(level).toInt()) { 32: case Level.DEBUG_INT: 33: Logger.getLogger(loggerName).setLevel(Level.DEBUG); 34: break; 35: case Level.INFO_INT: 36: Logger.getLogger(loggerName).setLevel(Level.INFO); 37: break; 38: case Level.ERROR_INT: 39: Logger.getLogger(loggerName).setLevel(Level.ERROR); 40: break; 41: case Level.FATAL_INT: 42: Logger.getLogger(loggerName).setLevel(Level.FATAL); 43: break; 44: case Level.WARN_INT: 45: Logger.getLogger(loggerName).setLevel(Level.WARN); 46: break; 47: } 48: return getCurrentLogLevel(loggerName); 49: } 50: /** 51: * @@org.springframework.jmx.export.metadata.ManagedOperation (description="Return current log level for named logger.") 52: * @@org.springframework.jmx.export.metadata.ManagedOperationParameter(index=0,name="loggerName",description="Logger name") 53: * 54: * Returns the current log level for the specified logger name. 55: * 56: * @param loggerName 57: * @return current log level for the named logger 58: */ 59: public String getCurrentLogLevel(String loggerName) { 60: // validate logger name 61: if (StringUtils.isEmpty(loggerName)) { 62: return "Invalid logger name '" + loggerName + "' was specified."; 63: } 64: return Logger.getLogger(loggerName) != null && Logger.getLogger(loggerName).getLevel() != null ? loggerName 65: + " log level is " + Logger.getLogger(loggerName).getLevel().toString() : "unrecognized logger " 66: + loggerName; 67: } 68: private boolean isLevelValid(String level) { 69: return (!StringUtils.isEmpty(level) && ("debug".equalsIgnoreCase(level) || "info".equalsIgnoreCase(level) 70: || "error".equalsIgnoreCase(level) || "fatal".equalsIgnoreCase(level) || "warn".equalsIgnoreCase(level) || "trace" 71: .equalsIgnoreCase(level))); 72: } 73: } |
This attribute compiler generates additional java classes that will hold the metadata information provided in the attribute tags in the sample code. Make sure to compile the generated source along with your normal compile.
Finally Spring must be configured to use commons attributes. Once this step is done you are in business.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
1: <bean id="httpConnector" class="com.sun.jdmk.comm.HtmlAdaptorServer" init-method="start"> 2: <property name="port" value="12001"/> 3: </bean> 4: <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean"/> 5: <bean id="exporter" class="com.nasd.proctor.jmx.CommonsModelerMBeanExporter" lazy-init="false"> 6: <property name="beans"> 7: <map> 8: <entry key="myapp:name=Log4jLevelChanger" value-ref="com.aver.jmx.Log4jLevelChanger" /> 9: <entry key="myapp:name=httpConnector"><ref bean="httpConnector"/></entry> 10: </map> 11: </property> 12: <property name="server" ref="mbeanServer"/> 13: <property name="assembler"> 14: <ref local="assembler"/> 15: </property> 16: </bean> 17: <bean id="attributeSource" class="org.springframework.jmx.export.metadata.AttributesJmxAttributeSource"> 18: <property name="attributes"> 19: <bean class="org.springframework.metadata.commons.CommonsAttributes"/> 20: </property> 21: </bean> 22: <bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler"> 23: <property name="attributeSource"> 24: <ref local="attributeSource"/> 25: </property> 26: </bean> |
I will not go into any explanations here. The AttributesJmxAttributeSource and MetadataMBeanInfoAssembler beans are the ones that configure Spring to use the commons attribute generated classes and thereby the metadata is available at runtime. Take a look at the generated attribute java source and you will quickly realize what commons-attributes is doing. By default Spring uses org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler which uses reflection to expose all of the public methods as JMX attributes/operations. With commons-attributes you can pick and choose which methods get exposed.
The only other thing to note; In the XML configuration above I start a JMX server (from Sun). Whether you want to use Sun’s reference JMX console or use a commercial tool (or open source tool like JManage) is your choice. Similarly I chose to create my own MBeanServer. You can tag along with your containers MBean Server if you prefer.