SLF4J + Logback
Scalaアプリケーションで人気の伝統的な組み合わせ。SLF4Jが異なるJVMフレームワーク上でのロギング抽象化問題を解決し、LogbackとLog4j 2が柔軟性と強力な機能を提供。Scalaコミュニティでの実績と信頼性が高い。
GitHub概要
スター2,427
ウォッチ109
フォーク1,011
作成日:2009年8月20日
言語:Java
ライセンス:MIT License
トピックス
なし
スター履歴
データ取得日時: 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アプリケーションに強力で柔軟なロギング機能を提供します。パフォーマンスと使いやすさのバランスが取れており、小規模なアプリケーションから大規模な分散システムまで幅広く対応できます。適切な設定とベストプラクティスに従うことで、効果的なログ管理とトラブルシューティングが可能になります。