SLF4J logging backend for Akka actor system
npx @tessl/cli install tessl/maven-com-typesafe-akka--akka-slf4j@2.5.0Akka SLF4J provides SLF4J (Simple Logging Facade for Java) integration for the Akka actor system, enabling developers to use SLF4J logging infrastructure within Akka applications. It includes a logger actor that implements Akka's logging interface and provides seamless integration between Akka's internal logging system and SLF4J, allowing use of popular logging frameworks like Logback or Log4j through SLF4J's facade.
"com.typesafe.akka" %% "akka-slf4j" % "2.5.23"import akka.event.slf4j.{SLF4JLogging, Logger, Slf4jLogger, Slf4jLoggingFilter, Slf4jLogMarker}akka {
loggers = ["akka.event.slf4j.Slf4jLogger"]
logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
loglevel = "INFO"
}import akka.actor.Actor
import akka.event.slf4j.SLF4JLogging
class MyActor extends Actor with SLF4JLogging {
def receive = {
case message =>
log.info("Received message: {}", message)
log.debug("Processing message...")
}
}import akka.event.slf4j.Logger
class MyService {
private val log = Logger(this.getClass.getName)
def doSomething(): Unit = {
log.info("Service operation started")
// ... do work ...
log.info("Service operation completed")
}
}Base trait that provides SLF4J logging capability to any class.
trait SLF4JLogging {
@transient
lazy val log: org.slf4j.Logger
}Mix this trait into your classes to get access to a lazy-initialized SLF4J logger.
Factory object for obtaining SLF4J logger instances.
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) - Returns a logger for the specified logger nameapply(logClass: Class[_], logSource: String) - Returns a logger for the specified class and sourceroot - Returns the SLF4J root loggerActor that handles Akka logging events and forwards them to SLF4J backends.
class Slf4jLogger extends Actor with SLF4JLogging
with RequiresMessageQueue[LoggerMessageQueueSemantics] {
val mdcThreadAttributeName: String
val mdcActorSystemAttributeName: String
val mdcAkkaSourceAttributeName: String
val mdcAkkaTimestamp: String
def receive: Receive
final def withMdc(logSource: String, logEvent: LogEvent)(logStatement: => Unit): Unit
protected def formatTimestamp(timestamp: Long): String
}The logger actor:
MDC attribute names:
mdcThreadAttributeName - "sourceThread"mdcActorSystemAttributeName - "sourceActorSystem"mdcAkkaSourceAttributeName - "akkaSource"mdcAkkaTimestamp - "akkaTimestamp"LoggingFilter that uses SLF4J backend configuration to filter log events before publishing to the event stream.
class Slf4jLoggingFilter(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
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 respects both Akka's log level configuration and the SLF4J backend's log level configuration, only allowing events through that are enabled at both levels.
Support for SLF4J markers in Akka logging.
final class Slf4jLogMarker(val marker: org.slf4j.Marker) extends LogMarker
object Slf4jLogMarker {
def apply(marker: org.slf4j.Marker): Slf4jLogMarker
def create(marker: org.slf4j.Marker): Slf4jLogMarker
}Slf4jLogMarker wraps SLF4J markers for use with Akka's marker-based logging. The create method provides Java API compatibility.
import akka.event.Logging
import akka.event.slf4j.Slf4jLogMarker
import org.slf4j.MarkerFactory
class MarkedLoggingActor extends Actor with DiagnosticActorLogging {
val markLog = Logging.withMarker(this)
val securityMarker = MarkerFactory.getMarker("SECURITY")
def receive = {
case sensitiveData =>
markLog.info(Slf4jLogMarker(securityMarker), "Processing sensitive data")
}
}// From akka.event
sealed trait LogEvent {
def logSource: String
def logClass: Class[_]
def message: Any
def timestamp: Long
def thread: Thread
def mdc: Map[String, Any]
}
case class Error(cause: Throwable, logSource: String, logClass: Class[_], message: Any = Error.NoCause) extends LogEvent
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
trait LogEventWithCause extends LogEvent {
def cause: Throwable
}
trait LogEventWithMarker extends LogEvent {
def marker: LogMarker
}
abstract class LogMarker(val name: String)
// Message queue semantics
trait LoggerMessageQueueSemantics// From org.slf4j
trait Logger {
def error(msg: String): Unit
def error(marker: Marker, msg: String): Unit
def error(msg: String, t: Throwable): Unit
def error(marker: Marker, msg: String, t: Throwable): Unit
def warn(msg: String): Unit
def warn(marker: Marker, msg: String): Unit
def warn(msg: String, t: Throwable): Unit
def warn(marker: Marker, msg: String, t: Throwable): Unit
def info(msg: String): Unit
def info(marker: Marker, msg: String): Unit
def info(format: String, arg: Object): Unit
def info(marker: Marker, format: String, arg: Object): Unit
def debug(msg: String): Unit
def debug(marker: Marker, msg: String): Unit
def debug(format: String, arg: Object): Unit
def debug(marker: Marker, format: String, arg: Object): Unit
def isErrorEnabled(): Boolean
def isErrorEnabled(marker: Marker): Boolean
def isWarnEnabled(): Boolean
def isWarnEnabled(marker: Marker): Boolean
def isInfoEnabled(): Boolean
def isInfoEnabled(marker: Marker): Boolean
def isDebugEnabled(): Boolean
def isDebugEnabled(marker: Marker): Boolean
}
trait Marker {
def getName(): String
}
object MDC {
def put(key: String, value: String): Unit
def remove(key: String): Unit
}Configure Akka to use SLF4J logger and filter:
akka {
# Use SLF4J logger
loggers = ["akka.event.slf4j.Slf4jLogger"]
# Use SLF4J-based filtering
logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
# Set Akka log level
loglevel = "INFO"
# Log config on startup
log-config-on-start = on
# Logger startup timeout
logger-startup-timeout = 30s
}Configure your SLF4J backend (e.g., Logback) to handle Akka's MDC attributes:
<!-- logback.xml -->
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %-5level [%X{akkaSource}] [%X{sourceThread}] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>Add to your build.sbt:
libraryDependencies += "com.typesafe.akka" %% "akka-slf4j" % "2.5.23"You'll also need an SLF4J implementation like Logback:
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3"