SLF4J logging integration for Akka providing logger implementation and utilities for integrating Akka's logging system with SLF4J-based logging frameworks
npx @tessl/cli install tessl/maven-com-typesafe-akka--akka-slf4j_2-12@2.8.0Akka SLF4J provides seamless integration between Akka's actor-based logging system and the SLF4J logging facade. It enables developers to leverage existing SLF4J-compatible logging frameworks (like Logback, Log4J) within Akka applications while maintaining compatibility with Akka's event bus and logging levels.
The library includes the Slf4jLogger actor for processing Akka log events, the SLF4JLogging trait for easy logging access, utilities for configuring MDC (Mapped Diagnostic Context) support for structured logging, and filtering capabilities that respect SLF4J backend configuration.
build.sbt: "com.typesafe.akka" %% "akka-slf4j" % "2.8.8"akka.event.slf4jimport akka.event.slf4j._For specific components:
import akka.event.slf4j.{SLF4JLogging, Logger, Slf4jLogger, Slf4jLogMarker, Slf4jLoggingFilter}// application.conf
akka {
loggers = ["akka.event.slf4j.Slf4jLogger"]
loglevel = "INFO"
logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
}import akka.actor.{Actor, ActorLogging}
import akka.event.slf4j.SLF4JLogging
class MyActor extends Actor with SLF4JLogging {
def receive = {
case message =>
log.info("Received message: {}", message)
log.debug("Processing in actor: {}", self.path)
}
}import akka.event.slf4j.Logger
class MyService {
private val log = Logger("com.example.MyService")
def processData(data: String): Unit = {
log.info("Processing data of length: {}", data.length)
if (data.isEmpty) {
log.warn("Empty data received")
}
}
}Akka SLF4J is built around several key components:
log field initialization for classesBase trait that provides easy access to SLF4J logging infrastructure with lazy logger initialization.
trait SLF4JLogging {
@transient lazy val log: org.slf4j.Logger = Logger(this.getClass.getName)
}Mix this trait into any class to get access to a logger instance named after the class.
Factory object for creating SLF4J logger instances with various initialization options.
object Logger {
def apply(logger: String): org.slf4j.Logger
def apply(logClass: Class[_], logSource: String): org.slf4j.Logger
def root: org.slf4j.Logger
}apply(logger: String): Creates logger for the given logger nameapply(logClass: Class[_], logSource: String): Creates logger for specified class and source. Uses pattern matching to handle DummyClassForStringSources specially, falling back to logSource parameter for logger nameroot: Returns the SLF4J root loggerCore actor that processes Akka logging events and forwards them to SLF4J with proper context and formatting.
class Slf4jLogger extends Actor with SLF4JLogging with RequiresMessageQueue[LoggerMessageQueueSemantics] {
def receive: Receive
protected def formatTimestamp(timestamp: Long): String
// MDC attribute name constants
val mdcThreadAttributeName: String = "sourceThread"
val mdcActorSystemAttributeName: String = "sourceActorSystem"
val mdcAkkaSourceAttributeName: String = "akkaSource"
val mdcAkkaTimestamp: String = "akkaTimestamp"
val mdcAkkaAddressAttributeName: String = "akkaAddress"
val mdcAkkaUidAttributeName: String = "akkaUid"
}The actor handles these Akka log events:
Error - Maps to SLF4J error level with exception handlingWarning - Maps to SLF4J warn levelInfo - Maps to SLF4J info levelDebug - Maps to SLF4J debug levelInitializeLogger - Logger initialization messageLogging filter that uses the SLF4J backend configuration to determine if log events should be published to the event stream.
class Slf4jLoggingFilter(@unused settings: ActorSystem.Settings, eventStream: EventStream)
extends LoggingFilterWithMarker {
def isErrorEnabled(logClass: Class[_], logSource: String): Boolean
def isWarningEnabled(logClass: Class[_], logSource: String): Boolean
def isInfoEnabled(logClass: Class[_], logSource: String): Boolean
def isDebugEnabled(logClass: Class[_], logSource: String): Boolean
// Marker-aware versions
def isErrorEnabled(logClass: Class[_], logSource: String, marker: LogMarker): Boolean
def isWarningEnabled(logClass: Class[_], logSource: String, marker: LogMarker): Boolean
def isInfoEnabled(logClass: Class[_], logSource: String, marker: LogMarker): Boolean
def isDebugEnabled(logClass: Class[_], logSource: String, marker: LogMarker): Boolean
}This filter checks both Akka's event stream log level and the SLF4J backend configuration to determine if logging should occur.
Wrapper class and factory for integrating SLF4J markers with Akka's logging system.
final class Slf4jLogMarker(val marker: org.slf4j.Marker) extends LogMarker(name = marker.getName, properties = Map.empty)
object Slf4jLogMarker {
def apply(marker: org.slf4j.Marker): Slf4jLogMarker
def create(marker: org.slf4j.Marker): Slf4jLogMarker // Java API
}Use with Akka's marker-based logging:
import akka.event.Logging
import org.slf4j.MarkerFactory
val marker = MarkerFactory.getMarker("BUSINESS_EVENT")
val markLog = Logging.withMarker(context.system, this)
markLog.info(Slf4jLogMarker(marker), "Business event occurred")// From SLF4J API (external dependency)
trait org.slf4j.Logger {
def info(msg: String): Unit
def info(format: String, arg: Any): Unit
def warn(msg: String): Unit
def error(msg: String, t: Throwable): Unit
def debug(msg: String): Unit
def isInfoEnabled(): Boolean
def isDebugEnabled(): Boolean
// ... other standard SLF4J methods
}
trait org.slf4j.Marker {
def getName(): String
// ... other marker methods
}// From akka-actor (dependency)
trait Actor {
def receive: Receive
def context: ActorContext
}
trait LoggingFilterWithMarker {
def isErrorEnabled(logClass: Class[_], logSource: String, marker: LogMarker): Boolean
def isWarningEnabled(logClass: Class[_], logSource: String, marker: LogMarker): Boolean
def isInfoEnabled(logClass: Class[_], logSource: String, marker: LogMarker): Boolean
def isDebugEnabled(logClass: Class[_], logSource: String, marker: LogMarker): Boolean
}
abstract class LogMarker(val name: String, val properties: Map[String, Any])
trait RequiresMessageQueue[T <: MessageQueue]
trait LoggerMessageQueueSemantics extends MessageQueue
// From akka-event
trait EventStream {
def logLevel: Int
}
class ActorSystem {
type Settings
}
// Additional types referenced in the API
trait LogEvent {
def timestamp: Long
def thread: Thread
def mdc: Map[String, Any]
}
trait LogEventWithMarker extends LogEvent {
def marker: LogMarker
}
trait LogEventWithCause extends LogEvent {
def cause: Throwable
}
case class Error(cause: Throwable, logSource: String, logClass: Class[_], message: Any) extends LogEvent with LogEventWithCause
case class Warning(logSource: String, logClass: Class[_], message: Any) extends LogEvent
case class Info(logSource: String, logClass: Class[_], message: Any) extends LogEvent
case class Debug(logSource: String, logClass: Class[_], message: Any) extends LogEvent
case class InitializeLogger(bus: EventStream) extends LogEvent
case object LoggerInitialized
trait DiagnosticActorLogging {
def log: DiagnosticLoggingAdapter
}
trait DiagnosticLoggingAdapter {
def mdc(mdc: Map[String, Any]): Unit
def clearMDC(): Unit
}
class DummyClassForStringSources
// From akka-actor types
trait ActorContext {
def system: ActorSystem
}
type Receive = PartialFunction[Any, Unit]
trait MessageQueue
// Log level constants
val ErrorLevel: Int
val WarningLevel: Int
val InfoLevel: Int
val DebugLevel: Intakka {
loggers = ["akka.event.slf4j.Slf4jLogger"]
loglevel = "DEBUG"
logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
logger-startup-timeout = 30s
}Create logback.xml:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level [%X{akkaSource}] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="akka" level="INFO" />
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>The logger automatically populates MDC with Akka-specific context:
// These MDC keys are automatically available in your logging pattern:
// sourceThread - Thread where logging occurred
// sourceActorSystem - Name of the actor system
// akkaSource - Akka logging source
// akkaTimestamp - Formatted timestamp
// akkaAddress - Actor system address
// akkaUid - Actor system unique identifierAccess in logback pattern:
<pattern>%d [%X{sourceThread}] [%X{akkaSource}] %-5level %logger - %msg%n</pattern>The SLF4J integration handles various error scenarios:
This module requires:
akka-actor - Core Akka actor systemslf4j-api (version 1.7.36) - SLF4J logging facadeAdd to your build.sbt:
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-slf4j" % "2.8.8",
"ch.qos.logback" % "logback-classic" % "1.2.12" // or your preferred SLF4J implementation
)