
- はじめに
- Platform Logging API
- System.Logger
- System.LoggerFinder
- SLF4JSystemLoggerFinder
- Log4jSystemLoggerFinder
- まとめ
はじめに
Java9 の JPMS(Java Platform Module System) に合わせて導入された、JEP 264: Platform Logging API and Service ですが、大きな変更の陰に隠れて意外とマイナーな存在のままなので、こちらに紹介しておきます。
JEP は以下になります。
Platform Logging API
Platform Logging API は、SFL4J(Simple Logging Facade) や、Apache Commons Logging のような、ロギング用の(最小限の)ファサード・インターフェースを提供します。
典型的には以下のように、java.util.logging.Logger.getLogger() に変えて System.getLogger() を利用します。
import java.lang.System.Logger; private static final Logger logger = System.getLogger(Foo.class.getName()); logger.log(DEBUG, "Initialization is completed.");
Platform Logging の中心となるAPIは以下です。
System.LoggerインターフェースSystem.LoggerFinder抽象クラス
旧来からの java.util.logging API は java.logging モジュールに属しますが、新しいAPIは java.lang パッケージに属しており、java.base モジュールのメンバです。
これにより、ロギングに関するモジュール間の依存が減り、モジュールグラフが簡素化されています。
Platform Logging では、java.util.ServiceLoader API により、System.LoggerFinder を介してロガーの実装をロードします。
System.LoggerFinder により、slf4j(logback) や log4j の実装が見つかればそれらを利用します。
見つからない場合は従来からの java.util.logging が使われます。
System.Logger
System.Logger インターフェースは以下のAPIが提供されています。
public interface Logger { public String getName(); public boolean isLoggable(Level level); public default void log(Level level, String msg) { ... } public default void log(Level level, Supplier<String> msgSupplier) { ... } public default void log(Level level, Object obj) { ... } public default void log(Level level, String msg, Throwable thrown) { ... } public default void log(Level level, Supplier<String> msgSupplier, Throwable thrown) { ... } public default void log(Level level, String format, Object... params) { ... } public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown); public void log(Level level, ResourceBundle bundle, String format, Object... params); }
logger.debug("...") 形式ではなく、logger.log(DEBUG, "...") 形式なのは好みの分かれるところかも知れません。
Level は以下のような定義となっており、不評な JUL のレベル定義が簡素化され、slf4j や log4j のレベル定義に寄った定義になっています。
public enum Level { ALL(Integer.MIN_VALUE), // typically mapped to/from j.u.l.Level.ALL TRACE(400), // typically mapped to/from j.u.l.Level.FINER DEBUG(500), // typically mapped to/from j.u.l.Level.FINEST/FINE/CONFIG INFO(800), // typically mapped to/from j.u.l.Level.INFO WARNING(900), // typically mapped to/from j.u.l.Level.WARNING ERROR(1000), // typically mapped to/from j.u.l.Level.SEVERE OFF(Integer.MAX_VALUE); // typically mapped to/from j.u.l.Level.OFF }
ログの出力は MessageFormat でフォーマットを指定できるため、以下のように文字列パラメータを指定できます。
logger.log(ERROR, "Index {0} out of bounds for length {1}", arg0, arg1);
重い文字列が必要な場合には、サプライヤ Supplier<String> msgSupplier でメッセージを構築することもできます。
slf4j(logback) をバックエンドとして使う場合は slf4j-jdk-platform-logging を使います。
runtimeOnly("org.slf4j:slf4j-jdk-platform-logging:2.0.0") runtimeOnly("ch.qos.logback:logback-classic:1.3.0-beta0")
log4j2 の場合は log4j-jpl を使います。
runtimeOnly("org.apache.logging.log4j:log4j-core:2.18.0") runtimeOnly("org.apache.logging.log4j:log4j-jpl:2.18.0")
System.LoggerFinder
先に述べた通り、LoggerFinder サービスを使うことで、ロガーのバックエンドを設定できます。
META-INF/services/java.lang.System$LoggerFinder に、独自実装した LoggerFinder サービスを登録し、LoggerFinder から Logger を提供します。
LoggerFinder は以下のような実装になっています。
public static abstract class LoggerFinder { static final RuntimePermission LOGGERFINDER_PERMISSION = new RuntimePermission("loggerFinder"); protected LoggerFinder() { this(checkPermission()); } private LoggerFinder(Void unused) { // nothing to do. } private static Void checkPermission() { // ... } public abstract Logger getLogger(String name, Module module); public Logger getLocalizedLogger(String name, ResourceBundle bundle, Module module) { return new LocalizedLoggerWrapper<>(getLogger(name, module), bundle); } public static LoggerFinder getLoggerFinder() { // ... return accessProvider(); } private static volatile LoggerFinder service; @SuppressWarnings("removal") static LoggerFinder accessProvider() { // We do not need to synchronize: LoggerFinderLoader will // always return the same instance, so if we don't have it, // just fetch it again. if (service == null) { PrivilegedAction<LoggerFinder> pa = () -> LoggerFinderLoader.getLoggerFinder(); service = AccessController.doPrivileged(pa, null, LOGGERFINDER_PERMISSION); } return service; } }
サブクラスで public abstract Logger getLogger(String name, Module module) の実装を提供し、自身で実装した System.Logger を提供することで自作したロガーのバックエンドを使うことができます。
ここでは、slf4j(logback) と log4j2 における System.LoggerFinder の実装を確認しておきましょう。
SLF4JSystemLoggerFinder
META-INF/services/java.lang.System$LoggerFinder には以下のサービスが登録されています。
org.slf4j.jdk.platform.logging.SLF4JSystemLoggerFinder
System.LoggerFinder の実装である SLF4JSystemLoggerFinder は以下のようになっており、SLF4JPlarformLoggerFactory に処理が委譲されます。
public class SLF4JSystemLoggerFinder extends System.LoggerFinder { final SLF4JPlarformLoggerFactory platformLoggerFactory = new SLF4JPlarformLoggerFactory(); @Override public System.Logger getLogger(String name, Module module) { SLF4JPlatformLogger adapter = platformLoggerFactory.getLogger(name); return adapter; } }
SLF4JPlarformLoggerFactory では、ロガー名をキーとした SLF4JPlatformLogger のマップが管理されます。
public class SLF4JPlarformLoggerFactory { ConcurrentMap<String, SLF4JPlatformLogger> loggerMap = new ConcurrentHashMap<>(); public SLF4JPlatformLogger getLogger(String loggerName) { SLF4JPlatformLogger spla = loggerMap.get(loggerName); if (spla != null) { return spla; } else { Logger slf4jLogger = LoggerFactory.getLogger(loggerName); SLF4JPlatformLogger newInstance = new SLF4JPlatformLogger(slf4jLogger); SLF4JPlatformLogger oldInstance = loggerMap.putIfAbsent(loggerName, newInstance); return oldInstance == null ? newInstance : oldInstance; } } }
SLF4JPlatformLogger は System.Logger の実装で、 org.slf4j.Logger へ処理を委譲しています。
class SLF4JPlatformLogger implements System.Logger { private final Logger slf4jLogger; // ... }
Log4jSystemLoggerFinder
META-INF/services/java.lang.System$LoggerFinder には以下のサービスが登録されています。
org.apache.logging.log4j.jpl.Log4jSystemLoggerFinder
System.LoggerFinder の実装である Log4jSystemLoggerFinder は以下のようになっており、Log4jSystemLoggerAdapter に処理が委譲されます。
public class Log4jSystemLoggerFinder extends System.LoggerFinder { private final Log4jSystemLoggerAdapter loggerAdapter = new Log4jSystemLoggerAdapter(); @Override public Logger getLogger(String name, Module module) { return loggerAdapter.getLogger(name); } }
Log4jSystemLoggerAdapter では、ロガー名をキーとした SLF4JPlatformLogger のマップが管理されます。
public class Log4jSystemLoggerAdapter extends AbstractLoggerAdapter<Logger> { @Override protected Logger newLogger(String name, LoggerContext context) { return new Log4jSystemLogger(context.getLogger(name)); } @Override protected LoggerContext getContext() { return getContext(LogManager.getFactory().isClassLoaderDependent() ? StackLocatorUtil.getCallerClass(LoggerFinder.class) : null); } }
AbstractLoggerAdapter で以下のようにコンテキスト別に、ロガー名をキーとした SLF4JPlatformLogger のマップが管理されています。
protected final Map<LoggerContext, ConcurrentMap<String, L>> registry = new ConcurrentHashMap<>();
newLogger(String name, LoggerContext context) でインスタンス化する Log4jSystemLogger は System.Logger の実装で、 org.apache.logging.log4j.spi.ExtendedLogger へ処理を委譲しています。
public class Log4jSystemLogger implements System.Logger { private final ExtendedLogger logger; // ... }
まとめ
JEP 264: Platform Logging API and Service について簡単ではありますが、そして今更ではありますが、紹介しました。
ライブラリプロジェクトでは、なるべく依存を減らしたいため、ロギングライブラリに何を使うかは悩ましいところでした。 JUL がもう少しマシなら良かっただけの話ではあります。
これからは、System.Logger で書いておき、バックエンドは利用側に任せる形が良いでしょう。