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 adaptor 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 lets first see what steps you need to setup basic logging in Akka
Step1 : 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")
Step2 : application.conf
You must then configure how Akka will log its entries. This is done in an 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" }
Step3 : 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”
<?xml version="1.0" encoding="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>
Step4 : 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 = { //create the actor system 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 experiences 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
kka { 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:
Hi,
Can you please give code examples using java instead of scala.
-vijayakumar
Hi,
Can you please give sample code using java instead of scala.
– vijay
Um,no its my blog and I like scala
For java I’ll assume you’re using gradle instead of sbt
compile (“com.typesafe.akka:akka-actor_$scalaVersion:$akkaVersion”) {}
runtime(“ch.qos.logback:logback-classic:1.2.3”) {}
runtime(‘com.typesafe.akka:akka-slf4j_2.11:2.5.3’) {}
The only real difference between the scala and java code is that the scala for
val log = Logging(system, classOf[Runner])
becomes
LoggingAdapter log = Logging.getLogger(system, this);
This java code is assuming you’re trying to access the logger inside of an Actor object and is actually the default implementation of log() within AbstractLoggingActor
Yeah that looks right