ScalaLogging (Alternative)

SLF4Jの上に構築されたScala向けロギングライブラリの代替実装。基本的な機能に焦点を当て、軽量性を重視。Scala-loggingとは異なるアプローチで、よりシンプルなAPIと最小限の依存関係を提供。

ロギングScala型安全マクロベースSLF4J

ライブラリ

ScalaLogging

概要

ScalaLoggingは「Scala向けの便利で高性能なロギングライブラリ」として開発された、SLF4Jをラップする型安全なロギングソリューションです。Scalaの強力なマクロシステムを活用し、コンパイル時の最適化により無効化されたログレベルでのパフォーマンスコストを完全に排除。SLF4Jの軽量ラッパーとして構築され、型安全な文字列補間、遅延評価、豊富なSLF4Jエコシステムとのシームレスな統合を提供しながら、Javaロギングフレームワークとの完全な互換性を維持しています。

詳細

ScalaLogging 2025年版は、Lightbend Labsによって保守される10年以上の本番実証済みの安定性を持つ、Scalaアプリケーションロギングのデファクトスタンダードです。ライブラリのマクロベースアーキテクチャは、コンパイル時にログ呼び出しを条件文に自動変換し、無効化されたログレベルでのランタイムオーバーヘッドを完全に排除。異なる初期化パターンに対応する複数のロギングトレイト(LazyLogging、StrictLogging、AnyLogging)を提供し、構造化ログシナリオでのLoggerTakingImplicitによるコンテキストロギングなどの高度な機能をサポートします。

主な特徴

  • マクロベースパフォーマンス: コンパイル時最適化による完全なゼロオーバーヘッド
  • 型安全文字列補間: Scalaのs"..."補間の直接サポート
  • 複数ロギングトレイト: LazyLogging、StrictLogging、AnyLoggingパターン
  • SLF4J互換性: SLF4Jエコシステムとバックエンドとの完全統合
  • コンテキストロギング: 構造化・コンテキストデータ用LoggerTakingImplicit
  • ゼロ依存関係: 最小限の外部依存関係を持つ軽量ライブラリ

メリット・デメリット

メリット

  • マクロベース最適化によるランタイムチェック排除での優秀なパフォーマンス
  • 型安全文字列補間サポートによるイディオマティックなScalaロギング
  • 既存SLF4Jインフラストラクチャとバックエンドとのシームレス統合
  • 異なるアーキテクチャニーズに対応する複数の初期化パターン
  • コンパイル時の一般的なロギングエラー防止による強力な型安全性
  • SLF4Jに慣れた開発者にとっての最小限の学習コスト

デメリット

  • SLF4Jバックエンドエコシステムに限定、非SLF4Jロギングフレームワーク使用不可
  • 大規模プロジェクトでマクロ依存関係がコンパイル時間を増加させる可能性
  • 複雑なログ分析用の構造化ロギングライブラリより柔軟性に欠ける
  • Javaロギングライブラリと比較して小さなドキュメント・コミュニティリソース
  • コンテキストロギングなどの高度機能には追加の学習投資が必要
  • マクロ機能のためのScalaコンパイラバージョンとの密結合

参考ページ

書き方の例

インストール・セットアップ

// build.sbt - Scala Logging依存関係追加
libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.5"

// SLF4Jバックエンド追加(Logback推奨)
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.4.12"

// 代替バックエンド
libraryDependencies += "org.slf4j" % "slf4j-simple" % "2.0.9"  // シンプルロガー
libraryDependencies += "org.slf4j" % "slf4j-jdk14" % "2.0.9"   // Java Util Logging

// 動作確認
import com.typesafe.scalalogging.Logger
val logger = Logger("test")
logger.info("Scala Loggingが動作しています!")

基本的なログ出力

import com.typesafe.scalalogging.Logger

// 名前によるロガー作成
val logger = Logger("com.example.MyApp")

// 異なるレベルでの基本ログ出力
logger.trace("詳細なトレース情報")
logger.debug("開発用デバッグ情報")
logger.info("アプリケーションフローの一般情報")
logger.warn("警告: 潜在的な問題を検出")
logger.error("エラー: 何かが失敗しました")

// クラスベースロガー
val classLogger = Logger(getClass.getName)
val classOfLogger = Logger(classOf[MyClass])
val genericLogger = Logger[MyClass]  // ClassTag使用

// 文字列補間(推奨)
val userId = 12345
val action = "login"
logger.info(s"ユーザー$userIdがアクション「$action」を実行しました")

// 従来のSLF4Jスタイル(サポート済み)
logger.info("ユーザー{}がアクション「{}」を実行しました", userId, action)

// レベルが有効かチェック
if (logger.isDebugEnabled) {
  val expensiveDebugInfo = computeExpensiveDebugInfo()
  logger.debug(s"デバッグ情報: $expensiveDebugInfo")
}

// マクロが自動的に上記チェックを生成
logger.debug(s"デバッグ情報: ${computeExpensiveDebugInfo()}")  // 自動最適化

ログレベル設定

// LazyLoggingトレイト使用(最も一般的)
import com.typesafe.scalalogging.LazyLogging

