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.
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")
}
}