如何正确擦除调试日志
2018-07-19 小文字
背景
开发期间我们经常会使用android.os.Log进行日志输出,这在调试开发中没有问题,但是如果要在线上包中去除这些日志,就会遇到一些问题。
擦除不生效
我们都知道利用Proguard可以做代码混淆,利用-assumenosideeffects可以配置需要移除的日志;
public class CustomApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Logger.info("TestLog", "AAA");
        Logger.info("BBB","CCC", "DDD");
    }
}
public class Logger {
    private final static String TAG = "XXLog";
    public static void info(String msg, Object... args) {
        if (BuildConfig.DEBUG) {
            Log.i(TAG, String.format(msg, args));
        }
    }
}
现在我们通过配置来擦除这些日志:
# Disable Android logging
-assumenosideeffects class android.util.Log {
  public static boolean isLoggable(java.lang.String, int);
  public static *** v(...);
  public static *** d(...);
  public static *** i(...);
  public static *** w(...);
  public static *** e(...);
}
# custom logger
-assumenosideeffects class cn.hacktons.xx.Logger {
    *;
}
但是这样配置确实能生效么?我们可以反编译一下代码:
public class CustomApplication extends Application {
    public void onCreate() {
        super.onCreate();
        C0979c.m3936a("TestLog", (Object) "AAA");
        C0979c.m3937a("BBB", "CCC", "DDD");
    }
}
public class C0979c {
    /* renamed from: a */
    public static void m3938a(String str, Object... objArr) {
    }
}
可以发现Logger内部逻辑已经被优化了一些,但是日志调用仍然存在,这并没有达到我们的预期,
我们希望连整个日志调用的代码行都擦除掉。
出现这个现象是由于proguard配置中有其他选项阻止了字节码的优化:-dontoptimize
确保你的配置中没有这个选项。
擦除不彻底
现在我们再次打包并反编译查看下日志情况:
public class CustomApplication extends Application {
    public void onCreate() {
        super.onCreate();
        new Object[1][0] = "AAA";
        Object[] objArr = new Object[]{"CCC", "DDD"};
    }
}
这次,调用函数没有了,但是残留了两行代码:
new Object[1][0] = "AAA";
Object[] objArr = new Object[]{"CCC", "DDD"};
这两行其实就是函数调用的参数,我们的Logger通过可变参数来接收日志,这两句话实际上就是构造了可变参的数组,并进行初始化赋值。
我们希望这两句话也没有,怎么处理?
彻底擦除
既然方法调用能被擦除,说明配置是没问题的,我只需要不生成这个可变参的构造过程,就可以实现整体擦除。要去除可变参的构造过程是不太可能的; 我们可以通过提供多个同名,但是参数个数不同的方法,来满足常见的可变参调用场景,比如我们提供1-3个参数方法:
public class Logger {
    private final static String TAG = "XXLog";
    public static void info(String msg) {
        if (BuildConfig.DEBUG) {
            Log.i(TAG, msg);
        }
    }
    public static void info(String msg, Object arg1) {
        if (BuildConfig.DEBUG) {
            Log.i(TAG, String.format(msg, arg1));
        }
    }
    public static void info(String msg, Object arg1, Object arg2) {
        if (BuildConfig.DEBUG) {
            Log.i(TAG, String.format(msg, arg1, arg2));
        }
    }
    public static void info(String msg, Object... args) {
        if (BuildConfig.DEBUG) {
            Log.i(TAG, String.format(msg, args));
        }
    }
}
再次反编译,可以发现, 整个日志都被擦除了:
public class CustomApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
    }
}
其他方案
除了利用proguard擦除日志,也可以通过源代码删除的形式,比如通过命令/脚本遍历所有源代码,根据需要移除代码的特征,简历正则表达式,同时剔除命中的代码即可。