Kotlin-Logging

SLF4Jをベースにした軽量で強力なKotlinロギングライブラリ。ラムダ式による遅延評価文字列をサポートし、クラス名やロガー名の定型コードを削減。Kotlinのイディオムに最適化された現代的なロギングソリューション。

ロギングKotlinSLF4Jマルチプラットフォーム構造化ログ

ライブラリ

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)