SLF4J + Logback

Scalaアプリケーションで人気の伝統的な組み合わせ。SLF4Jが異なるJVMフレームワーク上でのロギング抽象化問題を解決し、LogbackとLog4j 2が柔軟性と強力な機能を提供。Scalaコミュニティでの実績と信頼性が高い。

GitHub概要

qos-ch/slf4j

Simple Logging Facade for Java

ホームページ:http://www.slf4j.org
スター2,427
ウォッチ109
フォーク1,011
作成日:2009年8月20日
言語:Java
ライセンス:MIT License

トピックス

なし

スター履歴

qos-ch/slf4j Star History
データ取得日時: 2025/7/17 08:20

slf4j-logback

SLF4J(Simple Logging Facade for Java)とLogbackの組み合わせは、Scalaアプリケーションにおける標準的なロギングソリューションです。SLF4Jは抽象化レイヤーとして機能し、Logbackは高性能な実装を提供します。scala-loggingライブラリを使用することで、Scalaネイティブな方法でこれらを活用できます。

主な特徴

  • ファサードパターン - 実装を切り替え可能な抽象化レイヤー
  • 高パフォーマンス - パラメータ化されたロギングとマクロによる最適化
  • MDCサポート - スレッドごとのコンテキスト情報管理
  • 非同期ロギング - パフォーマンスを向上させる非同期処理
  • 柔軟な設定 - XMLによる詳細な設定が可能

インストール

// build.sbt
libraryDependencies ++= Seq(
  "com.typesafe.scala-logging" %% "scala-logging" % "3.9.5",
  "ch.qos.logback" % "logback-classic" % "1.4.11"
)

// 注意: Logback 1.3.x はJDK8をサポート
// Logback 1.4.x 以降はJDK11以上が必要

基本的な使い方

ロガーの作成

import com.typesafe.scalalogging.Logger

// 方法1: 名前を指定
val logger = Logger("MyApplication")

// 方法2: クラスを指定
val logger = Logger(classOf[MyService])

// 方法3: 型パラメータを使用
class MyService {
  private val logger = Logger[MyService]
}

ログレベル

// 利用可能なログレベル(重要度順)
logger.error("エラーメッセージ")
logger.warn("警告メッセージ")
logger.info("情報メッセージ")
logger.debug("デバッグメッセージ")
logger.trace("トレースメッセージ")

// 例外と共にログ出力
try {
  riskyOperation()
} catch {
  case e: Exception =>
    logger.error("処理中にエラーが発生しました", e)
}

トレイトを使用したロギング

import com.typesafe.scalalogging.LazyLogging
import com.typesafe.scalalogging.StrictLogging

// 遅延ロギング(推奨)
class MyService extends LazyLogging {
  def processData(): Unit = {
    logger.info("データ処理を開始します")
    // 処理
    logger.debug(s"処理結果: ${expensiveComputation()}")
  }
}

// 即座にロガーを初期化
class CriticalService extends StrictLogging {
  logger.info("CriticalServiceが初期化されました")
}

設定ファイル(logback.xml)

基本設定

<!-- src/main/resources/logback.xml -->
<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>
  
  <!-- ルートロガー -->
  <root level="INFO">
    <appender-ref ref="STDOUT" />
  </root>
  
  <!-- 特定パッケージのログレベル設定 -->
  <logger name="com.mycompany.myapp" level="DEBUG" />
</configuration>

ファイル出力とローテーション

<configuration>
  <!-- ローリングファイルアペンダー -->
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>logs/application.log</file>
    
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!-- 日次ローテーション -->
      <fileNamePattern>logs/application.%d{yyyy-MM-dd}.log</fileNamePattern>
      <!-- 30日間保持 -->
      <maxHistory>30</maxHistory>
      <!-- 最大3GB -->
      <totalSizeCap>3GB</totalSizeCap>
    </rollingPolicy>
    
    <encoder>
      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>
  
  <root level="INFO">
    <appender-ref ref="FILE" />
  </root>
</configuration>

非同期ロギング

<configuration>
  <!-- ファイルアペンダー -->
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>logs/app.log</file>
    <encoder>
      <pattern>%d %level %logger - %msg%n</pattern>
    </encoder>
  </appender>
  
  <!-- 非同期アペンダー -->
  <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <queueSize>512</queueSize>
    <discardingThreshold>0</discardingThreshold>
    <appender-ref ref="FILE" />
  </appender>
  
  <root level="INFO">
    <appender-ref ref="ASYNC" />
  </root>
</configuration>

高度な使い方

パラメータ化ロギング

// 効率的: 文字列補間はログレベルが有効な場合のみ実行される
val userId = 12345
val action = "login"
logger.info(s"ユーザー $userId が $action しました")

// Scala-loggingがSLF4Jの形式に自動変換
// 内部的には: slf4jLogger.info("ユーザー {} が {} しました", userId, action)

// 条件付きロギング
logger.whenDebugEnabled {
  // このブロックはDEBUGレベルが有効な場合のみ実行される
  val stats = computeExpensiveStatistics()
  logger.debug(s"統計情報: $stats")
}

MDC(Mapped Diagnostic Context)

import org.slf4j.MDC
import scala.util.control.NonFatal

