25 2月 2011

Manage Logger Level via JMX

常用的Log library不外乎logback跟log4j,logback自備了JMX控制的功能,只要在logback.xml中加入

<configuration> 
	<jmxConfigurator />
</configuration>

lo4j沒有,但只要簡單地寫個小小Util就可以達成

@ManagedResource(objectName = "log4j:name=Lo4jLoggerManager", description = "log4j logger level manager")
public class Lo4jLoggerManager {
	@ManagedOperation
	@ManagedOperationParameters({@ManagedOperationParameter(name="loggerName", description=""), 
		@ManagedOperationParameter(name="loggerLevel", description="")})
	public void setLoggerLevel(String loggerName, String loggerLevel) {
		Logger logger = Logger.getLogger(loggerName);
		logger.setLevel(Level.toLevel(loggerLevel));
	}
	
	@ManagedOperation
	@ManagedOperationParameters({@ManagedOperationParameter(name="loggerName", description="")})
	public String getLoggerLevel(String loggerName) {
		Logger logger = Logger.getLogger(loggerName);
		Level level = logger.getEffectiveLevel();
		
		return null != level ? level.toString():Logger.getRootLogger().getLevel().toString();
	}
}

Startup MX4J HttpAdaptor with Spring

我用JMX用的並不多,多半而言就是拿來控制Logger 的Level,最多加個清Cache,其餘就沒什麼特別在意的。
控制的介面用jconsole多半還過得去,部份案子還是會使用mx4j的HttpAdaptor來建立Web的操作介面,只是在使用上,要自動帶起HttpAdaptor還是有些困擾。

先看看MX4J 的HttpAdaptor,

public void start() throws IOException {
    final Logger logger = getLogger();
    if (server != null) {
         serverSocket = createServerSocket();
         .....
    } else {
        if (logger.isEnabledFor(Logger.INFO)) logger.info("Start failed, no server target server has been set");
    }
}
呼叫start()後,HttpAdaptor就可以提供Web的操作介面。
不過HttpAdaptor一開始就會先確認MBeanServer "server" 的有無,如果沒有就會寫個Log然後結束。
而這個MBeanServer什麼時候會被加進來呢?
是依JMX規格MBeanRegistration,執行preRegister()後才會帶進來,所以如果想在Spring的設定檔裡,加個init-method="start",是無法讓這個Adaptor正常提供Web操作。

那就看看Spring MBeanExporter的原始碼是如何操作MBean的Lifecycle,看到MBeanExporterLinstener這Interface,所以我們可以透過這interface讓系統自動帶起HttpAdaptor;但實際上執行有點困難,因為Spring 2.5以後提供了一個context:mbean-server,可以直接取得系統中現有的MBeanServer或是自行建立一個,所以比較無從加入MBeanExporterLinstener到MBeanServer中。

最後想想,還是從MBeanRegistration這個介面下手,所以直接extend HttpAdaptor後override postRegister()....

 

public class AutostartHttpAdaptor extends HttpAdaptor {
 
 private static final Logger logger = LoggerFactory.getLogger(AutostartHttpAdaptor.class);
 @Override
 public void postRegister(Boolean registrationDone) {
  super.postRegister(registrationDone);
  
  if (!registrationDone) {
   logger.warn("HttpAdaptor should not be invoked start() without registration success.");
   return;
  }
  
  logger.info("Post Register HttpAdaptor('"+registrationDone+"'):");

  try {
   this.start();
  } catch (IOException e) {
   logger.error("HttpAdaptor startup has been failed.", e);
  }
 }

 @Override
 public void postDeregister() {
  super.postDeregister();
  logger.info("Post Deregister HttpAdaptor():");
  this.stop();
 }
 
}

05 2月 2011

Struts2 上 i18n的小技巧

最近被問到的小問題
網頁程式上我們經常需要產生一些表單上的選項,而選項內容也需要參考到i18n的properties裡的設定,
例如要顯示“是:否“兩個選項,最糟的寫法可以是
<select>
<option value="true"><s:text name="msg.true" /></option>
<option value="false"><s:text name="msg.false" /></option>
</select>
要簡單一點,就用ognl產生一個map或list
map 的做法
<s:select list="#{'true':getText('msg.true'),'false':getText('msg.false')}"></s:select>
list 的做法
<s:select list="{'true','false'}" listValue="%{getText('msg.'+toString())}"></s:select> 
不過利用getText()這個method來取得i18n的文字,就必需extends ActionSupport。

這樣也許還不是最理想的,我比較喜歡利用enum來做,像是“男:女“這種選項
<s:radio list="@idv.elliot.domain.Gender@values()" listValue="%{getText('enum.Gender.'+toString())}"></s:radio>
利用@可以指出enum的Class及values(),再利用getText()取得i18n的設定

%{getText('enum.Gender.'+toString())}是很方便的用法,相信大家一定會想到從Context中傳出其他的map或list,可以用這方式產生選項的應用方式。