Tracing function calls

Is there a way in IntelliJ to trace a function? This is something which is normal in all dialects of Lisp I’ve used. I’m not sure whether the concept exists for non-lisp languages. What I’d expect is to be able to point to a function definition (or method definition) and somehow mark it so that when the program runs when this function is called an indication is printed to the log. normally you can trace entrance and/or exit of a function call, tracing the values passed to the function, and the return value.

The IntelliJ debugger supports simple stdout logging at breakpoints - but method breakpoints don’t seem to work for Scala.

Outside the IDE, I am not aware of any Scala-native off-the-shelf solutions. There are AspectJ based approaches in Javaland.

You could roll your own using macros. When I needed something similar for a toy project, I repurposed the benchmark example from this blog post.

Method breakpoints are probably not needed in this case. Just setting a breakpoint at the first and/ or last instruction in a method implementation should be enough and it works reliably and efficiently.

Sorry tarsa for the silly question but what is a breakpoint? In lisp a breakpoint is a place where the execution pauses. The user sets a breakpoint at an expression where he wants the execution to wait, and the at runtime, when the breakpoint is reached, the user may examine the environment, or the stack and decide to abort or continue. Are you using a more general definition?

Unless I misunderstand a breakpoint, how does this help to implement tracing?
Tracing (for me) is some sort of intervention at a function definition, for which the system prints the values being passed to the function, prints that the function is being called (at which stack depth), and then prints the return value.

I’m struggling to understand the connection between breakpoint and tracing?

The only way I know how to do proper tracing (programatically) is using AspectJ. It works very well for scala defs as well as they belong to classes the same way java methods do. AspectJ can be told to wire up any class, using aop.xml config.

AspectJ sounds really scary, especially to a non-Java programmer.

It comes as a bit of a surprise to me that tracing is not part of the state-of-the art.

Just try it out in IntelliJ. You can configure a debugger breakpoint to not suspend, but to log a generic message (optionally plus some custom expression to be evaluated) when it is hit.

However, this doesn’t cover tracing enter/exit, arguments, etc.

1 Like

Well, as they say - “Any sufficiently advanced technology is indistinguishable from magic.”. AspectJ seems magical at first, but really isn’t that scary.

Frankly, I never missed such a feature so far. (The toy project mentioned above was about writing quick design studies for component interactions and dumping calls in order to generate sequence diagrams.) If I require something like method level tracing (for debugging or in production), it usually feels like I need customized messages, anyway. What’s your specific use case?

The use case is usually, that some function is doing something weird situation, and I don’t know which function or which situation. So I trace several of the functions, run my program and look at the trace output.

Here’s an example of the output:

CL-USER> (defun factorial (n)
           (case n
             ((0 1) 1)
             (t (* n (factorial (1- n))))))
FACTORIAL
CL-USER> (factorial 10)
3628800
CL-USER> (trace factorial)
(FACTORIAL)
CL-USER> (factorial 10)
  0: (FACTORIAL 10)
    1: (FACTORIAL 9)
      2: (FACTORIAL 8)
        3: (FACTORIAL 7)
          4: (FACTORIAL 6)
            5: (FACTORIAL 5)
              6: (FACTORIAL 4)
                7: (FACTORIAL 3)
                  8: (FACTORIAL 2)
                    9: (FACTORIAL 1)
                    9: FACTORIAL returned 1
                  8: FACTORIAL returned 2
                7: FACTORIAL returned 6
              6: FACTORIAL returned 24
            5: FACTORIAL returned 120
          4: FACTORIAL returned 720
        3: FACTORIAL returned 5040
      2: FACTORIAL returned 40320
    1: FACTORIAL returned 362880
  0: FACTORIAL returned 3628800
3628800
CL-USER> (defun factorial (n &optional (acc 1))
           (case n
             ((0 1) acc)
             (t (factorial (1- n) (* n acc)))))