class RequestHandler {
  def handleRequest(requestId: String, userId: String): Unit = {
    // MDCにコンテキスト情報を設定
    MDC.put("requestId", requestId)
    MDC.put("userId", userId)
    
    try {
      logger.info("リクエスト処理開始")
      processRequest()
      logger.info("リクエスト処理完了")
    } catch {
      case NonFatal(e) =>
        logger.error("リクエスト処理エラー", e)
    } finally {
      // MDCをクリーンアップ
      MDC.remove("requestId")
      MDC.remove("userId")
    }
  }
}

// logback.xmlでMDCを使用
// <pattern>%d [%thread] %level %logger - %msg [%X{requestId}] [%X{userId}]%n</pattern>

構造化ログ(JSON形式)

import net.logstash.logback.argument.StructuredArguments._

class StructuredLogging extends LazyLogging {
  def logUserActivity(userId: Long, action: String, details: Map[String, Any]): Unit = {
    logger.info(
      "ユーザーアクティビティ",
      value("userId", userId),
      value("action", action),
      entries(details.asJava)
    )
  }
}
<!-- JSONエンコーダーの設定 -->
<configuration>
  <appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="net.logstash.logback.encoder.LogstashEncoder">
      <!-- タイムスタンプフィールド名をカスタマイズ -->
      <timestampPattern>yyyy-MM-dd'T'HH:mm:ss.SSS'Z'</timestampPattern>
      <fieldNames>
        <timestamp>@timestamp</timestamp>
        <message>message</message>
        <logger>logger_name</logger>
        <thread>thread_name</thread>
        <level>level</level>
      </fieldNames>
    </encoder>
  </appender>
  
  <root level="INFO">
    <appender-ref ref="JSON" />
  </root>
</configuration>

マーカーの使用

import org.slf4j.MarkerFactory

object SecurityMarkers {
  val SECURITY = MarkerFactory.getMarker("SECURITY")
  val AUDIT = MarkerFactory.getMarker("AUDIT")
}

class SecurityService extends LazyLogging {
  def authenticateUser(username: String): Boolean = {
    val result = performAuthentication(username)
    
    if (result) {
      logger.info(SecurityMarkers.SECURITY, s"認証成功: $username")
    } else {
      logger.warn(SecurityMarkers.SECURITY, s"認証失敗: $username")
    }
    
    result
  }
}

パフォーマンスの最適化

遅延評価

// 悪い例: 常に文字列が構築される
logger.debug("計算結果: " + expensiveComputation())

// 良い例: DEBUGレベルが有効な場合のみ実行
logger.debug(s"計算結果: ${expensiveComputation()}")

// より良い例: 明示的な条件チェック
if (logger.underlying.isDebugEnabled) {
  logger.debug(s"計算結果: ${expensiveComputation()}")
}

// 最良の例: whenDebugEnabledを使用
logger.whenDebugEnabled {
  logger.debug(s"計算結果: ${expensiveComputation()}")
}

非同期ロギングの設定

// アプリケーション側の設定
class HighPerformanceService extends LazyLogging {
  // バッファリングを活用
  private val logBuffer = new java.util.concurrent.ConcurrentLinkedQueue[String]()
  
  def processHighVolumeData(): Unit = {
    // バッチでログ出力
    if (logBuffer.size() > 100) {
      logger.info(s"バッチログ: ${logBuffer.asScala.mkString(", ")}")
      logBuffer.clear()
    }
  }
}

ベストプラクティス

1. 適切なログレベルの使用

class BestPracticesExample extends LazyLogging {
  def processOrder(orderId: String): Unit = {
    logger.trace(s"processOrderメソッド開始: $orderId")
    logger.debug(s"注文詳細を取得中: $orderId")
    logger.info(s"注文処理開始: $orderId")
    
    try {
      // 処理
      logger.info(s"注文処理完了: $orderId")
    } catch {
      case e: PaymentException =>
        logger.error(s"支払い処理エラー: $orderId", e)
      case e: Exception =>
        logger.error(s"予期しないエラー: $orderId", e)
    }
  }
}

2. 環境別設定

<!-- logback-test.xml (テスト環境) -->
<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>
  
  <root level="DEBUG">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>
// 環境変数による動的設定
object LoggerConfig {
  def configureLogger(): Unit = {
    sys.env.get("LOG_LEVEL") match {
      case Some("DEBUG") => System.setProperty("ROOT_LOG_LEVEL", "DEBUG")
      case Some("WARN") => System.setProperty("ROOT_LOG_LEVEL", "WARN")
      case _ => System.setProperty("ROOT_LOG_LEVEL", "INFO")
    }
  }
}

3. セキュリティへの配慮

class SecureLogging extends LazyLogging {
  def logSensitiveOperation(userId: String, creditCard: String): Unit = {
    // センシティブな情報をマスク
    val maskedCard = creditCard.take(4) + "****" + creditCard.takeRight(4)
    logger.info(s"支払い処理: ユーザー=$userId, カード=$maskedCard")
  }
  
  // パスワードは絶対にログに出力しない
  def authenticate(username: String, password: String): Boolean = {
    logger.debug(s"認証試行: ユーザー=$username")
    // パスワードはログに含めない
    val result = checkCredentials(username, password)
    logger.info(s"認証結果: ユーザー=$username, 成功=${result}")
    result
  }
}

まとめ

SLF4J + Logback + scala-loggingの組み合わせは、Scalaアプリケーションに強力で柔軟なロギング機能を提供します。パフォーマンスと使いやすさのバランスが取れており、小規模なアプリケーションから大規模な分散システムまで幅広く対応できます。適切な設定とベストプラクティスに従うことで、効果的なログ管理とトラブルシューティングが可能になります。