rightfold github contact

Coming Soon

Latest Posts

Logging in Scala

#scala #logging

I recently had the need to log some messages to a file in Scala. I tried to find a suitable logging library but they all seemed very convoluted for something that should be very simple, so I decided to write my own.

Let’s first define a type that represents a log record. A log record represents a single log message. Each log record has a log level, which specifies the severity of the event logged. This is useful for filtering, and has a natural ordering for exactly this purpose. A log record also has a message, the time at which it was logged and additional metadata such as stack traces and user IDs.

import java.time.Instant

sealed abstract class Level private[logging](val level: Int) extends Ordered[Level] {
  override def compare(other: Level) = level - other.level

case object DEBUG extends Level(0)
case object INFO extends Level(1)
case object WARNING extends Level(2)
case object ERROR extends Level(3)
case object CRITICAL extends Level(4)

case class Record(level: Level, message: String, time: Instant, data: Map[Symbol, Any])

Now the loggers. A logger is simply a function from Record to Unit. Why’d you need more? We also define a few useful methods on loggers so we can manipulate and filter records easily.

type Logger = Record => Unit

def dispatch(targets: Logger*): Logger =
  { record => for (target <- targets) { target(record) } }

implicit class RichLogger(val logger: Logger) extends AnyVal {
  def map(f: Record => Record): Logger =
    logger compose f

  def filter(p: Record => Boolean): Logger =
    { record => if (p(record)) { logger(record) } }

And now we define utility functions for producing log messages. Since there is often only one logger in scope, we allow it to be passed implicitly. Additionally, logging debug messages adds a stack trace to the record.

def log(level: Level, message: String, data: (Symbol, Any)*)
       (implicit logger: Logger) =
  logger(Record(level, message, Instant.now(), Map(data: _*)))

def debug(message: String, data: (Symbol, Any)*)
         (implicit logger: Logger) = {
  val stackTrace = Thread.currentThread().getStackTrace()
  val dataWithStackTrace = data :+ ('stackTrace -> stackTrace)
  log(DEBUG, message, dataWithStackTrace: _*)

def info(message: String, data: (Symbol, Any)*)(implicit logger: Logger) =
  log(INFO, message, data: _*)

def warning(message: String, data: (Symbol, Any)*)(implicit logger: Logger) =
  log(WARNING, message, data: _*)

def error(message: String, data: (Symbol, Any)*)(implicit logger: Logger) =
  log(ERROR, message, data: _*)

def critical(message: String, data: (Symbol, Any)*)(implicit logger: Logger) =
  log(CRITICAL, message, data: _*)

And that’s a simple logging library in Scala! Writing logs to files, sockets and other destinations is application-specific and may be included in a future blog post. Such mechanisms can be achieved by simply creating instances of Logger. Any maybe some day I will make this into a library and publish it on GitHub. :)