[logging] Logging in Scala

What is a good way to do logging in a Scala application? Something that is consistent with the language philosophy, does not clutter the code, and is low-maintenance and unobtrusive. Here's a basic requirement list:

  • simple
  • does not clutter the code. Scala is great for its brevity. I don't want half of my code to be logging statements
  • log format can be changed to fit the rest of my enterprise logs and monitoring software
  • supports levels of logging (ie debug, trace, error)
  • can log to disk as well as other destinations (i.e. socket, console, etc.)
  • minimum configuration, if any
  • works in containers (ie, web server)
  • (optional, but nice to have) comes either as part of the language or as a maven artifact, so I don't have to hack my builds to use it

I know I can use the existing Java logging solutions, but they fail on at least two of the above, namely clutter and configuration.

Thanks for your replies.

This question is related to logging scala

The answer is


You should have a look at the scalax library : http://scalax.scalaforge.org/ In this library, there is a Logging trait, using sl4j as backend. By using this trait, you can log quite easily (just use the logger field in the class inheriting the trait).


Don't use Logula

I've actually followed the recommendation of Eugene and tried it and found out that it has a clumsy configuration and is subjected to bugs, which don't get fixed (such as this one). It doesn't look to be well maintained and it doesn't support Scala 2.10.

Use slf4s + slf4j-simple

