Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically get the current line number

Is there a way in Java to dynamically get the current line number through reflection or some awesome API? Just like when exceptions occur, the line number gets printed out in the stack trace like this:

at weblogic.rmi.cluster.ClusterableRemoteRef.invoke(ClusterableRemoteRef.java:348)

Now is there a way to print or log like in the below code?

log.error("Error in: " + this.getClass.getName() + "at line #"+ this.getClass.getActualLine());

You may ask, why don't I simply print the line number? Well because the code may get deleted or added before the specific log.error() method call.

like image 344
VSZM Avatar asked Jul 04 '13 14:07

VSZM


4 Answers

You can create a Throwable and use its StackTraceElements:

  System.err.println(new Throwable().getStackTrace()[0].getLineNumber());

As @Joachim said, you can also use Thread.getStackTrace(), e.g. like

  System.err.println(Thread.currentThread().getStackTrace()[1].getLineNumber());

Be aware that the second approach returns a somewhat different array - you need to use the array element with index 1 to get the current line number, since it includes the call to getStackTrace() itself as the first element.

Also note the comments about Logging and performance from @Joachim's answer.

like image 125
Andreas Fester Avatar answered Nov 15 '22 19:11

Andreas Fester


First of all: If anything, then the logging pattern (or layouter, or whatever your logging framework calls that part) should do this. The logger call in your code should only write the actual business information. Information about the where should be added by the logger.

Next: getting that kind of operation is expensive (in terms of time), because Java is not optimized for this. At runtime, the JVM need to inspect its state, load/parse debug information and find the line number corresponding to a given instruction. That's why this kind of information is usually just given when an exception occurs (in which case we already have a problem and know that the time spent will usually be worth it).

And last but not least: if for some reason you need that information on your own, you can use Thread.getStackTrace() and inspect the second StackTraceElement on it.

like image 16
Joachim Sauer Avatar answered Nov 15 '22 19:11

Joachim Sauer


I was able to use the Thread.currentThread().getStackTrace() method to create a set of functions that work together to produce the line number of the code that called the first method, like so:

/** @return The line number of the code that ran this method
 * @author Brian_Entei */
public static int getLineNumber() {
    return ___8drrd3148796d_Xaf();
}

/** This methods name is ridiculous on purpose to prevent any other method
 * names in the stack trace from potentially matching this one.
 * 
 * @return The line number of the code that called the method that called
 *         this method(Should only be called by getLineNumber()).
 * @author Brian_Entei */
private static int ___8drrd3148796d_Xaf() {
    boolean thisOne = false;
    int thisOneCountDown = 1;
    StackTraceElement[] elements = Thread.currentThread().getStackTrace();
    for(StackTraceElement element : elements) {
        String methodName = element.getMethodName();
        int lineNum = element.getLineNumber();
        if(thisOne && (thisOneCountDown == 0)) {
            return lineNum;
        } else if(thisOne) {
            thisOneCountDown--;
        }
        if(methodName.equals("___8drrd3148796d_Xaf")) {
            thisOne = true;
        }
    }
    return -1;
}

Hope this helps! I put these in a utility class so that they are out of the way, but still easily accessible. The second method is private to prevent any other method other than the first method from calling it so that it always works correctly.

like image 14
Brian_Entei Avatar answered Nov 15 '22 18:11

Brian_Entei


Starting from JDK9 you can use the StackWalker. This gave me around a 50% of speed increase over using getStackTrace().

int get_line_number() {
    return StackWalker.getInstance(StackWalker.Option.SHOW_HIDDEN_FRAMES).walk(
      (s) -> s.skip(1).findFirst()).get().getLineNumber();
}

My stream could be done better but I have no experience with it, and I really don't like streams in general, so feel free to edit my post to improve this part.

like image 6
clankill3r Avatar answered Nov 15 '22 18:11

clankill3r