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.