AndroidとJ2SEの両方で処理を共有するためのログラッパー
小ネタ。
ComponentとかModelとか、別にAndroidに依存しない部分はJ2SE用と共通で作りたいわけですが(・ω・)
テストとかも、エミュレータでやると遅いので、PC上で出来るところはしちゃいたいですし。*1
っで、そこでちょっと面倒なのが、ログ出力。
AndroidのLogはstaticなメソッドで実装されていたりするし(´д`;)
っということで、ログラッパークラスを作って、PCでもAndroidでも下記の様にログ出力を書けるようにする方法について。
public class MyComponent { private static final Logger log = LoggerFactory.getLogger(MyComponent.class); public void hoge() { log.trace("うさ☆うさだよもん"); } }
なお、内部的には、Android用の実装はLogクラスを使い、PC用実装はSLF4Jを使う形にします。
共通
まず、ログを抽象化するインターフェースとそのファクトリーインターフェースを下記のような感じで用意します(・ω・)
public interface Logger { boolean isTraceEnabled(); ... boolean isErrorEnabled(); void trace(String msg); void trace(String msg, Throwable t); ... void error(String msg); void error(String msg, Throwable t); }
public interface LoggerFactoryAdapter { Logger getLogger(final Class<?> clazz); }
っで、ファクトリーを取得するクラスを下記の様な感じで作成。
ANDROID_LOGGER_FACTORY_ADAPTER、STANDARD_LOGGER_FACTORY_ADAPTERはそれぞれAndroidとSLF4用のLoggerFactoryAdapter実装クラスのクラス名になります。
public final class LoggerFactory { private static final String ANDROID_LOGGER_FACTORY_ADAPTER = "os.android.log.AndroidLoggerFactoryAdapter"; private static final String STANDARD_LOGGER_FACTORY_ADAPTER = "os.standard.log.SLF4JLoggerFactoryAdapter"; private static final Map<String, LoggerFactoryAdapter> adopters = new ConcurrentHashMap<String, LoggerFactoryAdapter>(); public static Logger getLogger(final Class<?> clazz) { if (Environment.isAndroid()) { return getAdapter(ANDROID_LOGGER_FACTORY_ADAPTER).getLogger(clazz); } return getAdapter(STANDARD_LOGGER_FACTORY_ADAPTER).getLogger(clazz); } private static LoggerFactoryAdapter getAdapter(final String loggerName) { LoggerFactoryAdapter adopter = adopters.get(loggerName); if (adopter == null) { try { adopter = (LoggerFactoryAdapter)Class.forName(loggerName).newInstance(); } catch (InstantiationException e) { throw new WrapRuntimeException(e); } catch (IllegalAccessException e) { throw new WrapRuntimeException(e); } catch (ClassNotFoundException e) { throw new WrapRuntimeException(e); } adopters.put(loggerName, adopter); } return adopter; } private LoggerFactory() { } }
ちなみに、Androidか否かの実行環境の判定は下記の様にしてみましたが、どんなもんでしょう(・ω・)?
public final class Environment { public static boolean isAndroid() { String vmName = System.getProperty("java.vm.name"); return vmName.toLowerCase().contains("dalvik"); } }
Android用ログ実装
そしてAndroid用のログ実装としては、下記の様なクラスを用意。
内容としては、クラス名をTAGとして扱いLogの処理をラップするだけ(・ω・)
public class AndroidLogger implements Logger { private final String tag; public AndroidLogger(final String tag) { this.tag = tag; } @Override public boolean isTraceEnabled() { return Log.isLoggable(tag, Log.VERBOSE); } ... @Override public boolean isErrorEnabled() { return Log.isLoggable(tag, Log.ERROR); } @Override public void trace(final String msg) { Log.v(tag, msg); } ... @Override public void error(final String msg, final Throwable t) { Log.e(tag, msg, t); } }
public class AndroidLoggerFactoryAdapter implements LoggerFactoryAdapter { @Override public Logger getLogger(final Class<?> clazz) { return new AndroidLogger(clazz.getSimpleName()); } }
非Android用(SLF4J版)ログ実装
次はPC用のログ実装について、SLF4J版を使ってこんな感じで。
内容的には、SLF4Jが他のログライブラリ(Log4j、commons-loggingとか)のブリッジでやっているのと同じような事ですだ(・ω・)
public class SLF4JLogger implements Logger { private final org.slf4j.Logger logger; public SLF4JLogger(final org.slf4j.Logger logger) { this.logger = logger; } @Override public boolean isTraceEnabled() { return logger.isTraceEnabled(); } ... @Override public boolean isErrorEnabled() { return logger.isErrorEnabled(); } @Override public void trace(final String msg) { logger.trace(msg); } ... @Override public void error(final String msg, final Throwable t) { logger.error(msg, t); } }
public class SLF4JLocationAwareLogger implements Logger { private static final String FQCN = SLF4JLocationAwareLogger.class.getName(); private final LocationAwareLogger logger; public SLF4JLocationAwareLogger(final LocationAwareLogger logger) { this.logger = logger; } @Override public boolean isTraceEnabled() { return logger.isTraceEnabled(); } ... @Override public boolean isErrorEnabled() { return logger.isErrorEnabled(); } @Override public void trace(final String msg) { logger.log(null, FQCN, LocationAwareLogger.TRACE_INT, msg, null, null); } ... @Override public void error(final String msg, final Throwable t) { logger.log(null, FQCN, LocationAwareLogger.ERROR_INT, msg, null, t); } }
public class SLF4JLoggerFactoryAdapter implements LoggerFactoryAdapter { @Override public Logger getLogger(final Class<?> clazz) { org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(clazz); if (logger instanceof LocationAwareLogger) { return new SLF4JLocationAwareLogger((LocationAwareLogger)logger); } else { return new SLF4JLogger(logger); } } }