- はじめに
- 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 で書いておき、バックエンドは利用側に任せる形が良いでしょう。