BlazeDS のログを Log4j で制御して出力したい

Javaでログ出力するなら Apache log4j を使いたくなります。AMF通信やサーバプッシュで有名な BlazeDS において、この Log4j を使おうと思っても標準機能では上手くいきません。

BlazeDS には flex.messaging.log パッケージにある AbstractTarget クラスを基底とする独自のロギングの仕組みが提供されています。そしてこれらの設定は WEB-INF/flex/services-config.xml で定義されています。

<services-config>    
 (・・・省略・・・)
    <logging>
        <target class="flex.messaging.log.ConsoleTarget" level="INFO">
            <properties>
                <prefix>[BlazeDS] </prefix>
                <includeDate>true</includeDate>
                <includeTime>true</includeTime>
                <includeLevel>true</includeLevel>
                <includeCategory>true</includeCategory>
            </properties>
            <filters>
            </filters>
        </target>
    </logging>
</services-config>

標準のコンソール出力は上記のとおりです。要するに Log4j で出力するターゲットクラスを作って指定すれば、期待する実装が可能になります。

そしていきなりですが、作成したクラスは下記のとおりになります。flex.messaging.log.LineFormattedTarget クラスをベースに、ログレベルとタイムスタンプの機能を除いて作りました。これらは Log4j で制御するためです。

package jp.cm.log;

import org.apache.log4j.Logger;

import flex.messaging.log.AbstractTarget;
import flex.messaging.log.LogEvent;
import flex.messaging.config.ConfigMap;
import flex.messaging.util.ExceptionUtil;
import flex.messaging.util.StringUtils;

public class BlazeLog4jTarget extends AbstractTarget {

	protected Logger logger = Logger.getRootLogger();

	protected boolean includeCategory;

	protected String prefix = null;

	public BlazeLog4jTarget() {
		super();
	}

	/**
	 * Initializes the target with id and properties. Subclasses can overwrite.
	 * 
	 * @param id
	 *            id for the target which is ignored.
	 * @param properties
	 *            ConfigMap of properties for the target.
	 */
	public void initialize(String id, ConfigMap properties) {
		super.initialize(id, properties);

		includeCategory = properties.getPropertyAsBoolean("includeCategory",
				false);
		prefix = properties.getPropertyAsString("prefix", null);
	}

	/**
	 * Returns includeCategory property.
	 * 
	 * @return <code>true</code> if category is included; <code>false</code>
	 *         otherwise.
	 */
	public boolean isIncludeCategory() {
		return includeCategory;
	}

	/**
	 * Sets includeCategory property.
	 * 
	 * @param includeCategory
	 */
	public void setIncludeCategory(boolean includeCategory) {
		this.includeCategory = includeCategory;
	}

	/**
	 * Returns prefix property.
	 * 
	 * @return The prefix for log messages.
	 */
	public String getPrefix() {
		return prefix;
	}

	/**
	 * Sets prefix property.
	 * 
	 * @param prefix
	 */
	public void setPrefix(String prefix) {
		this.prefix = prefix;
	}

	/**
	 * This method handles a <code>LogEvent</code> from an associated logger. A
	 * target uses this method to translate the event into the appropriate
	 * format for transmission, storage, or display. This method will be called
	 * only if the event's level is in range of the target's level.
	 */
	@Override
	public void logEvent(LogEvent event) {
		StringBuffer result = new StringBuffer();
		if (prefix != null) {
			result.append(prefix);
		}

		if (includeCategory) {
			result.append("[" + event.logger.getCategory() + "] ");
		}

		result.append(event.message);

		if (event.throwable != null) {
			result.append(StringUtils.NEWLINE).append(
					ExceptionUtil.toString(event.throwable));
		}
		
		switch (event.level) {
		case LogEvent.ALL:
			if (logger.isTraceEnabled()) {
				logger.trace(result.toString());
			}
		case LogEvent.DEBUG:
			if (logger.isDebugEnabled()) {
				logger.debug(result.toString());
			}
			break;
		case LogEvent.INFO:
			if (logger.isInfoEnabled()) {
				logger.debug(result.toString());
			}
			break;
		case LogEvent.WARN:
			logger.warn(result.toString());
			break;
		case LogEvent.ERROR:
			logger.error(result.toString());
			break;
		case LogEvent.FATAL:
			logger.fatal(result.toString());
			break;
		}
	}
}

この BlazeLog4JTarget クラスを定義する services-config.xml は下記のようになります。

<services-config>
    <logging>
        <target class="jp.cm.log.BlazeLog4jTarget" level="INFO">
            <properties>
                <prefix>[BlazeDS] </prefix>
                <includeCategory>true</includeCategory>
            </properties>
            <filters>
            </filters>
        </target>
    </logging>

</services-config>