Key benefits:

  • Supports latest Scala 2.10 (to date it's M7)
  • Configuration is versatile but couldn't be simpler. It's done with system properties, which you can set either by appending something like -Dorg.slf4j.simplelogger.defaultlog=trace to execution command or hardcode in your script: System.setProperty("org.slf4j.simplelogger.defaultlog", "trace"). No need to manage trashy config files!
  • Fits nicely with IDEs. For instance to set the logging level to "trace" in a specific run configuration in IDEA just go to Run/Debug Configurations and add -Dorg.slf4j.simplelogger.defaultlog=trace to VM options.
  • Easy setup: just drop in the dependencies from the bottom of this answer

Here's what you need to be running it with Maven:

<dependency>
  <groupId>com.weiglewilczek.slf4s</groupId>
  <artifactId>slf4s_2.9.1</artifactId>
  <version>1.0.7</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-simple</artifactId>
  <version>1.6.6</version>
</dependency>

Logging in 2020

I was really surprised that Scribe logging framework that I use at work isn't even mentioned here. What is more, it doesn't even appear on the first page in Google after searching "scala logging". But this page appears when googling it! So let me leave that here.

Main advantages of Scribe:


I find very convenient using some kind of java logger, sl4j for example, with simple scala wrapper, which brings me such syntax

val #! = new Logger(..) // somewhere deep in dsl.logging.

object User with dsl.logging {

  #! ! "info message"
  #! dbg "debug message"
  #! trace "var a=true"

}

In my opinion very usefull mixin of java proven logging frameworks and scala's fancy syntax.


I use SLF4J + Logback classic and apply it like this:

trait Logging {
  lazy val logger = LoggerFactory.getLogger(getClass)

  implicit def logging2Logger(anything: Logging): Logger = anything.logger
}

Then you can use it whichever fits your style better:

class X with Logging {
    logger.debug("foo")
    debug("bar")
}

but this approach of course uses a logger instance per class instance.


Haven't tried it yet, but Configgy looks promising for both configuration and logging:

http://github.com/robey/configgy/tree/master


With Scala 2.10+ Consider ScalaLogging by Typesafe. Uses macros to deliver a very clean API

https://github.com/typesafehub/scala-logging

Quoting from their wiki:

Fortunately Scala macros can be used to make our lives easier: ScalaLogging offers the class Logger with lightweight logging methods that will be expanded to the above idiom. So all we have to write is:

logger.debug(s"Some ${expensiveExpression} message!")

After the macro has been applied, the code will have been transformed into the above described idiom.

In addition ScalaLogging offers the trait Logging which conveniently provides a Logger instance initialized with the name of the class mixed into:

import com.typesafe.scalalogging.slf4j.LazyLogging

class MyClass extends LazyLogging {
  logger.debug("This is very convenient ;-)")
}

Quick and easy forms.

Scala 2.10 and older:

import com.typesafe.scalalogging.slf4j.Logger
import org.slf4j.LoggerFactory
val logger = Logger(LoggerFactory.getLogger("TheLoggerName"))
logger.debug("Useful message....")

And build.sbt:

libraryDependencies += "com.typesafe" %% "scalalogging-slf4j" % "1.1.0"

Scala 2.11+ and newer:

import import com.typesafe.scalalogging.Logger
import org.slf4j.LoggerFactory
val logger = Logger(LoggerFactory.getLogger("TheLoggerName"))
logger.debug("Useful message....")

And build.sbt:

libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.1.0"

After using slf4s and logula for a while, I wrote loglady, a simple logging trait wrapping slf4j.

It offers an API similar to that of Python's logging library, which makes the common cases (basic string, simple formatting) trivial and avoids formatting boilerplate.

http://github.com/dln/loglady/


Writer, Monoid and a Monad implementation.


This is how I got Scala Logging working for me:

Put this in your build.sbt:

libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.7.2",
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3"

Then, after doing an sbt update, this prints out a friendly log message:

import com.typesafe.scalalogging._
object MyApp extends App with LazyLogging {
  logger.info("Hello there")
}

If you are using Play, you can of course simply import play.api.Logger for writing log messages: Logger.debug("Hi").

See the docs for more info.


I pulled a bit of work form the Logging trait of scalax, and created a trait that also integrated a MessageFormat-based library.

Then stuff kind of looks like this:

class Foo extends Loggable {
    info( "Dude, I'm an {0} with {1,number,#}", "Log message", 1234 )
}

We like the approach so far.

Implementation:

trait Loggable {

    val logger:Logger = Logging.getLogger(this)

    def checkFormat(msg:String, refs:Seq[Any]):String =
        if (refs.size > 0) msgfmtSeq(msg, refs) else msg 

    def trace(msg:String, refs:Any*) = logger trace checkFormat(msg, refs)

    def trace(t:Throwable, msg:String, refs:Any*) = logger trace (checkFormat(msg, refs), t)

    def info(msg:String, refs:Any*) = logger info checkFormat(msg, refs)

    def info(t:Throwable, msg:String, refs:Any*) = logger info (checkFormat(msg, refs), t)

    def warn(msg:String, refs:Any*) = logger warn checkFormat(msg, refs)

    def warn(t:Throwable, msg:String, refs:Any*) = logger warn (checkFormat(msg, refs), t)

    def critical(msg:String, refs:Any*) = logger error checkFormat(msg, refs)

    def critical(t:Throwable, msg:String, refs:Any*) = logger error (checkFormat(msg, refs), t)

}

/**
 * Note: implementation taken from scalax.logging API
 */
object Logging {  

    def loggerNameForClass(className: String) = {  
        if (className endsWith "$") className.substring(0, className.length - 1)  
        else className  
    }  

    def getLogger(logging: AnyRef) = LoggerFactory.getLogger(loggerNameForClass(logging.getClass.getName))  
}

Using slf4j and a wrapper is nice but the use of it's built in interpolation breaks down when you have more than two values to interpolate, since then you need to create an Array of values to interpolate.

A more Scala like solution is to use a thunk or cluster to delay the concatenation of the error message. A good example of this is Lift's logger

Log.scala Slf4jLog.scala

Which looks like this:

class Log4JLogger(val logger: Logger) extends LiftLogger {
  override def trace(msg: => AnyRef) = if (isTraceEnabled) logger.trace(msg)
}

Note that msg is a call-by-name and won't be evaluated unless isTraceEnabled is true so there's no cost in generating a nice message string. This works around the slf4j's interpolation mechanism which requires parsing the error message. With this model, you can interpolate any number of values into the error message.

If you have a separate trait that mixes this Log4JLogger into your class, then you can do

trace("The foobar from " + a + " doesn't match the foobar from " +
      b + " and you should reset the baz from " + c")

instead of

info("The foobar from {0} doesn't match the foobar from {1} and you should reset the baz from {c},
     Array(a, b, c))