CodeX

Everything connected with Tech & Code. Follow to join our 1M+ monthly readers

Follow publication

Flog: a functional logger for Scala

--

Logging can, unfortunately, be either a blessing or a curse. For production systems, logging is a discipline all of its own. But often, it seems, it’s not used as much as it could be in code development. It should be so simple that it’s the first, not the last, resort when trying to understand why code is not working as expected. And nowhere is this more true than in functional programming languages. That’s because logging is, well, statement-oriented rather than expression-oriented. Even if you don’t bother with logging, but use println instead, you will still have to wrestle with your code because println yields Unit, i.e. it is statement-oriented.

That’s why I developed Flog, a functional logger for Scala (2). It’s really easy to use. Let’s say you want to define a val x of type X as some expression (expr) and you write:

val x: X = expr

But now you decide that you’d like to know what the actual value of expr is. Just add a msg (the log message, a String) and the operator !! as follows:

val flog = Flog[MyClass]
import flog._
// other codeval x: X = msg !! expr

That’s it! That’s basically all you have to do. A representation of expr will be appended to the log and, otherwise, the program behaves exactly as it did before. But what if you don’t want to see that value in the logs again? Just replace the !! operator by |! and the program will run the same but there will be no logging at this point (oh, and by the way, msg will not be evaluated).

The !! operator corresponds to info if you are using a standard logger. You can also use !? for debug or !?? for trace.

Alternatively, you can emasculate Flog for all its uses in a module simply by replacing Flog() with Flog().disabled. Eventually, when you are sure you never want to log this expression again, you simply remove the msg |! construct altogether.

The actual logging utility used, by default, by Flog is org.slf4j.LoggerFactory.getLogger for the Flog class itself. For serious use, you’ll want to use a logger for your own class. You can do this by instantiating flog as:

val flog: Flog = Flog[MyClass]

But, you’re also probably wondering how the logger will know how to represent instances of type X. If X is some compound but familiar type such as Iterable[Int], Future[Double], or Option[String] (there are many other supported containers) then the logger will just do the sensible thing without any extra work on your part. But what if X is a type defined in your own code? Is it a case class (or other Product)? Then simply invoke the loggableN method with the appropriate number of parameters:

case class Complex(real: Double, imag: Double)
implicit val complexLoggable: Loggable[Complex] = new Loggables {}.loggable2(Complex)
"test complex" !! Complex(1, 0)

But, what if your home-grown class isn’t a Product? You have two choices then. The quick and dirty version where you use !| operator. This will apply toString to the value and use that. The better approach, and the necessary one if you will be logging values of types such as Try[X] or List[X], is to create an implicit value of Loggable[X] in the companion object of X. This is easy to do, and is described in the README.

You can find this logging library at https://github.com/rchillyard/Flog. You can include flog in your project using the following sbt directive:

libraryDependencies += “com.phasmidsoftware” % “flog_2.13” % “1.0.8”

Edited 2021/9/10 to add sbt dependency information.

Sign up to discover human stories that deepen your understanding of the world.

--

--

CodeX
CodeX

Published in CodeX

Everything connected with Tech & Code. Follow to join our 1M+ monthly readers

Robin Hillyard
Robin Hillyard

Written by Robin Hillyard

I’ve been addicted to programming for over 50 years now. I currently teach two classes to graduates at Northeastern University, Boston, USA.

No responses yet

Write a response