Akka Logging outside Actor

Solution 1:

Actually I would redirect Akka logging to slf4j and use this API directly in all unrelated classes. First add this to your configuration:

akka {
    event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
    loglevel = "DEBUG"
}

Then choose some SLF4J implementation, I suggest logback. In your actors continue using ActorLogging trait. In other classes simply rely on SLF4J API - or even better - try out slf4s facade around SLF4J.

Tip: try out the following logging pattern in Logback:

<pattern>%d{HH:mm:ss.SSS} | %-5level | %thread | %X{akkaSource} | %logger{1} | %m%n%rEx</pattern>

The %X{akkaSource} will print actor path when available (just like standard logging).

Solution 2:

Using Akka 2.2.1, I was able to put this into my App to get logging outside of an actor:

import akka.event.Logging
val system = ActorSystem("HelloSystem", ConfigFactory.load.getConfig("akka"))
val log = Logging.getLogger(system, this)
log.info("Hi!")

This seems like a simpler solution for unifying an application's logging.

Solution 3:

As has been mentioned, you're spoiled for options for non-actor logging within an actor system. I am going to attempt to provide a set of heuristics to help you determine how you should route logging for your work.

  1. You can use a logger (log4j 1.x, logback, log4j 2.x) directly in both actor and non-actor code.
    • This tightly couples your code to a logger implementation. This is fine if it's your code, not to be used elsewhere, but not fine if you're building a library or intend to open source your work.
    • If you do this, you gain no benefits from the actor system. Logging calls may become blocking calls, depending on how you have your logger set up, and thus this is frowned upon wherever performance or control over back pressure are important concerns.
    • Because actor code (along with services it can consume) can operate on many different threads, some traditional logging activities such as the use of a threadlocal MDC (Mapped Diagnostic Context) can result in bizarre race conditions and context swtiching with logs output from messages that pass from actor to actor. Activities such as swapping MDCs onto messages before sending them may become necessary to preserve context between actor and non actor code.
    • To capture ActorSystem events such as dead letters and supervision, you may need to write a logging adapter and specify it in your application.conf. These are pretty straightforward.
  2. You can use the SLF4J facade for both actor and non-actor logging.
    • You are no longer coupled to a logger impl and what's more your services aren't coupled to akka. This is the best option for portability.
    • You may inherit blocking behavior from your log framework.
    • You may have to manage MDCs
    • To capture ActorSystem events you'll need to specify "akka.event.slf4j.Slf4jLogger" in your application.conf
    • You'll need to include an slf4j provider jar on the classpath to route slf4j log events to your chosen logger
  3. You can use Akka's Logging as your facade in both Actor and non-actor code
    • You aren't coupled to a logger impl OR to slf4j, but you're coupled to a version of akka. This is probably a requirement of your system anyway, but for libraries it might reduce portability.
    • You have to pass around an actor system to act as the "bus" for loggers. Tight coupling to a working actor system reduces portability further. (Within an app I usually build a little LoggingViaActorSystem trait with an implicit or global ActorSystem, which makes it easier to deal with this in code but not across dependencies).
    • Non-blocking aynchronous logging is guaranteed, even if your logger doesn't support them. Causal consistency of logging is likely due to the use of a single consumer mailbox. However, memory safety and back pressure are not (I believe Akka logging uses an unbounded mailbox) --
    • There are options such as use of a DiagnosticLoggingAdapter for avoiding the complexity of managing your own MDCs as work passes from actor to actor. Consistency should be preserved even as non-actor code mutates these MDCs.
    • Logging is not likely to be available during an out-of-memory crash, and is sensitive to thread starvation on the default dispatcher
    • You'll need to specify your chosen logger in application.conf unless you're interested in logging to standard out

You're welcome to mix and match the above behaviors as necessary to meet your requirements. For example, you might choose to bind to SLF4J for libraries and use Akka logging for everything else. Just note that mixing blocking and non-blocking logging could cause race conditions where causes (logged async via an actor) are logged after their effects (logged sync directly).

Solution 4:

I've now settled on simply passing my central logging system around through DI constructor injection (Guice). And in my classes that do logging regularly (where asynchronicity is important), I take the injected ActorSystem and call the

this.log = akka.event.Logging.getLogger(actorSystem, this);

in the classes constructor.

Solution 5:

According to the latest (currently version 2.6.9) logging documentation using a Logger obtained from org.slf4j.LoggerFactory is perfectly fine, and is actually the recommended way to log outside an actor. I copy hereafter the exact wording.

It’s perfectly fine to use a Logger retrieved via org.slf4j.LoggerFactory, but then the logging events will not include the akkaSource MDC value. This is the recommend way when logging outside of an actor, including logging from Future callbacks.

I also provide hereafter a snippet based on the example

val log = LoggerFactory.getLogger("com.mypackage.MyObject")

Future {
  // do something
  "result"
}.onComplete {
  case Success(result) => log.info("Success!: {}", result)
  case Failure(exc)    => log.error("Failure!", exc)
}

In order to minimize performance penalties by logging one can configure an asynchronous appender for the SLF4J backend. Logback is the recommended logging backend.

dependencies {
  compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
}

A starting point for configuration of logback.xml for production:

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>myapp.log</file>
    <immediateFlush>false</immediateFlush>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>myapp_%d{yyyy-MM-dd}.log</fileNamePattern>
    </rollingPolicy>
    <encoder>
        <pattern>[%date{ISO8601}] [%level] [%logger] [%marker] [%thread] - %msg MDC: {%mdc}%n</pattern>
    </encoder>
</appender>

<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <queueSize>8192</queueSize>
    <neverBlock>true</neverBlock>
    <appender-ref ref="FILE" />
</appender>

<root level="INFO">
    <appender-ref ref="ASYNC"/>
</root>

Logging generally means IO and locks, which can slow down the operations of your code if it was performed synchronously.

The configurations shown above are the ones provided by the AKKA logging documentation. The documentation provides more information and can be found here