This is likely to be a smaller one of the series, but just because it is small in size doesn’t mean that it is not mighty.
Every app needs logging, and when working with a distributed set of actors, this is crucial.
Akka provides 2 types of logging adaptors out of the box:
- Console
- SLF4J (where you need the appropriate back end for this which is usually Logback)
It also has various configuration sections that allow you to adjust the following elements of Akka.
- Akka setup messages
- Receive of messages
- All actor lifecycle messages
- Finite state machine messages
- EventStream subscription messages
- Remoting messages
Before we look at how you can customize the logging to capture all this good stuff, let's first see what steps you need to setup basic logging in Akka.
Step 1: Grab the JARs
There are a couple of JARs you will need to perform logging in Akka. These are shown below.
See Built.sbt:
name := "HelloWorld"
version := "1.0"
scalaVersion := "2.11.8"
libraryDependencies ++= Seq(
"com.typesafe.akka" % "akka-actor_2.11" % "2.4.8",
"ch.qos.logback" % "logback-classic" % "1.1.7",
"com.typesafe.akka" % "akka-slf4j_2.11" % "2.4.8")
Step 2: application.conf
You must then configure how Akka will log its entries. This is done in a configuration file, I have decided to call mine “application.conf”.
See resources/application.conf.
akka {
# event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
loggers = ["akka.event.slf4j.Slf4jLogger"]
loglevel = "DEBUG"
}
Step 3: logback.xml
For the SL4J logging to work, we need to configure logback. This is typically done with a configuration file called “logback.xml”.
An example of this may look like this:
See “resources/logback.xml”:
="1.0"="UTF-8"
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<encoder>
<pattern>%X{akkaTimestamp} %-5level[%thread] %logger{0} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>c:/logs/akka.log</file>
<append>true</append>
<encoder>
<pattern>%date{yyyy-MM-dd} %X{akkaTimestamp} %-5level[%thread] %logger{1} - %msg%n</pattern>
</encoder>
</appender>
<logger name="akka" level="DEBUG" />
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
Step 4: Log Some Stuff
Once you have the above steps completed, you have 2 choices about how to consume the logging.
Using the LoggingAdaptor.Apply
You are able to use the LoggingAdaptor.Apply
to create a new log that you may use. Here is an example:
import akka.actor._
import scala.language.postfixOps
import scala.io.StdIn
import akka.event.Logging
class Runner {
def Run(): Unit = {
val system = ActorSystem("LifeCycleSystem")
val log = Logging(system, classOf[Runner])
log.debug("Runner IS UP BABY")
...
StdIn.readLine()
}
}
Using Logging Trait
To make this process easier, Akka also provides a trait that can be mixed in called “akka.actor.ActorLogging
”. This can be mixed in wherever you require logging.
import akka.actor.Actor
import akka.event.Logging
class LifeCycleActorWithLoggingTrait extends Actor with akka.actor.ActorLogging {
log.info("LifeCycleActor: constructor")
.....
}
Async Or NOT
Some of the more experienced JVM/Scala devs amongst you may think heck I can just use my own logging, I don’t need the Akka trait or LoggingAdaptor
.
The thing is if you use the Akka trait or LogginAdaptor
, they are setup to log asynchronously and not introduce any time delays into the messaging pipeline when logging.
So just be warned that you should probably use the inbuilt Akka stuff rather than roll your own. Logging to things like an ELK stack may be the exception.
Common Akka Configuration Around Logging
These configuration sections are useful for controlling what is logged.
General Log Level
akka {
# general logging level
loglevel = DEBUG
}
Log Akka Config At Start
akka {
# Log the complete configuration at INFO level when the actor system is started.
# This is useful when you are uncertain of what configuration is used.
log-config-on-start = on
}
Actor Receive Messages
akka {
debug {
# enable function of LoggingReceive, which is to log any received message at
# DEBUG level
receive = on
}
}
You can also monitor lifecycle events like this:
akka {
debug {
# enable DEBUG logging of actor lifecycle changes
lifecycle = on
}
}
Remoting Logging
Firstly, you can log the message the remote actor is sent by the transport layer.
akka {
remote {
# If this is "on", Akka will log all outbound messages at DEBUG level,
if off then they are not logged
log-sent-messages = on
}
}
You may also see what messages are received by the transport layer like this:
akka {
remote {
# If this is "on",
Akka will log all inbound messages at DEBUG level, if off then they are not logged
log-received-messages = on
}
}
Where is the Code?
As previously stated, all the code for this series will end up in this GitHub repo: