Kotlin-Logging
SLF4Jをベースにした軽量で強力なKotlinロギングライブラリ。ラムダ式による遅延評価文字列をサポートし、クラス名やロガー名の定型コードを削減。Kotlinのイディオムに最適化された現代的なロギングソリューション。
ライブラリ
Kotlin-Logging
概要
Kotlin-LoggingはSLF4Jをベースにした軽量で強力なKotlinロギングライブラリです。ラムダ式による遅延評価文字列をサポートし、クラス名やロガー名の定型コードを削減します。Kotlinのイディオムに最適化された現代的なロギングソリューションとして、2025年Kotlinアプリケーションで最も人気の選択肢として定着しています。Kotlinらしい記述とパフォーマンス最適化により、従来のJavaロギングフレームワークより優位性を持ち、Android開発とサーバーサイドKotlinの両分野で広く採用されています。
詳細
Kotlin-Logging 2025年版は、軽量なマルチプラットフォームロギングフレームワークとして確固たる地位を築いています。既存のロギングフレームワーク(JVM上のSLF4J、JavaScript、WebAssembly、Native環境のネイティブロギング)の薄いラッパーとして機能し、KotlinらしいAPIと遅延評価によるパフォーマンス最適化を提供します。バージョン5以降ではMavenグループIDがio.github.oshaiに変更され、パッケージ構成もio.github.oshai.kotlinloggingに統一されました。構造化ロギング機能としてKLoggingEventBuilderを使用したatメソッド群が追加され、メッセージ、例外、ペイロード(構造化されたメタデータ)を含むリッチなログイベントを作成可能です。
主な特徴
- 軽量なファサード: 既存のロギングフレームワークの上に構築され、それらの機能を活用
- 遅延評価: ラムダ式によりログレベルが有効な場合にのみメッセージが評価されパフォーマンス向上
- Kotlinイディオム: Kotlin開発者にとってより自然なAPIを提供
- マルチプラットフォーム対応: JVM、JavaScript、WebAssembly、Native環境で一貫動作
- 構造化ロギング: リッチなログイベント作成による高度なログ管理機能
- ボイラープレートコード削減: ロガーの定義やクラス名指定が不要
メリット・デメリット
メリット
- Kotlinエコシステムでの圧倒的な普及率と豊富な学習リソース
- ラムダ式による遅延評価によるパフォーマンス最適化と省メモリ実行
- ボイラープレートコードの大幅削減による開発効率向上
- 既存のSLF4Jバックエンドとの完全互換性による既存システム統合容易性
- マルチプラットフォーム対応による一貫したログAPI提供
- 構造化ロギングによる高度なログ分析とデバッグ機能
デメリット
- JVM環境でのSLF4J依存性とランタイム実装追加の必要性
- バージョン5以降での後方互換性の課題(パッケージ名、グループID変更)
- 構造化ロギング機能の学習コスト増加
- 複雑な設定が必要な高度な使用例での制約
- デバッグ時のスタックトレース追跡がラムダ式により若干複雑化
- プラットフォーム間での機能差異による統一性の課題
参考ページ
書き方の例
基本セットアップとロガー定義
// build.gradle.kts に依存関係を追加
dependencies {
implementation("io.github.oshai:kotlin-logging-jvm:7.0.0")
implementation("ch.qos.logback:logback-classic:1.4.14") // SLF4J実装
}
// 基本的なロガー定義
import io.github.oshai.kotlinlogging.KotlinLogging
private val logger = KotlinLogging.logger {} // クラス名で自動ロガー作成
class FooWithLogging {
val message = "world"
fun bar() {
logger.debug { "hello $message" } // 遅延評価によるログ
}
}
// カスタムロガー名の指定
private val customLogger = KotlinLogging.logger("MyCustomLogger")
fun main() {
logger.info { "アプリケーションが開始されました" }
customLogger.warn { "カスタムロガーからの警告" }
}
ログレベルと基本的なメッセージ
import io.github.oshai.kotlinlogging.KotlinLogging
private val logger = KotlinLogging.logger {}
fun main() {
// 基本的なログレベル
logger.trace { "これはトレースメッセージです" }
logger.debug { "これはデバッグメッセージです" }
logger.info { "これは情報メッセージです" }
logger.warn { "これは警告メッセージです" }
logger.error { "これはエラーメッセージです" }
// 変数を含む遅延評価メッセージ
val userName = "田中太郎"
val userId = 123
logger.info { "ユーザー $userName (ID: $userId) がログインしました" }
// 条件付きログ(パフォーマンス最適化)
logger.debug {
// この処理は DEBUG レベルが有効な場合のみ実行される
val expensiveCalculation = performComplexCalculation()
"計算結果: $expensiveCalculation"
}
}
fun performComplexCalculation(): String {
// 重い処理のシミュレーション
Thread.sleep(10)
return "複雑な計算の結果"
}
例外処理とエラーログ
import io.github.oshai.kotlinlogging.KotlinLogging
private val logger = KotlinLogging.logger {}
fun processUserData(userData: Map<String, Any>) {
try {
val userId = userData["id"] as? Int ?: throw IllegalArgumentException("User ID is required")
val userName = userData["name"] as? String ?: throw IllegalArgumentException("User name is required")
logger.info { "ユーザーデータ処理開始: $userName (ID: $userId)" }
// ビジネスロジック
validateUserData(userData)
saveUserData(userData)
logger.info { "ユーザーデータ処理完了: $userName" }
} catch (e: IllegalArgumentException) {
logger.error(e) { "ユーザーデータのバリデーションエラー: ${e.message}" }
throw e
} catch (e: Exception) {
logger.error(e) { "ユーザーデータ処理中に予期しないエラーが発生しました" }
throw RuntimeException("ユーザーデータ処理に失敗しました", e)
}
}
fun validateUserData(userData: Map<String, Any>) {
val email = userData["email"] as? String
if (email?.contains("@") != true) {
throw IllegalArgumentException("無効なメールアドレス: $email")
}
}
fun saveUserData(userData: Map<String, Any>) {
// データベース保存処理
logger.debug { "データベースにユーザーデータを保存中..." }
// 実際の保存処理
}
構造化ロギング(KLoggingEventBuilder)
import io.github.oshai.kotlinlogging.KotlinLogging
private val logger = KotlinLogging.logger {}
data class User(val id: Int, val name: String, val email: String)
data class Order(val id: String, val userId: Int, val amount: Double, val status: String)
fun processOrder(user: User, order: Order) {
// 構造化ログイベントの作成
logger.atInfo {
message = "注文処理を開始しました"
payload = mapOf(
"userId" to user.id,
"userName" to user.name,
"orderId" to order.id,
"orderAmount" to order.amount,
"timestamp" to System.currentTimeMillis()
)
}
try {
// 注文処理ロジック
validateOrder(order)
processPayment(order)
updateInventory(order)
logger.atInfo {
message = "注文処理が正常に完了しました"
payload = mapOf(
"orderId" to order.id,
"finalStatus" to "completed",
"processingTime" to measureProcessingTime()
)
}
} catch (e: InsufficientFundsException) {
logger.atWarn {
message = "決済処理で残高不足エラーが発生しました"
cause = e
payload = mapOf(
"orderId" to order.id,
"userId" to user.id,
"requestedAmount" to order.amount,
"availableBalance" to e.availableBalance,
"errorType" to "insufficient_funds"
)
}
} catch (e: Exception) {
logger.atError {
message = "注文処理中に予期しないエラーが発生しました"
cause = e
payload = mapOf(
"orderId" to order.id,
"userId" to user.id,
"errorType" to e.javaClass.simpleName,
"errorMessage" to e.message
)
}
throw e
}
}
class InsufficientFundsException(val availableBalance: Double) : Exception("残高不足")
fun validateOrder(order: Order) { /* validation logic */ }
fun processPayment(order: Order) { /* payment logic */ }
fun updateInventory(order: Order) { /* inventory logic */ }
fun measureProcessingTime(): Long = 150L // ミリ秒
Spring Boot統合
// build.gradle.kts
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("io.github.oshai:kotlin-logging-jvm:7.0.0")
implementation("ch.qos.logback:logback-classic")
}
// application.yml
logging:
level:
com.example: DEBUG
io.github.oshai.kotlinlogging: INFO
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
// UserController.kt
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/api/users")
class UserController(private val userService: UserService) {
companion object {
private val logger = KotlinLogging.logger {}
}
@GetMapping("/{id}")
fun getUser(@PathVariable id: Int): User {
logger.info { "ユーザー取得リクエスト: ID=$id" }
return try {
val user = userService.findById(id)
logger.info { "ユーザー取得成功: ${user.name} (ID: ${user.id})" }
user
} catch (e: UserNotFoundException) {
logger.warn { "ユーザーが見つかりません: ID=$id" }
throw e
} catch (e: Exception) {
logger.error(e) { "ユーザー取得中にエラーが発生しました: ID=$id" }
throw e
}
}
@PostMapping
fun createUser(@RequestBody userRequest: CreateUserRequest): User {
logger.atInfo {
message = "ユーザー作成リクエスト"
payload = mapOf(
"requestedName" to userRequest.name,
"requestedEmail" to userRequest.email
)
}
return userService.create(userRequest)
}
}
Android統合
// build.gradle.kts (app module)
dependencies {
implementation("io.github.oshai:kotlin-logging-android:7.0.0")
implementation("org.slf4j:slf4j-android:1.7.36")
}
// UserRepository.kt
import io.github.oshai.kotlinlogging.KotlinLogging
import android.content.Context
class UserRepository(private val context: Context) {
companion object {
private val logger = KotlinLogging.logger {}
}
suspend fun fetchUserProfile(userId: String): UserProfile {
logger.info { "ユーザープロフィール取得開始: $userId" }
return try {
val profile = apiService.getUserProfile(userId)
logger.atInfo {
message = "ユーザープロフィール取得成功"
payload = mapOf(
"userId" to userId,
"profileId" to profile.id,
"responseTime" to measureApiResponseTime()
)
}
profile
} catch (e: NetworkException) {
logger.atError {
message = "ネットワークエラーによりユーザープロフィール取得に失敗"
cause = e
payload = mapOf(
"userId" to userId,
"networkState" to getNetworkState(),
"retryCount" to e.retryCount
)
}
throw e
}
}
private fun getNetworkState(): String {
// ネットワーク状態の取得
return "connected"
}
private fun measureApiResponseTime(): Long = 250L
}
コルーチンとの統合
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.*
private val logger = KotlinLogging.logger {}
class AsyncTaskProcessor {
suspend fun processTasksConcurrently(tasks: List<Task>) = coroutineScope {
logger.info { "並行タスク処理開始: ${tasks.size}個のタスク" }
val results = tasks.map { task ->
async {
logger.debug { "タスク処理開始: ${task.id}" }
try {
val result = processTask(task)
logger.atInfo {
message = "タスク処理完了"
payload = mapOf(
"taskId" to task.id,
"taskType" to task.type,
"processingTime" to result.processingTimeMs,
"status" to "completed"
)
}
result
} catch (e: Exception) {
logger.atError {
message = "タスク処理でエラーが発生"
cause = e
payload = mapOf(
"taskId" to task.id,
"taskType" to task.type,
"errorType" to e.javaClass.simpleName
)
}
throw e
}
}
}
val completedResults = results.awaitAll()
logger.info { "全タスク処理完了: ${completedResults.size}個の結果を取得" }
completedResults
}
private suspend fun processTask(task: Task): TaskResult {
delay(100) // 非同期処理のシミュレーション
return TaskResult(task.id, 100L)
}
}
data class Task(val id: String, val type: String)
data class TaskResult(val taskId: String, val processingTimeMs: Long)