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:

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
 
    <root level="info">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

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.

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>