ScalaLogging (Alternative)

Alternative implementation of Scala logging library built on top of SLF4J. Focuses on basic functionality with emphasis on lightweight nature. Provides simpler API and minimal dependencies through different approach from scala-logging.

LoggingScalaType-safeMacro-basedSLF4J

Library

ScalaLogging

Overview

ScalaLogging is a convenient and performant logging library for Scala developed as a type-safe logging solution wrapping SLF4J. Leveraging Scala's powerful macro system, it completely eliminates performance costs for disabled log levels through compile-time optimization. Built as a lightweight wrapper around SLF4J, it provides type-safe string interpolation, lazy evaluation, and seamless integration with the extensive SLF4J ecosystem while maintaining complete compatibility with Java logging frameworks.

Details

ScalaLogging 2025 edition continues as the de facto standard for Scala application logging, maintained by Lightbend Labs with over a decade of production-proven stability. The library's macro-based architecture automatically transforms logging calls into conditional statements at compile time, completely eliminating runtime overhead for disabled log levels. It provides multiple logging traits (LazyLogging, StrictLogging, AnyLogging) to accommodate different initialization patterns and supports advanced features like contextual logging with LoggerTakingImplicit for structured logging scenarios.

Key Features

  • Macro-based Performance: Complete zero-overhead through compile-time optimization
  • Type-safe String Interpolation: Direct support for Scala's s"..." interpolation
  • Multiple Logging Traits: LazyLogging, StrictLogging, and AnyLogging patterns
  • SLF4J Compatibility: Complete integration with SLF4J ecosystem and backends
  • Contextual Logging: LoggerTakingImplicit for structured and contextual data
  • Zero Dependencies: Lightweight library with minimal external dependencies

Pros and Cons

Pros

  • Excellent performance through macro-based optimization eliminating runtime checks
  • Idiomatic Scala logging with type-safe string interpolation support
  • Seamless integration with existing SLF4J infrastructure and backends
  • Multiple initialization patterns accommodating different architectural needs
  • Strong type safety preventing common logging errors at compile time
  • Minimal learning curve for developers familiar with SLF4J

Cons

  • Limited to SLF4J backend ecosystem, cannot use non-SLF4J logging frameworks
  • Macro dependencies may increase compilation time in large projects
  • Less flexible than structured logging libraries for complex log analysis
  • Documentation and community resources smaller compared to Java logging libraries
  • Advanced features like contextual logging require additional learning investment
  • Tight coupling to Scala compiler version for macro functionality

Reference Pages

Code Examples

Installation and Setup

// build.sbt - Add Scala Logging dependency
libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.5"

// Add SLF4J backend (Logback recommended)
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.4.12"

// Alternative backends
libraryDependencies += "org.slf4j" % "slf4j-simple" % "2.0.9"  // Simple logger
libraryDependencies += "org.slf4j" % "slf4j-jdk14" % "2.0.9"   // Java Util Logging

// Verification
import com.typesafe.scalalogging.Logger
val logger = Logger("test")
logger.info("ScalaLogging is working!")

Basic Logging Output

import com.typesafe.scalalogging.Logger

// Create logger by name
val logger = Logger("com.example.MyApp")

// Basic logging with different levels
logger.trace("Detailed trace information")
logger.debug("Debug information for development")
logger.info("General information about application flow")
logger.warn("Warning: potential issue detected")
logger.error("Error: something went wrong")

// Class-based logger
val classLogger = Logger(getClass.getName)
val classOfLogger = Logger(classOf[MyClass])
val genericLogger = Logger[MyClass]  // Using ClassTag

// String interpolation (recommended)
val userId = 12345
val action = "login"
logger.info(s"User $userId performed action: $action")

// Traditional SLF4J style (also supported)
logger.info("User {} performed action: {}", userId, action)

// Check if level is enabled
if (logger.isDebugEnabled) {
  val expensiveDebugInfo = computeExpensiveDebugInfo()
  logger.debug(s"Debug info: $expensiveDebugInfo")
}

// Macro automatically generates the above check
logger.debug(s"Debug info: ${computeExpensiveDebugInfo()}")  // Automatically optimized

Log Level Configuration

// Using LazyLogging trait (most common)
import com.typesafe.scalalogging.LazyLogging

class UserService extends LazyLogging {
  def createUser(name: String): User = {
    logger.debug("Creating user with name: {}", name)
    val user = User(name)
    logger.info("User created successfully: {}", user.id)
    user
  }
  
  def deleteUser(userId: Long): Unit = {
    logger.warn("Deleting user: {}", userId)
    // deletion logic
    logger.info("User {} deleted", userId)
  }
}

// Using StrictLogging trait (eager initialization)
import com.typesafe.scalalogging.StrictLogging

class DatabaseService extends StrictLogging {
  // Logger is initialized immediately when class is instantiated
  
  def connect(): Unit = {
    logger.info("Connecting to database...")
    logger.debug("Connection parameters: {}", getConnectionParams())
  }
}

// Using AnyLogging trait (custom logger)
import com.typesafe.scalalogging.AnyLogging

class CustomLoggingService extends AnyLogging {
  // Override logger for custom configuration
  override protected val logger: Logger = Logger("custom.service")
  
  def process(): Unit = {
    logger.info("Processing with custom logger")
  }
}

// Programmatic level control (via SLF4J backend)
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)  // Set to DEBUG level

val specificLogger = context.getLogger("com.example.UserService")
specificLogger.setLevel(Level.WARN)  // Only WARN and above

Scala-specific Features

import com.typesafe.scalalogging.Logger

// String interpolation with complex expressions
val logger = Logger("app")
val user = User("John", 30)

