Motivation
Kotlin Multiplatformの構成がDroidKaigi2019 に導入されたことで、日本ではかなり注目度が高まっているように感じる。(嬉しい)
ご存知の通りKotlin Multiplatform ProjectのCommon moduleでは、Kotlinで書かれた(各プラットフォームに依存しない)コードしか使用することが出来ない。
サンプル程度の量であればそこまで問題ないが、プロジェクトレベルの開発となるとどうしても必要となってくるのが、ログライブラリだ。
現状Common moduleで使えるのは println
のような標準出力のみとなっているため、表示場所の情報等が一切表示されない問題と、リリース用の配布用アプリにもそのままログが出力されてしまう問題がある。
そこで、Napierという名前のKotlin Multiplatform用のログライブラリを作った。
https://github.com/AAkira/Napier
使い方
Dependencies
jCenterにも上げたので、通常はjCenter経由で取得の方が楽だと思う。
gradleファイルに以下を記述していく。
repositories {
jCenter()
}
def napierVersion = "0.0.1"
kotlin {
...
sourceSets {
commonMain {
dependencies {
implementation "com.github.aakira:napier:$napierVersion"
}
}
androidMain {
dependencies {
implementation "com.github.aakira:napier-android:$napierVersion"
}
}
iosMain {
dependencies {
implementation "com.github.aakira:napier-ios:$napierVersion"
}
}
jsMain {
dependencies {
implementation "com.github.aakira:napier-js:$napierVersion"
}
}
jvmMain {
dependencies {
implementation "com.github.aakira:napier-jvm:$napierVersion"
}
}
}
}
Common
例えばこんな感じのコードをCommonに書いてみる。
import kotlinx.coroutines.delay
class Sample {
fun hello(): String {
Napier.v("Hello napier")
Napier.d("optional tag", tag = "your tag")
return "Hello Napier"
}
suspend fun suspendHello(): String {
Napier.i("Hello")
runBlocking {
delay(3000L)
}
Napier.w("Napier!")
return "Suspend Hello Napier"
}
fun handleError() {
try {
throw Exception("throw error")
} catch(e: Exception) {
Napier.e("Napier Error", e)
}
}
}
そうすると各プラットフォームにはこのように表示される。
- android
- ios
androidではLogcat
を使っていて、iOSはprint
、jvmではprintln
、jsではconsole.log
を使っている。
それぞれなんとなく見たことある形式だとは思うが、androidはTimber
というログライブラリを真似ている。
ただこのTimber、Multiplatform対応をしようとしている。ただJakeにはつい最近子供が生まれて休職しているみたいだし、彼は物凄く多くのライブラリをメンテしているのでいつ対応されるかわからないので作成した。
Timberがmpp対応したら潔く乗り換えたいと思っているので、それまでの開発に使って欲しいというお気持ちですw (interfaceはなるべく揃えたので移行はとてもしやすいと思います)
iosに関してはioserに聞いてSwifty Beaver
の形式を真似た。
Log Levelに若干差異があったが、そこはandroid側に合わせてしまった。
日付のフォーマット等は変えられるようにしているので、適宜プロジェクトに合わせて変えて欲しい。
Initialize
プロダクト開発をしていると、Debug buildではログを出したいけどRelease buildではログは吐かずにエラーログだけCrashlyticsや自前のログサーバに送りたくなる。
そこでNapierでは、Napier.base()
にAntilog
を指定することで挙動を変えられるようにしている。DebugAntilog
というのは例にあるようにログの書いてある場所を表示する機能で、ライブラリ側で提供している。CrashlyticsAntilog
に関しては、各々やりたい事が異なると思うので、サンプルを参考にしてプロダクトの要望に合う形にカスタマイズして使って頂ければ。
サンプルのCrashlyticsAntilogでは、エラーログ以上をCrashlyticsに送っている。このやり方は、前に担当していたプロジェクトでcatch出来たエラーに関してはアプリを落とさずにCrashlyticsで送信してstacktraceを見て都度対応していたので、参考になれば。
- android
Application classで初期化をしている。例ではわかりやすいようにBuildConfig
を用いているが、通常はDIで切り分けるのが定石だと思います。
if (BuildConfig.DEBUG) {
// Debug build
// init napier
Napier.base(DebugAntilog())
} else {
// Others(Release build)
// init firebase crashlytics
Fabric.with(this, Crashlytics())
// init napier
Napier.base(CrashlyticsAntilog(this))
}
- ios
ios側もAppDelegateで初期化します。ios側はandroidとは異なりNapierの初期化コードをKotlin側にも書く必要があります。
fun releaseBuild(antilog: Antilog) {
Napier.base(antilog)
}
#if DEBUG
// Debug build
// init napier
NapierProxyKt.debugBuild()
#else
// Others(Release build)
// init firebase crashlytics
FirebaseApp.configure()
Fabric.with([Crashlytics.self])
// init napier
NapierProxyKt.releaseBuild(antilog: CrashlyticsAntilog(
crashlyticsAddLog: { priority, tag, message in
let args = [tag, message].compactMap { $0 }
CLSLogv("%@", getVaList(args))
return .init()
},
crashlyticsSendLog: { throwable in
Crashlytics.sharedInstance().recordError(throwable)
return .init()
}))
#endif
以上の初期化の設定が終われば、Debug時にはCommon moduleに書いたログが各プラットフォームに良い感じに表示され、Release時には致命的なエラーのみCrashlyticsに送信され、ログ表示は行われなくなる。
名前の由来
Timberというライブラリは木材とLogがかかっていて、初期化は
Timber.plant(tree)
で行う。
Treeはカスタマイズしたログの振る舞いを記述するクラスとなっている。
なんともオシャレ。
そこで、こちらは対数のLogとかけ対数の発見者であるジョン・ネイピア から名前を頂戴した。 Napierの初期化は
Napier.base(antilog)
となっており、Napierの底にantilogarithm
を指定する形式にした。Timber程意味は噛み合っていないが、これはこれで気に入っている。
また、ロゴは自然対数の底ネイピア数eを逆にしている。
何故かと言うと
ご存知の通りy = e^x
の逆関数はy = logx
そうLogになるのだ。オシャレ
Kotlin Multiplatform Project開発する方は是非使ってみてフィードバック、PRをして頂けると助かる⭐️