Introduction to Logging With Finatra

Finatra uses the SLF4J API for framework logging. By coupling the framework to only the SLF4J API, application developers are free to choose their actual logging implementation.

From the SLF4J documentation

“The Simple Logging Facade for Java serves as a simple facade or abstraction for various logging frameworks, such as java.util.logging, Logback and Log4j. SLF4J allows the end-user to plug in the desired logging framework at deployment time.”

Important

Logback is the recommended SLF4J-API implementation. As such, finatra/inject/inject-app and finatra/inject/inject-server transitively provide SLF4J bridges for the following logging providers:

Bridging Logging Implementations

For background see the SLF4J Bridging legacy APIs documentation.

TwitterServer and Finatra’s inject-app both mix in the the c.t.util.logging.Slf4jBridge trait to attempt installation of the SLF4JBridgeHandler.

If you are not using one of these and want to bridge JUL to the SLF4J-API, you can mix in the c.t.util.logging.Slf4jBridge trait to your main application class.

Since SLF4J is an interface, it requires an actual logging implementation. However, you should ensure that you do not end-up with multiple logging implementations on your classpath, e.g., you should not have multiple SLF4J bindings (slf4j-nop, slf4j-log4j12, slf4j-jdk14, etc.) nor the java.util.logging implementation, etc. on your classpath as these are all competing implementations and since classpath order is non-deterministic, this will lead to unexpected logging behavior.

util/util-slf4j-api

While there are several scala-wrappers for SLF4J, Finatra uses the TwitterUtil util-slf4j-api project.

There are two main utilities in the library: the c.t.util.logging.Logging trait and the c.t.util.logging.Logger class which provides a standard “Logger” interface that wraps the SLF4J org.slf4j.Logger but with API ergonomics for Scala users and other optimizations.

c.t.util.logging.Logging

The c.t.util.logging.Logging trait can be mixed into any object or class:

 import com.twitter.util.logging.Logging

 object MyObject extends Logging {
   def foo() = {
     info("Calculating...")
     "bar"
   }
 }

When mixing in this trait, a new c.t.util.logging.Logger is instantiated for the current class. The trait exposes logging methods which can be called as though they exist within the current class, e.g., trace, debug, info, warn, or error.

See the c.t.util.logging.Logging trait scaladocs for details.

As an example, in the code above, by mixing in the Logging trait the MyObject now has a logger named for the MyObject.getClass class. E.g.,

Welcome to Scala 2.12.15 (JDK 64-Bit Server VM, Java 1.8.0_275).
Type in expressions for evaluation. Or try :help.

scala> import com.twitter.util.logging.Logging
import com.twitter.util.logging.Logging

scala> import com.twitter.util.logging.Logger
import com.twitter.util.logging.Logger

scala> object MyObject extends Logging {
     |
     |   def myLogger: Logger = this.logger
     |
     |   def myLoggerName: String = this.loggerName
     |
     |   def foo() = {
     |     info("Calculating...")
     |     "bar"
     |   }
     | }
defined object MyObject

scala> MyObject.myLoggerName
res0: String = MyObject

scala> val logger = MyObject.myLogger
logger: com.twitter.util.logging.Logger = com.twitter.util.logging.Logger@65da4a5d

scala> logger.name
res1: String = MyObject

scala> logger.isInfoEnabled
res2: Boolean = true

scala> MyObject.foo
12:41:20.171 [main] INFO $line5.$read$$iw$$iw$MyObject - Calculating...
res3: String = bar

scala> class MyClass extends Logging {
     |
     |   def myLogger: Logger = this.logger
     |
     |   def myLoggerName: String = this.loggerName
     |
     |   def bar() = {
     |     info("Calculating...")
     |     "foo"
     |   }
     |
     | }
defined class MyClass

scala> val c = new MyClass
c: MyClass = MyClass@eaa901a

scala> c.myLoggerName
res4: String = MyClass

scala> val logger = c.myLogger
logger: com.twitter.util.logging.Logger = com.twitter.util.logging.Logger@4a03c4bc

scala> c.bar
12:43:19.130 [main] INFO $line15.$read$$iw$$iw$MyClass - Calculating...
res5: String = foo

scala>

Note

The c.t.util.logging.Logger will remove the trailing $ from Scala object class names.

c.t.util.logging.Logger

The c.t.util.logging.Logger class provides a standard “Logger” interface wrapping the SLF4J org.slf4j.Logger:

 import com.twitter.util.logging.Logger

 private object MyCompanion {
   val logger: Logger = Logger(this.getClass)
 }

 class MyCompanion {
   import MyCompanion._

   def foo() = {
     logger.info("Calculating...")
     "bar"
   }
 }
 Welcome to Scala 2.12.15 (JDK 64-Bit Server VM, Java 1.8.0_275).
 Type in expressions for evaluation. Or try :help.

 scala> import com.twitter.util.logging.Logger
 import com.twitter.util.logging.Logger

 scala> :paste
 // Entering paste mode (ctrl-D to finish)

 private object MyCompanion {
   val logger: Logger = Logger(this.getClass)
 }

 class MyCompanion {
   import MyCompanion._

   def foo() = {
     logger.info("Calculating...")
     "bar"
   }
 }

 // Exiting paste mode, now interpreting.

 defined object MyCompanion
 defined class MyCompanion

 scala> val c = new MyCompanion
 c: MyCompanion = MyCompanion@63cf578f

 scala> c.foo
 12:52:43.809 [main] INFO $line4.$read$$iw$$iw$MyCompanion - Calculating...
 res0: String = bar

 scala>

For more information see the util-slf4j-api scaladocs or README.