logger.info(s"User ${user.name} is ${user.age} years old")
logger.debug(s"Processing user: ${user.toDebugString}")

// Exception logging with stack traces
try {
  riskyOperation()
} catch {
  case ex: SQLException =>
    logger.error("Database error occurred", ex)
  case ex: Exception =>
    logger.error(s"Unexpected error: ${ex.getMessage}", ex)
}

// Conditional logging with when-enabled
logger.whenDebugEnabled {
  println("This code only executes when DEBUG is enabled")
  (1 to 10).foreach(x => println(s"Expensive operation $x"))
}

logger.whenInfoEnabled {
  val diagnostics = generateDiagnostics()  // Expensive operation
  logger.info(s"System diagnostics: $diagnostics")
}

// Using markers for structured logging
import org.slf4j.MarkerFactory

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

logger.warn(securityMarker, "Failed login attempt from IP: {}", clientIp)
logger.info(performanceMarker, "Operation completed in {}ms", duration)

// Pattern matching integration
def processResult(result: Either[Error, Success]): Unit = {
  result match {
    case Left(error) =>
      logger.error(s"Operation failed: ${error.message}")
    case Right(success) =>
      logger.info(s"Operation succeeded: ${success.data}")
  }
}

// Future and Try integration
import scala.concurrent.Future
import scala.util.{Try, Success, Failure}

def asyncOperation(): Future[String] = {
  Future {
    logger.debug("Starting async operation")
    // Some async work
    "result"
  }.andThen {
    case Success(result) =>
      logger.info(s"Async operation completed: $result")
    case Failure(exception) =>
      logger.error("Async operation failed", exception)
  }
}

// Option and collection logging
val optionalValue: Option[String] = Some("value")
optionalValue match {
  case Some(value) =>
    logger.debug(s"Found value: $value")
  case None =>
    logger.warn("No value found")
}

val items = List("item1", "item2", "item3")
logger.info(s"Processing ${items.length} items: ${items.mkString(", ")}")

Configuration Files

<!-- logback.xml - Logback configuration -->
<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>

  <!-- Application-specific loggers -->
  <logger name="com.example.UserService" level="DEBUG" />
  <logger name="com.example.DatabaseService" level="INFO" />
  
  <!-- Third-party library loggers -->
  <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 configuration
status = warn

# Console appender
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

# File appender
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

# Loggers
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

Performance Optimization

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

class PerformanceOptimizedService extends LazyLogging {
  
  // Efficient logging with macro optimization
  def processLargeDataset(data: List[DataItem]): Unit = {
    // This check is automatically generated by macro when debug is disabled
    logger.debug(s"Processing ${data.size} items: ${data.map(_.id).mkString(",")}")
    
    data.foreach { item =>
      // Expensive computation only happens when debug is enabled
      logger.trace(s"Processing item: ${item.toDetailedString}")
      processItem(item)
    }
    
    logger.info(s"Completed processing ${data.size} items")
  }
  
  // Using whenEnabled for complex conditional logging
  def performComplexOperation(): Unit = {
    val startTime = System.currentTimeMillis()
    
    // Expensive diagnostic information only computed when needed
    logger.whenDebugEnabled {
      val memoryUsage = Runtime.getRuntime.totalMemory() - Runtime.getRuntime.freeMemory()
      val threadCount = Thread.activeCount()
      logger.debug(s"Starting operation - Memory: ${memoryUsage}MB, Threads: $threadCount")
    }
    
    // Main operation
    doComplexWork()
    
    val duration = System.currentTimeMillis() - startTime
    logger.info(s"Operation completed in ${duration}ms")
  }
  
  // Efficient exception logging
  def handleError(operation: String, ex: Throwable): Unit = {
    ex match {
      case _: IllegalArgumentException =>
        // Simple message for expected exceptions
        logger.warn(s"Invalid argument in $operation: ${ex.getMessage}")
      case _ =>
        // Full stack trace for unexpected exceptions
        logger.error(s"Unexpected error in $operation", ex)
    }
  }
  
  // Batched logging for high-throughput scenarios
  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"Batch log (${messageBuffer.size} messages): ${messageBuffer.mkString("; ")}")
        messageBuffer = Vector.empty
        messageCount = 0
      }
    }
  }
  
  // Contextual logging with 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("Starting user operation")
      performUserOperation()
      logger.info("User operation completed successfully")
      
    } finally {
      MDC.clear()  // Always clean up MDC
    }
  }
  
  // Async logging integration
  import scala.concurrent.{Future, ExecutionContext}
  implicit val ec: ExecutionContext = ExecutionContext.global
  
  def asyncProcessing(): Future[Unit] = {
    Future {
      logger.debug("Starting async processing")
      Thread.sleep(1000)  // Simulate work
      logger.debug("Async processing completed")
    }.recover {
      case ex =>
        logger.error("Async processing failed", ex)
    }
  }
}

// Custom logger factory for specialized use cases
object CustomLoggerFactory {
  def createServiceLogger(serviceName: String): Logger = {
    Logger(s"service.$serviceName")
  }
  
  def createPerformanceLogger(): Logger = {
    Logger("performance")
  }
  
  def createSecurityLogger(): Logger = {
    Logger("security")
  }
}

// Usage example
class OptimizedApplication {
  val serviceLogger = CustomLoggerFactory.createServiceLogger("user-management")
  val perfLogger = CustomLoggerFactory.createPerformanceLogger()
  
  def run(): Unit = {
    val startTime = System.nanoTime()
    
    serviceLogger.info("Application starting")
    // Application logic
    
    val duration = (System.nanoTime() - startTime) / 1000000
    perfLogger.info(s"Application startup time: ${duration}ms")
  }
}