Suppose I want to wrap code that can throw exceptions with a try-catch block that logs the exception and continues. Something like:
loggingExceptions {
// something dangerous
}
Ideally, I would like to use for logging the Logger defined on the calling object, if any (and if none, get a compile-time error). I'd love to define something like this:
def loggingExceptions[L <: { def logger: Logger }](work: => Unit)(implicit objectWithLogger: L): Unit = {
try {
work
} catch {
case t: Exception => objectWithLogger.logger.error(t.getMessage)
}
}
where objectWithLogger would somehow "magically" expand to "this" in client code. Is this (or a similar thing) possible?
It can in fact be done just as you want. The other answerers surrendered too quickly. No white flags!
package object foo {
type HasLogger = { def logger: Logger }
implicit def mkLog(x: HasLogger) = new {
def loggingExceptions(body: => Unit): Unit =
try body
catch { case ex: Exception => println(ex) }
}
}
package foo {
case class Logger(name: String) { }
// Doesn't compile:
// class A {
// def f = this.loggingExceptions(println("hi"))
// }
// 1124.scala:14: error: value loggingExceptions is not a member of foo.A
// def f = this.loggingExceptions(println("hi"))
// ^
// one error found
// Does compile
class B {
def logger = Logger("B")
def f = this.loggingExceptions(println("hi"))
def g = this.loggingExceptions(throw new Exception)
}
}
object Test {
def main(args: Array[String]): Unit = {
val b = new foo.B
b.f
b.g
}
}
// output
//
// % scala Test
// hi
// java.lang.Exception
Debilski's answer will work, but I'm not sure I see a good reason to use a structural type (i.e. { def logger: Logger }
) here. Doing so will incur extra runtime overhead whenever logger
is invoked, since the implementation of structural types relies on reflection. The loggingExceptions
method is closely tied to logging, so I would just make it part of a Logging trait:
trait Logging {
def logger: Logger
final def loggingExceptions(body: => Unit) =
try body catch { case e: Exception => logger.error(e.getMessage) }
}
trait ConcreteLogging extends Logging {
val logger = // ...
}
object MyObject extends SomeClass with ConcreteLogging {
def main {
// ...
loggingExceptions {
// ...
}
}
}
You could add a trait to all classes which want to use def loggingExceptions
and in this trait add a self-type which expects def logger: Logger
being available.
trait LoggingExceptions {
this: { def logger: Logger } =>
def loggingExceptions(work: => Unit) {
try { work }
catch { case t: Exception => logger.error(t.getMessage) }
}
}
object MyObjectWithLogging extends OtherClass with LoggingExceptions {
def logger: Logger = // ...
def main {
// ...
loggingExceptions { // ...
}
}
}