WARNING: redefining COMMON-LISP-USER::FACTORIAL in DEFUN
FACTORIAL
CL-USER> (factorial 10)
  0: (FACTORIAL 10)
    1: (FACTORIAL 9 10)
      2: (FACTORIAL 8 90)
        3: (FACTORIAL 7 720)
          4: (FACTORIAL 6 5040)
            5: (FACTORIAL 5 30240)
              6: (FACTORIAL 4 151200)
                7: (FACTORIAL 3 604800)
                  8: (FACTORIAL 2 1814400)
                    9: (FACTORIAL 1 3628800)
                    9: FACTORIAL returned 3628800
                  8: FACTORIAL returned 3628800
                7: FACTORIAL returned 3628800
              6: FACTORIAL returned 3628800
            5: FACTORIAL returned 3628800
          4: FACTORIAL returned 3628800
        3: FACTORIAL returned 3628800
      2: FACTORIAL returned 3628800
    1: FACTORIAL returned 3628800
  0: FACTORIAL returned 3628800
3628800
CL-USER> (defun factorial (n &optional (acc 1))
           (declare (optimize (speed 3))) // allow tail call optimization
           (case n
             ((0 1) acc)
             (t (factorial (1- n) (* n acc)))))
WARNING: redefining COMMON-LISP-USER::FACTORIAL in DEFUN
FACTORIAL
CL-USER> (factorial 10)
  0: (FACTORIAL 10)
  0: FACTORIAL returned 3628800
3628800
CL-USER> 

I very much miss trace, too.

1 Like

I have on occasion learned how to use a JVM debugger, but then never used it and therefore forgot about it again.

I do seem to vaguely remember that it can tell you the return value of methods, or am I confusing it with my C++ time?

Instead of using a debugger, I find it easier to place a few println, sometimes inside an if, and an occasional Thread.dumpStack. If you doubt the correctness of a particular complex method, it’s time for a good unit test.

Classic tracing tends to be less helpful in heavily multi-threaded code – and most serious Scala code is multi-threaded, in my experience. When you don’t have a simple call stack, it can be challenging to interpret a trace.

Tracing usefully in the multi-threaded world is tricky, although I gather that the ZIO project has been making some interesting strides there…

For that sort of thing I’ve used byteman in the past.

It’s a Java agent which is able to inject bytecode into an application, without recompilation.

A trace script looks like this:

RULE trace 
CLASS java.net.URL
METHOD openStream
IF true
DO traceln("*** start for thread: "+ $0.getName())
ENDRULE

Note: this is mostly useful if you want to trace / adjust / modify code that you don’t have under control. And you need to be aware of Scala’s name mangling for objects and methods to use this.

@sangamon already responded:

Overall, a breakpoint under JVM can do zero (when it’s muted) or more of following things:

  • stop execution at given point
  • log a message with e.g. arguments, other variables, etc
  • evaluate condition before skipping or doing any of the above

Therefore you can use it as non-interrupting logging mechanism that doesn’t require restarting application. In some cases that’s sufficient.

IntelliJ has thing called Live Templates: Live templates | IntelliJ IDEA Documentation
There are different live templates for different languages, e.g. there’s soutp template for Java defined at intellij-community/resources/src/liveTemplates/Java.xml at master · JetBrains/intellij-community · GitHub (search for name="soutp"). It turns code like (<caret here> is not in the code, it only shows the cursor position):

public class Soutp {
    void aMethod(int arg1, double arg2, String... arg3) {
        soutp<caret here>
    }
}

to below code after hitting Tab key:

import java.util.Arrays;

public class Soutp {
    void aMethod(int arg1, double arg2, String... arg3) {
        System.out.println("arg1 = " + arg1 + ", arg2 = " + arg2 + ", arg3 = " + Arrays.deepToString(arg3));
    }
}

It’s not available for Scala language out of the box, but maybe it’s feasible to provide it somehow.