Log Best Practices for Android Debug

Posted by greedyisg00d on Mon, 18 Nov 2019 01:45:52 +0100

This WeChat public number "Android Traveler" was launched.

background

Debugging is an essential part of development.

When we want to determine the logic of a project, when we want to understand the life cycle of an interface, when we find new logic inconsistent with what we expect, when we think there is a problem with the data...

There are two ways to debug:

The first is to run APP in debug mode, then run the program to the specified location through breakpoints for analysis.

The second is to log the output to determine if the program is running to that location and the data at that time.

This article focuses primarily on the second approach.

In Android, the system API used for logging is Log. Do you think you have finished using it directly?

encapsulation

Assuming you are using the system's API directly where you need to print your logs, you will be "pulled all over" when you encounter the following situations.

Scenario 1: If I print logs using a tripartite library's log API, I'll find all the places where the project is used and replace each one.

Scenario 2: If I want to print the logs in a development environment, the release environment does not print, at which point each location needs to be handled separately.

So we need to do a layer of packaging before using Log for log printing.

Assuming our class name is ZLog, the code is as follows:

import android.util.Log;

/**
 * Created on 2019-10-26
 *
 * @author Zengyu.Zhan
 */
public class ZLog {
    public static int v(String tag, String msg) {
        return Log.v(tag, msg);
    }

    public static int d(String tag, String msg) {
        return Log.d(tag, msg);
    }

    public static int i(String tag, String msg) {
        return Log.i(tag, msg);
    }

    public static int w(String tag, String msg) {
        return Log.w(tag, msg);
    }

    public static int e(String tag, String msg) {
        return Log.e(tag, msg);
    }
}

After that, for Scenarios 1 and 2, we need to modify only the ZLog class, not everywhere that uses the ZLog.

Provide log printing control

We know that log printing can contain sensitive information and that too much log printing can affect the performance of APPs, so we usually open the log at development time and turn it off before publishing the APP.

So we need to provide a flag bit here to control whether the log is printed or not.

import android.util.Log;

/**
 * Created on 2019-10-26
 *
 * @author Zengyu.Zhan
 */
public class ZLog {
    private static boolean isDebugMode = false;
    public static void setDebugMode(boolean debugMode) {
        isDebugMode = debugMode;
    }

    public static int v(String tag, String msg) {
        return isDebugMode ? Log.v(tag, msg) : -1;
    }

    public static int d(String tag, String msg) {
        return isDebugMode ? Log.d(tag, msg) : -1;
    }

    public static int i(String tag, String msg) {
        return isDebugMode ? Log.i(tag, msg) : -1;
    }

    public static int w(String tag, String msg) {
        return isDebugMode ? Log.w(tag, msg) : -1;
    }

    public static int e(String tag, String msg) {
        return isDebugMode ? Log.e(tag, msg) : -1;
    }
}

By default, log printing is not turned on to avoid developers forgetting the settings.

Output comparison of normal log and run stack system log in console

Now we use ZLog in APP to print the log, the code is:

ZLog.setDebugMode(true);
ZLog.e("ZLog", "just test");

The output is as follows:

We will now add the following code:

String nullString = null;
if (nullString.equals("null")) {
}

After running, the console will display an abnormal run stack of null pointers as follows:

You can see that the Run stack information shows which file has a null pointer and which line.In our example, there are 24 lines of MainActivity.java.

And clicking the blue link cursor will direct you to the wrong location.

If we can also click on a regular log and jump to the corresponding location, our development efficiency will be greatly improved.

ZLogHelper

Since there are links within the runway stack to jump, we can get the print location of the log from the stack information.

Let's go directly to the code:

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

    public static String wrapMessage(int stackIndex, String message) {
        // DO NOT switch this to Thread.getCurrentThread().getStackTrace().
        if (stackIndex < 0) {
            stackIndex = CALL_STACK_INDEX;
        }
        StackTraceElement[] stackTrace = new Throwable().getStackTrace();
        if (stackTrace.length <= stackIndex) {
            throw new IllegalStateException(
                    "Synthetic stacktrace didn't have enough elements: are you using proguard?");
        }
        String clazz = extractClassName(stackTrace[stackIndex]);
        int lineNumber = stackTrace[stackIndex].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);
    }
}

Here we provide a wrap Message method to wrap messages, just by name.

The StackTraceElement is also analyzed within the method.

A control is also made to avoid negative stackIndex es.

Your little buddy might be curious, why open stackIndex to the public?

Because you print the log differently, the stackIndex here also needs to be adjusted.

Inside the method is the StackTraceElement, which is related to your method hierarchy.

Let's take the two most common forms of log printing as examples to illustrate how stackIndex is passed here and how this ZLogHelper is used.

