Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala get the line and file of a functions invocation at compile-time

What I want to have is a function that is able to print the lines + file from where it was called.

The behavior would be something like this:

log("x") //Called at line 15 of file Blax.scala

Should print

Blax.scala, line 15, message: x

Something analogous to the following code for c++:

    void _log(const char* message, const char* file, int line) { std::cout << file << ", " << line << ", message: " <<
message
<< "\n";}
    #define log(message) _log(message, __FILE__, __LINE__)

Is this possible to do in Scala? Is it possible to do it in a way that is half-readable and somewhat compatible with future and older version of the compiler?

I've been trying to implement something similar based on the macro section here: https://docs.scala-lang.org/overviews/macros/overview.html

But the code is extremely hard to read and the articles don't seem to cover anything that closely related to what I need.

I guess my question could be split into two or three:

a) Is it possible to do any type of "traditional" code inlining in Scala by calling a macro just like a function?

So basically calling blax(a) which expands into impl_blax(a, "b", __C__) before compilation.

b) What is the "recommended" way of getting the current line number and file name at compile time in Scala?

c) If either a or b is impossible, is there still away to accomplish what I want?

Clarification:

Yes, this is easily doable using a Stack trace, but throwing an exception and getting the Stack trace is unbearably slow and not suitable for anything but certain niche debug cases. I'm looking for a no-overhead or low overhead solution, hence why I asked for one using macros

Edits (In order to answer flags):

@Seth Tisue on the marking of the question as duplicate

If the question you marked this as a duplicate of solves my problem I'd love to hear how. Some of the links in the original questions lead to 404, the code of the highest voted answer (which is not marked correct) doesn't seem to do anything related to what I want and parts of it are marked as deprecated in scala 2.12... I did stumble upon the question but I hoped that being more explicit about the problem may yield an answer which that previously linked 4 years old question doesn't have.

like image 259
George Avatar asked Oct 30 '17 10:10

George


1 Answers

Unless you want to do this as an exercise and hence roll your own solution, I'd say the recommended way is to reuse libraries that exist, have been tested, and fulfil your requirements.

I found two candidates:

1. Sourcecode

The sourcecode library is based on macros and provides metadata at compile-time.

Example (taken from their page adjusted to your question):

object Main extends App {
  def log(message: String)(implicit line: sourcecode.Line, file: sourcecode.File) = 
    println(s"${file.value}, line ${line.value}, message: $message")

  log("x")
}

This will print:

/Users/jhoffmann/Development/sourcecode/src/main/scala/Main.scala, line 5, message: x

2. Scala-logging

As a standard alternative just use plain old logging. Take a look at scala-logging. Using logback as backend you can use %file, %line and %message in your pattern.

As an example, consider the following patten configured in logback.xml:

<pattern>%file, line %line, message: %message%n</pattern>

Then this code:

object Main extends App with LazyLogging {
  logger.info("x")
}

will print:

Main.scala, line 4, message: x

like image 178
Jens Hoffmann Avatar answered Nov 18 '22 00:11

Jens Hoffmann