Scala: debug logging facility and adjustment of logging level in code
As soon as users start to use your program, you want to implement some debug facilities with logging, and allow them to be turned on via command line switches or GUI elements. I was surprised that doing this in Scala wasn’t as easy as I thought, so I collected the information on how to set it up.
Basic ingredients are the scala-logging library which wraps up slf4j, the Simple Logging Facade for Java, and a compatible backend, I am using logback, a successor of Log4j.
At the current moment adding the following lines to your build.sbt will include the necessary libraries:
libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.7.2"
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3"
Next is to set up the default logging by adding a file src/main/resources/logback.xml containing at least the following entry for logging to stdout:
%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
Note the default level here is set to info. More detailed information on the format of the logback.xml can be found here.
In the scala code a simple mix-in of the LazyLogging trait is enough to get started:
import com.typesafe.scalalogging.LazyLogging
object ApplicationMain extends App with LazyLogging {
...
logger.trace(...)
logger.debug(...)
logger.info(...)
logger.warn(...)
logger.error(...)
(the above commands are in increasingly serious) The messages will only be shown if the logger call has higher seriousness than what is configured in logback.xml (or INFO by default). That means that anything of level trace and debug will not be shown.
But we don’t want to ship always a new program with different logback.xml, so changing the default log level programatically is somehow a strict requirement. Fortunately a brave soul posted a solution on stackexchange, namely
import ch.qos.logback.classic.{Level,Logger}
import org.slf4j.LoggerFactory
LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME).
asInstanceOf[Logger].setLevel(Level.DEBUG)
This can be used to evaluate command line switches and activate debugging on the fly. The way I often do this is to allow flags -q, -qq, -d, and -dd for quiet, extra quiet, debug, extra debug, which would be translated to the logging levels warning, error, debug, and trace, respectively. Multiple invocations select the maximum debug level (so -q -d does turn on debugging).
This can activated by the following simple code:
val cmdlnlog: Int = args.map( {
case "-d" => Level.DEBUG_INT
case "-dd" => Level.TRACE_INT
case "-q" => Level.WARN_INT
case "-qq" => Level.ERROR_INT
case _ => -1
} ).foldLeft(Level.OFF_INT)(scala.math.min(_,_))
if (cmdlnlog == -1) {
// Unknown log level has been passed in, error out
Console.err.println("Unsupported command line argument passed in, terminating.")
sys.exit(0)
}
// if nothing has been passed on the command line, use INFO
val newloglevel = if (cmdlnlog == Level.OFF_INT) Level.INFO_INT else cmdlnlog
LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME).
asInstanceOf[Logger].setLevel(Level.toLevel(newloglevel))
where args are the command line parameters (in case of ScalaFX that would be parameters.unnamed, in case of a normal Scala application the argument to the main entry function). More complicated command line arguments of course need a more sophisticated approach.
Hope that helps.
The problem of this approach, compared to the LazyLogging, is that the expression you put into logger.info() will be always evaluated even if the loglevel is set to “error”.
When you use logger provided by LazyLogging, the expressions are evaluated only if the configured log level matches the statement. That’s why it is called Lazy 🙂
You may easily check that by executing this:
logger.info(s”${println(“*****”); “message”}”)
and playing with the log level. When using LazyLogging he asterisks are not printed if the log level is more than info.