Direct Code Use

We use it directly in MainActivity.java, and stackIndex passes in 1.

Log.e("ZLog", ZLogHelper.wrapMessage(1, "just test"));

The console output is as follows:

You can see the number of classes and lines in which the code is located to display as link text, and click to locate it.

Encapsulation done

Normally we wrap logs, so suppose we have a Log Utils class that we call in MainActivity.java.

LogUtils.java:

class LogUtils {
    public static  void loge() {
        Log.e("ZLog", ZLogHelper.wrapMessage(2, "just test"));
    }
}

MainActivity.java:

LogUtils.loge();

Let's look at the results before we analyze them.The console output is as follows:

You can see that you have actually located a specific place to use MainActivity.java.

So why does the stackIndex passed in here differ from the first, which is 2 instead of 1?

The simple answer is that when you change to 1, the output console displays the log print statements in LogUtils.Here it is:

Log.e("ZLog", ZLogHelper.wrapMessage(2, "just test"));

So you can actually see a pattern, and this can also be found from the code.

Because the parsing call location in the code is based on the stack, the StackTraceElement is analyzed, so the situation continues to be used, passing in 1.Scenario two has one more layer of function calls and is wrapped by the loge method.So you need to pass in 2.If you do one more layer, you need to pass in 3.Knowing this, the following tool classes believe you can understand it.

ZLog

If you don't want to manually pass in stackIndex yourself, you can use the tool class ZLog provided by us directly.

public class ZLog {
    private static boolean isDebugMode = false;
    public static void setDebugMode(boolean debugMode) {
        isDebugMode = debugMode;
    }

    private static boolean isLinkMode = true;
    public static void setLinkMode(boolean linkMode) {
        isLinkMode = linkMode;
    }

    private static final int CALL_STACK_INDEX = 3;

    public static int v(String tag, String msg) {
        return isDebugMode ? Log.v(tag, mapMsg(msg)) : -1;
    }

    public static int d(String tag, String msg) {
        return isDebugMode ? Log.d(tag, mapMsg(msg)) : -1;
    }

    public static int i(String tag, String msg) {
        return isDebugMode ? Log.i(tag, mapMsg(msg)) : -1;
    }

    public static int w(String tag, String msg) {
        return isDebugMode ? Log.w(tag, mapMsg(msg)) : -1;
    }

    public static int e(String tag, String msg) {
        return isDebugMode ? Log.e(tag, mapMsg(msg)) : -1;
    }

    private static String mapMsg(String msg) {
        return isLinkMode ? ZLogHelper.wrapMessage(CALL_STACK_INDEX, msg) : msg;
    }
}

Believe that with the previous knowledge, the little partner should understand why pass in 3 here.

1 will be positioned

return isLinkMode ? ZLogHelper.wrapMessage(CALL_STACK_INDEX, msg) : msg;

2 (e for example) will be located at

return isDebugMode ? Log.e(tag, mapMsg(msg)) : -1;

3 to locate specific calls outside.

optimization

We know that although ZLog is encapsulated, we have to pass in ZLog every time we type in the log, which is a little troublesome?

Whether a default TAG can be provided to allow external settings.

Yes, we can modify it as follows (e for example):

private static String tag = "ZLOG";
public static void setTag(String tag) {
    if (!TextUtils.isEmpty(tag)) {
        ZLog.tag = tag;
    }
}

public static int e(String tag, String msg) {
    return isDebugMode ? Log.e(mapTag(tag), mapMsg(msg)) : -1;
}

public static int e(String msg) {
    return isDebugMode ? Log.e(tag, mapMsg(msg)) : -1;
}

private static String mapTag(String tag) {
    return TextUtils.isEmpty(tag) ? ZLog.tag : tag;
}

project

Follow these two steps to introduce open source libraries.

Step 1. Add the JitPack repository to your build file
Add it in your root build.gradle at the end of repositories:

allprojects {
  repositories {
    ...
    maven { url 'https://jitpack.io' }
  }
}

Step 2. Add the dependency

dependencies {
  implementation 'com.github.nesger:AndroidWheel:1.0.0'
}

Switch on the switch before use:

ZLog.setDebugMode(true);

Then you can use it directly.

Reminder

Since debug with links has a performance impact, it is recommended that you develop it and turn it off online.

epilogue

This is improving an open source repository AndroidWheel , like the name, avoid repeating wheels.

Log-related tool classes are currently available in version 1.0.0, and anti-dithering EditText is added in version 1.0.1.

The update iteration will continue in the future, and the function will be more complete and comprehensive.

Feel good. Welcome to star ha~

Reference link:
Android Studio Pro Tip: go to source from logcat output

Topics: Android Java Gradle Maven