class UserService extends LazyLogging {
  def createUser(name: String): User = {
    logger.debug("名前「{}」でユーザーを作成中", name)
    val user = User(name)
    logger.info("ユーザー作成成功: {}", user.id)
    user
  }
  
  def deleteUser(userId: Long): Unit = {
    logger.warn("ユーザー削除: {}", userId)
    // 削除ロジック
    logger.info("ユーザー{}を削除しました", userId)
  }
}

// StrictLoggingトレイト使用(積極的初期化)
import com.typesafe.scalalogging.StrictLogging

class DatabaseService extends StrictLogging {
  // クラスがインスタンス化されると即座にロガーが初期化される
  
  def connect(): Unit = {
    logger.info("データベースに接続中...")
    logger.debug("接続パラメータ: {}", getConnectionParams())
  }
}

// AnyLoggingトレイト使用(カスタムロガー)
import com.typesafe.scalalogging.AnyLogging

class CustomLoggingService extends AnyLogging {
  // カスタム設定用にロガーをオーバーライド
  override protected val logger: Logger = Logger("custom.service")
  
  def process(): Unit = {
    logger.info("カスタムロガーで処理中")
  }
}

// プログラム的レベル制御(SLF4Jバックエンド経由)
import ch.qos.logback.classic.{Level, LoggerContext}
import org.slf4j.LoggerFactory

val context = LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext]
val rootLogger = context.getLogger("ROOT")
rootLogger.setLevel(Level.DEBUG)  // DEBUGレベルに設定

val specificLogger = context.getLogger("com.example.UserService")
specificLogger.setLevel(Level.WARN)  // WARN以上のみ

Scala固有機能

import com.typesafe.scalalogging.Logger

// 複雑な式での文字列補間
val logger = Logger("app")
val user = User("田中", 30)

logger.info(s"ユーザー${user.name}は${user.age}歳です")
logger.debug(s"ユーザー処理中: ${user.toDebugString}")

// スタックトレース付き例外ログ
try {
  riskyOperation()
} catch {
  case ex: SQLException =>
    logger.error("データベースエラーが発生", ex)
  case ex: Exception =>
    logger.error(s"予期しないエラー: ${ex.getMessage}", ex)
}

// when-enabledによる条件付きログ
logger.whenDebugEnabled {
  println("このコードはDEBUGが有効な場合のみ実行されます")
  (1 to 10).foreach(x => println(s"高コストな操作 $x"))
}

logger.whenInfoEnabled {
  val diagnostics = generateDiagnostics()  // 高コストな操作
  logger.info(s"システム診断: $diagnostics")
}

// 構造化ログ用マーカー使用
import org.slf4j.MarkerFactory

val securityMarker = MarkerFactory.getMarker("SECURITY")
val performanceMarker = MarkerFactory.getMarker("PERFORMANCE")

logger.warn(securityMarker, "IPからのログイン試行失敗: {}", clientIp)
logger.info(performanceMarker, "操作完了時間: {}ms", duration)

// パターンマッチング統合
def processResult(result: Either[Error, Success]): Unit = {
  result match {
    case Left(error) =>
      logger.error(s"操作失敗: ${error.message}")
    case Right(success) =>
      logger.info(s"操作成功: ${success.data}")
  }
}

// FutureとTry統合
import scala.concurrent.Future
import scala.util.{Try, Success, Failure}

def asyncOperation(): Future[String] = {
  Future {
    logger.debug("非同期操作開始")
    // 非同期作業
    "result"
  }.andThen {
    case Success(result) =>
      logger.info(s"非同期操作完了: $result")
    case Failure(exception) =>
      logger.error("非同期操作失敗", exception)
  }
}

// Optionとコレクションログ
val optionalValue: Option[String] = Some("値")
optionalValue match {
  case Some(value) =>
    logger.debug(s"値を発見: $value")
  case None =>
    logger.warn("値が見つかりません")
}

val items = List("項目1", "項目2", "項目3")
logger.info(s"${items.length}個の項目を処理中: ${items.mkString(", ")}")

設定ファイル例

<!-- logback.xml - Logback設定 -->
<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>logs/application.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
      <fileNamePattern>logs/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
      <maxFileSize>10MB</maxFileSize>
      <maxHistory>30</maxHistory>
      <totalSizeCap>1GB</totalSizeCap>
    </rollingPolicy>
    <encoder>
      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- アプリケーション固有ロガー -->
  <logger name="com.example.UserService" level="DEBUG" />
  <logger name="com.example.DatabaseService" level="INFO" />
  
  <!-- サードパーティライブラリロガー -->
  <logger name="org.springframework" level="WARN" />
  <logger name="com.zaxxer.hikari" level="INFO" />

  <root level="INFO">
    <appender-ref ref="STDOUT" />
    <appender-ref ref="FILE" />
  </root>
</configuration>
# log4j2.properties - Log4j2設定
status = warn

# コンソールアペンダー
appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n

