こんにちは。GMOアドマーケティングの@zakisanbaimanです。
はじめに
「A Fluent Logging API for Java」という謳い文句でリリースされたGoogleのLoggingAPIであるFloggerを紹介します。
FloggerはGoogle内で推奨されており、2018年4月から公開されています。
最近ではバージョン0.4が2019年3月にリリースされました。
少し使ってみたところ拡張性や可読性が高くて良かったため、紹介したいと思います。
メリット
Floggerの良い所は以下にあります。
1.高い拡張性
2.無効なレベルでのログ出力は処理自体にコストがかからない
3.有効なログステートメントに対しても高いパフォーマンスを発揮する
4.メソッドチェーンで条件や出力内容を設定できるため可読性が高い
ためしてみる
環境
- 言語:Java8 (1.8.0_144)
- ビルドツール:Gradle 5.4.1
依存関係を記述
▼gradle.build
1 2 3 4 |
dependencies { compile group: 'com.google.flogger', name: 'flogger', version: '0.4' runtime group: 'com.google.flogger', name: 'flogger-system-backend', version: '0.4' } |
flogger-system-backendはバイナリが実行される際に必要となります。
▼floggerだけだと以下の実行時エラーが出てしまうのでご注意ください。
1 2 3 4 5 6 7 8 9 10 11 12 |
/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/bin/java -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/lib/tools.jar:/Users/usr0301554/dev/flogger02/out/production/classes:/Users/usr0301554/.gradle/caches/modules-2/files-2.1/com.google.flogger/flogger/0.4/9c8863dcc913b56291c0c88e6d4ca9715b43df98/flogger-0.4.jar:/Users/usr0301554/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/3.0.1/f7be08ec23c21485b9b5a1cf1654c2ec8c58168d/jsr305-3.0.1.jar greeting.Main Exception in thread "main" java.lang.ExceptionInInitializerError at com.google.common.flogger.backend.Platform.getCallerFinder(Platform.java:142) at com.google.common.flogger.FluentLogger.forEnclosingClass(FluentLogger.java:70) at greeting.Main.<clinit>(Main.java:9) Caused by: java.lang.IllegalStateException: No logging platforms found: com.google.common.flogger.backend.system.DefaultPlatform: java.lang.ClassNotFoundException: com.google.common.flogger.backend.system.DefaultPlatform at com.google.common.flogger.backend.Platform$LazyHolder.loadFirstAvailablePlatform(Platform.java:99) at com.google.common.flogger.backend.Platform$LazyHolder.<clinit>(Platform.java:67) ... 3 more Process finished with exit code 1 |
使用方法
続いてログ出力処理を記述します。
基本的な流れは従来のLoggingAPIとほとんど変わりありません。
1.import
2.FluentLoggerのインスタンス化
3.ログ出力処理を記述
ただし、ログ出力処理を記述方法が以下の通りやや見慣れない形式なので注意です。
logger.<level-selector>.<extensible-API-methods>.<terminal-log-statement>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package greeting; // 1.com.google.common.flogger.FluentLoggerをインポート import com.google.common.flogger.FluentLogger; public class Main { // 2.FluentLoggerのインスタンス作成 private static final FluentLogger logger = FluentLogger.forEnclosingClass(); public static void main(String[] args) { System.out.println("hello"); // 3.ログレベルを指定し、そのままメソッドチェーンで出力内容を設定 logger.atInfo().log("this is info"); logger.atWarning().log("this is warning"); } } |
ログ出力
問題なくログが出力されました。
hello
5 28, 2019 11:08:54 午後 greeting.Main main
情報: this is info
5 28, 2019 11:08:54 午後 greeting.Main main
警告: this is warning
従来のLoggingAPIとの違い
記法上、何が違うのかいくつかケースを見てみましょう。
1.単純にInfoレベルで出力するケース
1 2 3 4 5 |
// logback logger.info("hello logback"); // Flogger logger.atInfo().log("hello flogger"); |
これだけ見ると少し冗長そうに見えます。
2.100件ごとにログを出力するケース
1 2 3 4 5 6 7 8 9 |
// logback private static final AtomicInteger logCounter = new AtomicInteger(); ... if ((logCounter.incrementAndGet() % 100) == 0) { logger.info("My log message {0} [every 100]", arg); } // Flogger logger.atInfo().every(100).log("My log message %s", arg); |
everyメソッドはFloggerがデフォルトで提供しているメソッドですが、1行で書けるのがいいですね。
ちなみに記述数が少なくなるだけではありません。
真っ先にログレベルチェックを行うことで、無効なログレベルの際は実装が何もないシングルトンのNOPインスタンスを返すため、無駄な実行を省くことができます。
またFloggerAPIを拡張することで、デフォルトで備わっているメソッド以外にもログ出力の方法を設定することができます。
▼公式で紹介されている例として、FloggerAPIを拡張してUserLoggerクラスを作りforUserIdメソッドを定義する方法があります。
1 |
logger.at(INFO).forUserId(id).log("Message: %s", param); |
ユーザのidごとにログ出力の仕方を変えたいというような、カスタム用途に対応できます。
高い拡張性
続いてFloggerの高い拡張性について見ていきます。
仮にログ出力処理であるlogメソッド(従来のLoggingAPIでいうところのinfoメソッドやwarnメソッド)をオーバライドしたいケースがあるとします。
その際、従来であればログレベルごとに修正しなければなりませんでしたが、Floggerの場合は出力はlogメソッドに集約されているため、改修箇所が圧倒的に少なくて済みます。
改修箇所が少ないのはログレベルによるものだけでなく、Throwableの指定がログ出力メソッドの引数ではなく、以下のようにwithCauseメソッドで指定していることも理由としてあります。
1 |
logger.atInfo().withCause(exception).log("Log message with: %s", argument); |
まとめ
Floggerの使い方と利点をあげてみましたが、いかがだったでしょうか。
従来のLoggingAPIに比べると拡張性・可読性が高く、導入したいと思わせる設計がなされています。
まだ公開されて1年くらいしか立っておらずドキュメントも整備されていませんが、その中でもこの記事がお役に立てば幸いです。
参考URL
■Floggerの公式GitHubリポジトリ
https://github.com/google/flogger
■公式ドキュメント
https://google.github.io/flogger/
■Googleが新しいJavaロギングFrameworkをリリース
https://www.infoq.com/jp/news/2019/05/java-logging-framework-flogger/