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

1

5

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.

Dardan answered 30/10, 2017 at 10:10 Comment(10)
not a full answer, but take a look at github.com/lihaoyi/sourcecodeGammon
Looks same: #23261490Mccarron
Possible duplicate of Access code file and line number from Scala macro?Capitation
@Seth Tisue if you can explain to me how the above question solves my problem please do. As far as I can see the code in the top 2 answer isn't valid in scala 2.12 and the first answer seems outright arcane... on top of that none of the answers are marked as correct so I'm unsure even the original asker found them useful. So, hopefully you can see how they may be a bit vague to someone that is no very used to Scala compile time programmingDardan
This post addresses some of the issues with what you're after.Gutta
That post specifies what I'm after is impossible in java (yes, obviously writing the file name and line manually is possible, but that doesn't solve the problem described). Using a stack trace is obviously doable but very slow, for something like logging outside of debugging (and even when debugging, if one id debugging concurrent code) is basically unacceptable, hence why I'm asking for a way to do this using macors, but I should clarify that in the original question.Dardan
It's still a duplicate, according to my understanding of how SO works. If you found it when you were originally researching this, you could have saved everyone some time by including the link in the first place and being clear about how your question related to the previous question. If existing answers to that question are unsuitable, why not comment on those answers and say why they are unsuitable, to help the next person with the same question? The goal is to improve SO's Scala coverage for everyone, not merely to serve your immediate need. Allowing duplicates makes everything worse.Capitation
@Seth Tisue I didn't link the question or try to ask my question in the comments because it is: a) Poorly worded b) Lacks examples or a more detailed explaination of what the op needs c) Without any valid answer. As such, I think a new question is better than trying to get involved in that discussion. The question is, again, 4 years old, if it was newer I would consider trying to participate in the discussion there.Dardan
Why do you think stack traces are slow? They aren't, if used properly. There is a number of tricks to make stack traces fast for this particlar task: JVM TI, private sun.misc.JavaLangAccess API or new JDK 9 StackWalker API.Eisteddfod
They are slower than simply having a const string that's generated at compile time and in this case I don't need any of the extra info, as such I don't think they are the right tool for the job.Dardan
A
7

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

Ahlers answered 2/11, 2017 at 13:17 Comment(3)
I'd like to avoid a logging library since I need a more customized solution. I've considered Sourcecode but I'd rather not take an extra dependency just for building this macro. I've tried reading it's source code but it's structured in a very awkward way and hard to understand. If I'm unable to roll my own solution I will probably end up trying to read a bit more about scala's experimental macro system and trying to build a solution based on Sourcecode. My other problem with it is that it may break any time, since scala's macro system is still experimental.Dardan
@Dardan Hm, I don't understand the reasoning really why not to introduce a library if it is active and matches your use case quite well, but it is up to you of course. Keep in mind when rolling your own you introduce an "internal" dependency that then needs to be maintained by yourself, instead of relying on other (and multiple) people's work.Ahlers
@Dardan Also when building a solution based on Sourcecode, consider a) contributing your changes to it, or b) making your work public so that other people can benefit from your (improved) solution.Ahlers

© 2022 - 2024 — McMap. All rights reserved.