# ファイルアペンダー
appender.file.type = RollingFile
appender.file.name = FileAppender
appender.file.fileName = logs/scala-app.log
appender.file.filePattern = logs/scala-app-%d{yyyy-MM-dd}-%i.log
appender.file.layout.type = PatternLayout
appender.file.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{50} - %msg%n
appender.file.policies.type = Policies
appender.file.policies.size.type = SizeBasedTriggeringPolicy
appender.file.policies.size.size = 10MB
appender.file.strategy.type = DefaultRolloverStrategy
appender.file.strategy.max = 10

# ロガー
logger.userservice.name = com.example.UserService
logger.userservice.level = debug
logger.userservice.additivity = false
logger.userservice.appenderRef.console.ref = STDOUT

rootLogger.level = info
rootLogger.appenderRef.stdout.ref = STDOUT
rootLogger.appenderRef.file.ref = FileAppender

パフォーマンス最適化

import com.typesafe.scalalogging.{Logger, LazyLogging}

class PerformanceOptimizedService extends LazyLogging {
  
  // マクロ最適化による効率的ログ
  def processLargeDataset(data: List[DataItem]): Unit = {
    // debugが無効時にマクロが自動的にこのチェックを生成
    logger.debug(s"${data.size}個の項目を処理: ${data.map(_.id).mkString(",")}")
    
    data.foreach { item =>
      // debugが有効な場合のみ高コストな計算が実行される
      logger.trace(s"項目処理中: ${item.toDetailedString}")
      processItem(item)
    }
    
    logger.info(s"${data.size}個の項目処理完了")
  }
  
  // 複雑な条件付きログ用whenEnabled使用
  def performComplexOperation(): Unit = {
    val startTime = System.currentTimeMillis()
    
    // 必要時のみ高コストな診断情報を計算
    logger.whenDebugEnabled {
      val memoryUsage = Runtime.getRuntime.totalMemory() - Runtime.getRuntime.freeMemory()
      val threadCount = Thread.activeCount()
      logger.debug(s"操作開始 - メモリ: ${memoryUsage}MB, スレッド: $threadCount")
    }
    
    // メイン操作
    doComplexWork()
    
    val duration = System.currentTimeMillis() - startTime
    logger.info(s"操作完了時間: ${duration}ms")
  }
  
  // 効率的例外ログ
  def handleError(operation: String, ex: Throwable): Unit = {
    ex match {
      case _: IllegalArgumentException =>
        // 予期される例外には簡潔なメッセージ
        logger.warn(s"$operationでの不正引数: ${ex.getMessage}")
      case _ =>
        // 予期しない例外には完全なスタックトレース
        logger.error(s"$operationでの予期しないエラー", ex)
    }
  }
  
  // 高スループットシナリオ用バッチログ
  class BatchLogger(flushInterval: Int = 100) {
    private var messageBuffer = Vector.empty[String]
    private var messageCount = 0
    
    def logInfo(message: String): Unit = {
      messageBuffer = messageBuffer :+ message
      messageCount += 1
      
      if (messageCount >= flushInterval) {
        flush()
      }
    }
    
    def flush(): Unit = {
      if (messageBuffer.nonEmpty) {
        logger.info(s"バッチログ (${messageBuffer.size}件): ${messageBuffer.mkString("; ")}")
        messageBuffer = Vector.empty
        messageCount = 0
      }
    }
  }
  
  // MDC(Mapped Diagnostic Context)を使ったコンテキストログ
  def processWithContext(userId: String, sessionId: String): Unit = {
    import org.slf4j.MDC
    
    try {
      MDC.put("userId", userId)
      MDC.put("sessionId", sessionId)
      
      logger.info("ユーザー操作開始")
      performUserOperation()
      logger.info("ユーザー操作正常完了")
      
    } finally {
      MDC.clear()  // 常にMDCをクリーンアップ
    }
  }
  
  // 非同期ログ統合
  import scala.concurrent.{Future, ExecutionContext}
  implicit val ec: ExecutionContext = ExecutionContext.global
  
  def asyncProcessing(): Future[Unit] = {
    Future {
      logger.debug("非同期処理開始")
      Thread.sleep(1000)  // 作業シミュレーション
      logger.debug("非同期処理完了")
    }.recover {
      case ex =>
        logger.error("非同期処理失敗", ex)
    }
  }
}

// 特殊用途向けカスタムロガーファクトリ
object CustomLoggerFactory {
  def createServiceLogger(serviceName: String): Logger = {
    Logger(s"service.$serviceName")
  }
  
  def createPerformanceLogger(): Logger = {
    Logger("performance")
  }
  
  def createSecurityLogger(): Logger = {
    Logger("security")
  }
}

// 使用例
class OptimizedApplication {
  val serviceLogger = CustomLoggerFactory.createServiceLogger("user-management")
  val perfLogger = CustomLoggerFactory.createPerformanceLogger()
  
  def run(): Unit = {
    val startTime = System.nanoTime()
    
    serviceLogger.info("アプリケーション開始")
    // アプリケーションロジック
    
    val duration = (System.nanoTime() - startTime) / 1000000
    perfLogger.info(s"アプリケーション起動時間: ${duration}ms")
  }
}