matanox
matanox

Reputation: 13706

Scala reflection for logging purposes

Is it possible in Scala, to get the name of the member/function containing your code? e.g. in order to accomplish code like

def someFunc() {
  Log(s"error $x occurred in function ${SomeScalaAPI.enclosingFunction}")
}

that would produce a string:

"error X occurred in function someFunc"

without resorting to creating and catching exceptions? I would like to assume since exceptions know where they happen, this information might be available through some Scala or Java reflection api.. or, via a clever compile time macro?

Thanks!

Upvotes: 2

Views: 197

Answers (2)

stefan.schwetschke
stefan.schwetschke

Reputation: 8932

Time for some macro magic:

import language.experimental.macros

import reflect.macros.blackbox.Context

case class Location(filename: String, line: Int, column: Int, enclosingMethod : Option[String])

object PositionMacro {
  def currentLocation: Location = macro impl

  def impl(c: Context): c.Expr[Location] = {
    import c.universe._
    val pos = c.macroApplication.pos
    val owner = c.internal.enclosingOwner //Old, deprecated way: c.enclosingMethod.symbol
    val enclosingMethod = Some(owner).filter(_.isMethod).map(_.fullName)
    c.Expr(q"Location(${pos.source.path}, ${pos.line}, ${pos.column}, ${enclosingMethod})")
  }
}

You can use it like this:

def test(): Unit = {
  println(s"Called from ${PositionMacro.currentLocation}")
}

test()
// Called from Location(C:\Users\sschwets\AppData\Local\Temp\scratchPad.sc9.tmp,9,41,Some(A$A22.A$A22.test))

I added the following to my build.sbt file:

scalaVersion := "2.11.4"

libraryDependencies +=
  "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided"

Attention: This is my very first macro with the Scala 2.11 interface and quasi quotes. It works fine for me, but might format your hard disc, empty your bank account, spoil your children, anger your cat or run away with your wife / husband / whatever. So please be careful!

Upvotes: 6

dk14
dk14

Reputation: 22374

You can do it with java:

java.lang.Thread.currentThread.getStackTrace()(1).getMethodName

Example:

scala> def aa = {println(java.lang.Thread.currentThread.getStackTrace()(1).getMethodName)}
aa: Unit

scala> aa
aa

But it won't work for functions - only for methods:

scala> val zz = aa _
zz: () => Unit = <function0>

scala> zz()
aa

scala> val zzz = () => println(java.lang.Thread.currentThread.getStackTrace()(1).getMethodName)
zzz: () => Unit = <function0>

scala> zzz()
apply$mcV$sp

If you're looking for some compile-time macro, there is a simmilar solution - https://github.com/dk14/println-tracer; example of usage:

@trace class MyF {
  def call(param: Int): Int = if (param == 0) param else call(param - 1)
  def call2(param2: Int) = param2
  def call3(param2: Int) = ???
}

(new MyF).call(2)
(new MyF).call2(666)
Try{(new MyF).call3(666)}

Results:

 call(param = 2)
  call(param = 1)
   call(param = 0)
   call = 0
  call = 0
 call = 0
 call2(param2 = 666)
 call2 = 666
 call3(param2 = 666)
 call3 = scala.NotImplementedError: an implementation is missing

It uses println instead of logger (which is fixable) and requires macro-paradise library. All parameters/errors are printed automatically, which may be not what you want, but you can use it as a template if there is no logging-ready library around.

Upvotes: 1

Related Questions