この記事はkdnaktの1人 Advent Calendar 2020の24日目の記事です(2日遅れ)。
2020年は会社でKotlin dojoを主催して週1回30分Kotlinと戯れていました。12月はその集大成ということで、Kotlin/NativeでHTTPサーバーを作ってみたいと思います。どこまでできるか、お楽しみ……。
[Kotlin/Native用ログライブラリ]
Kotlin/Nativeでアクセスログをファイルに書き込むにあたって、ちょうどよいライブラリがないか探してみました。
2020年12月現在、AAkira/Napier、florent37/Multiplatform-Log、MicroUtils/kotlin-loggingなど、いくつかKotlin Multiplatform対応のログライブラリはあるのですが、Kotlin/Nativeをサポートしているものは見つかりませんでした。
仕方がないのでログをファイルに書き込む仕組みを自作してみます。
[アクセスログをファイルに書き込む]
ファイルへの書き込みはこちらのブログ記事を参考に実装しました。C言語同様に、fopen()
でファイルを開き、fputs()
でファイルへログを出力します。
Logger.kt
を以下の内容で作成します。
package com.kdnakt.kttpd import kotlinx.cinterop.memScoped import platform.posix.EOF import platform.posix.fclose import platform.posix.fopen import platform.posix.fputs import kotlinx.datetime.* enum class LogLevel(val logPrefix: String) { ERROR("[ERROR]"), INFO("[INFO ]"), DEBUG("[DEBUG]"); } class Logger(val path: String, val level: LogLevel = LogLevel.INFO) fun Logger.error(log: String) { write(log, LogLevel.ERROR) } fun Logger.info(log: String) { write(log, LogLevel.INFO) } fun Logger.debug(log: String) { write(log, LogLevel.DEBUG) } private fun Logger.write(log: String, minLevel: LogLevel) { if (level < minLevel) return val logString = "${minLevel.logPrefix} ${now()} $log" println(logString) val file = fopen(path, "a") ?: throw IllegalArgumentException("Cannot open output file $path") try { memScoped { if(fputs("$logString\n", file) == EOF) throw Error("File write error") } } finally { fclose(file) } } private fun now() = Clock.System.now().toLocalDateTime(TimeZone.of("Asia/Tokyo"))
三段階でログレベルを設定できるようにしています。KotlinではEnumの定義順でordinal(順序)が0始まりでプロパティに追加され、比較演算が可能です。
Enum Classes - Kotlin Programming Language
また、ログ出力時の接頭辞としてログレベルを出力したかったので、LogLevel
に直接文字列のプロパティとして持たせています。Kotlin/NativeではString.format()
関数が使えず、パディングを調整するのが面倒だったので、このような雑な実装になっています。
ファイルへの書き込み部分は毎回ファイルを開くようになっておりパフォーマンスがやや心配ですが、今後の改善課題としておきます。
main.kt
の実装を以下のように修正し、起動時引数でログレベルを指定できるようにします。
fun main(args: Array<String>) { val argsParser = ArgParser("kttpd") val port by argsParser.option(ArgType.Int, shortName="p").default(8080) val logLevel by argsParser.option(ArgType.Choice( listOf("error", "info", "debug") ), shortName = "l").default("info") argsParser.parse(args) val log = Logger("access_log", LogLevel.valueOf(logLevel.toUpperCase())) log.info("kttpd start: localhost:$port") // 省略 }
gradle build
でビルドし、ログレベルを指定せずに起動し、curl localhost:8080/index.html
でアクセスすると以下のようなログが残ります。
起動時引数で--logLevel debug
と指定すると、以下のようにデバッグログがターミナルに出力されます。
ログファイルにも同じ内容が記録されています。
[まとめ]
- Kotlin/Native用のログライブラリがなかったのでログ出力の仕組みを自作した
- 実装中のコードは以下のリポジトリにまとめてある