What is Logging
Logging helps us to monitor the activity of our application in case if any error occurs we can check it from the logs and fix it. It also keeps track of the unusual errors or circumstances which can lead down fall of our application. Logging in Scala is very easy to add and maintain.
How to do Logging
In scala we have three levels of logging which are debug, info and error.
Most of Scala’s logging libraries have been some wrappers around a Java logging framework (slf4j, log4j etc), but as of March 2015, the surviving log libraries are all slf4j. These log libraries provide some sort of log object to which you can call info(…), debug(…), etc. I’m not a big fan of slf4j, but it now seems to be the predominant logging framework.
How many ways of Logging in Scala
- Scala Logging
This is how Scala Logging works:
Put this in your build.sbt
:
libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.4",
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")
}
String interpolation with Scala logging:
logger.error(s"my log message: $arg1 $arg2 $arg3")
It’s convenient, because you can simply call log methods, without checking whether the respective log level is enabled.
- Lego Woof
A pure (in both senses of the word!) Scala 3 logging library with no runtime reflection.
libraryDependencies ++= Seq(
"org.legogroup" %% "woof-core" % "$VERSION",
"org.legogroup" %% "woof-slf4j" % "$VERSION", // only if you need to use Woof via slf4j
"org.legogroup" %% "woof-http4s" % "$VERSION", // only if you need to add correlation IDs in http4s
)
import cats.effect.IO import org.legogroup.woof.* def program(using Logger[IO]): IO[Unit] = for _ <- Logger[IO].debug("This is some debug") _ <- Logger[IO].info("HEY!") _ <- Logger[IO].warn("I'm warning you") _ <- Logger[IO].error("I give up") yield ()
- Slf4j+ Logback classic
trait Logging { lazy val logger = LoggerFactory.getLogger(getClass) implicit def logging2Logger(anything: Logging): Logger = anything.logger }
and can be used anywhere
class X with Logging {
logger.debug("foo")
debug("bar")
}
but this approach of course uses a logger instance per class instance.
- log4j
libraryDependencies += "org.apache.logging.log4j" % "log4j-api" % "2.17.0"
libraryDependencies += "org.apache.logging.log4j" % "log4j-core" % "2.17.0"
example:
import org.apache.logging.log4j.scala.Logging
import org.apache.logging.log4j.Level
class MyClass extends BaseClass with Logging {
def doStuff(): Unit = {
logger.info("Doing stuff")
}
def doStuffWithLevel(level: Level): Unit = {
logger(level, "Doing stuff with arbitrary level")
}
}
The output from the call to logger.info() will vary significantly depending on the configuration used.
Best Scala Logging Practices
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
Conclusion
So, after reading this blog, we are able to add logs in Scala program and able to understand how, where and why we use logging.
