Go to Code Line from Logcat Output Line

Let’s start by looking at our typical good’ol logcat window in Android Studio like this:

Now, when you get this kind of logs while coding your awesome apps, then your first question would be: Where is the source code line for this log?. So that you can know what problem is and how to fix it. But, Android Studio doesn’t let you jump to the source code’s exact line unless its a RuntimeException or simply called as Crash.

Now, some developers go to source code’s exact line by navigation (Ctrl + N) and searching about tag etc. Or there might be other ways. Let’s look at more cleaner and easier way to fix this issue.

After the fix, your logcat will be something similar to this.

Traditional Way (using android.util.Log)

Add this class in your app code, and call it like LogUtil.d("MyTag", "My Custom Message") instead of Log.d("tag", "message").

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.util.Log;

public class LogUtil {

    private static final int CALL_STACK_INDEX = 1;
    private static final Pattern ANONYMOUS_CLASS = Pattern.compile("(\\$\\d+)+$");

    public static String prependCallLocation(String message) {
        // DO NOT switch this to Thread.getCurrentThread().getStackTrace(). The test will pass
        // because Robolectric runs them on the JVM but on Android the elements are different.
        StackTraceElement[] stackTrace = new Throwable().getStackTrace();
        if (stackTrace.length <= CALL_STACK_INDEX) {
            throw new IllegalStateException(
                    "Synthetic stacktrace didn't have enough elements: are you using proguard?");
        }
        String clazz = extractClassName(stackTrace[CALL_STACK_INDEX]);
        int lineNumber = stackTrace[CALL_STACK_INDEX].getLineNumber();
        message = ".(" + clazz + ".java:" + lineNumber + ") - " + message;
        return message;
    }

    /**
     * Extract the class name without any anonymous class suffixes (e.g., {@code Foo$1}
     * becomes {@code Foo}).
     */
    private static String extractClassName(StackTraceElement element) {
        String tag = element.getClassName();
        Matcher m = ANONYMOUS_CLASS.matcher(tag);
        if (m.find()) {
            tag = m.replaceAll("");
        }
        return tag.substring(tag.lastIndexOf('.') + 1);
    }
    
    public static void d(String tag, String message)
    {
        String newMessage = LogUtil.prependCallLocation(message);
        Log.d(tag, newMessage);
    }
    
    public static void w(String tag, String message)
    {
        String newMessage = LogUtil.prependCallLocation(message);
        Log.w(tag, newMessage);
    }
    
    public static void e(String tag, String message)
    {
        String newMessage = LogUtil.prependCallLocation(message);
        Log.e(tag, newMessage);
    }
    
    public static void v(String tag, String message)
    {
        String newMessage = LogUtil.prependCallLocation(message);
        Log.v(tag, newMessage);
    }
    
    public static void i(String tag, String message)
    {
        String newMessage = LogUtil.prependCallLocation(message);
        Log.i(tag, newMessage);
    }
    
    public static void wtf(String tag, String message)
    {
        String newMessage = LogUtil.prependCallLocation(message);
        Log.wtf(tag, newMessage);
    }   
}

Timber Way

If you use Timber, then you can use this tree and attach it to the timber like a normal way.

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import timber.log.Timber;

public class LinkingDebugTree extends Timber.DebugTree {

    private static final int CALL_STACK_INDEX = 4;
    private static final Pattern ANONYMOUS_CLASS = Pattern.compile("(\\$\\d+)+$");

    @Override
    protected void log(int priority, String tag, String message, Throwable t) {
        // DO NOT switch this to Thread.getCurrentThread().getStackTrace(). The test will pass
        // because Robolectric runs them on the JVM but on Android the elements are different.
        StackTraceElement[] stackTrace = new Throwable().getStackTrace();
        if (stackTrace.length <= CALL_STACK_INDEX) {
            throw new IllegalStateException(
                    "Synthetic stacktrace didn't have enough elements: are you using proguard?");
        }
        String clazz = extractClassName(stackTrace[CALL_STACK_INDEX]);
        int lineNumber = stackTrace[CALL_STACK_INDEX].getLineNumber();
        message = ".(" + clazz + ".java:" + lineNumber + ") - " + message;
        super.log(priority, tag, message, t);
    }

    /**
     * Extract the class name without any anonymous class suffixes (e.g., {@code Foo$1}
     * becomes {@code Foo}).
     */
    private String extractClassName(StackTraceElement element) {
        String tag = element.getClassName();
        Matcher m = ANONYMOUS_CLASS.matcher(tag);
        if (m.find()) {
            tag = m.replaceAll("");
        }
        return tag.substring(tag.lastIndexOf('.') + 1);
    }
}

If you liked this article, you can read my new articles below:


Wajahat Karim is Pakistan’s first Google Developer Expert in Android. As an experience Android developer, he deeply cares about it and keeps writing and speaking about it. He has written two worldwide 300+ pages books on Android development with more than 100 articles around the internet either on his website or his Medium publications. He is also a passionate contributor in open source and has created many Android libraries used by thousands of developers in their apps worldwide. As active public speaker, he spends lots of time giving talks in conferences and motivating people on Android development. You can find Wajahat most active on Twitter @WajahatKarim where he regularly shares all the good stuff about Android and community building.

Author's picture

Wajahat Karim

🔥 Google Dev Expert (GDE) in Android .
📱 Android Dev. 💻 Open Source Contributor . 📝 Technical Writer . 🎤 Public Speaker

Senior Android Developer

Karachi, Pakistan