如何在将应用程序发布到Google Play之前删除所有调试日志记录调用?

Nic*_*oul 381 logging android proguard android-log

据谷歌称,在发布我的Android应用程序之前,我必须" 停用源代码中对Log方法的任何调用 ".摘录自出版物清单的第5部分:

在构建应用程序以进行发布之前,请确保停用日志记录并禁用调试选项.您可以通过删除源文件中对Log方法的调用来停用日志记录.

我的开源项目很大,每次发布时手动执行都很痛苦.此外,删除日志行可能很棘手,例如:

if(condition)
  Log.d(LOG_TAG, "Something");
data.load();
data.show();
Run Code Online (Sandbox Code Playgroud)

如果我对日志行进行注释,则条件适用于下一行,并且可能不会调用load().这种情况是否足够罕见,我可以决定它不应该存在?

这是在官方清单上,所以我想很多人会定期这样做.
那么,如何有效但安全地删除所有日志行?

Chr*_*Orr 479

我发现一个更容易的解决方案是忘记所有if地方的所有检查,并在我们调用Ant 目标时使用ProGuard去除任何Log.d()Log.v()方法调用release.

这样,我们总是为常规构建输出调试信息,而不必为发布版本进行任何代码更改.ProGuard还可以对字节码执行多次传递,以删除其他不需要的语句,空块,并可以在适当的情况下自动内联短方法.

例如,这是一个非常基本的Android ProGuard配置:

-dontskipnonpubliclibraryclasses
-dontobfuscate
-forceprocessing
-optimizationpasses 5

-keep class * extends android.app.Activity
-assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** v(...);
}
Run Code Online (Sandbox Code Playgroud)

因此,您可以将其保存到文件中,然后从Ant调用ProGuard,传入刚刚编译的JAR和您正在使用的Android平台JAR.

另请参阅ProGuard手册中的示例.


更新(4.5年后):现在我使用Timber进行Android日志记录.

它不仅比默认Log实现更好- 日志标记是自动设置的,并且很容易记录格式化的字符串和异常 - 但您也可以在运行时指定不同的日志记录行为.

在此示例中,日志记录语句将仅在我的应用程序的调试版本中写入logcat:

木材在我的Application onCreate()方法中设置:

if (BuildConfig.DEBUG) {
  Timber.plant(new Timber.DebugTree());
}
Run Code Online (Sandbox Code Playgroud)

然后我的代码中的任何其他地方都可以轻松记录:

Timber.d("Downloading URL: %s", url);
try {
  // ...
} catch (IOException ioe) {
  Timber.e(ioe, "Bad things happened!");
}
Run Code Online (Sandbox Code Playgroud)

有关更高级的示例,请参阅Timber示例应用程序,其中所有日志语句在开发期间发送到logcat,在生产中,不会记录任何调试语句,但会向Crashlytics静默报告错误.

  • 为什么不在默认的proguard文件中呢? (54认同)
  • 4年后更新答案的Upvote :) (28认同)
  • + rds因为它会使生产堆栈跟踪的行号与代码中的行号不同,因为删除了行. (10认同)
  • 我可以确认剥离Log调用会在stacktraces中移动行号.它并不总是不同步(我做了几次快速测试,但不能确切地指出原因是什么,可能如果你在Log调用中连接一个字符串),但有时会有几行关闭.值得IMO轻松删除日志调用的能力. (5认同)
  • @Fraggle来自ADT工具中的proguard-android.txt:"请注意,如果你想启用优化,你不能只在你自己的项目配置文件中包含优化标志;相反,你需要指向"proguard-android-optimize. txt"文件而不是你的"#project.properties文件中的这个文件. (5认同)
  • 正如espinchi在下面的回答中所说."这种方法的唯一问题是,如果你做Log.d("tag","Processed:"+ new ItemCounter(blabla)+"items"),即使你的发布版本中没有出现此日志消息,一个StringBuilder用于创建消息,创建起来可能很昂贵."这在Timber案例​​中也是如此吗? (3认同)
  • @androniennn是的.仅当您执行应用程序的发布版本时才会运行ProGuard. (2认同)
  • @kaps 这与这个问题无关,但 Java 允许您注册一个处理程序来捕获其他未处理的异常。 (2认同)

小智 114

所有好的答案,但是当我完成了我的开发时,我不想在所有Log调用中使用if语句,也不想使用外部工具.

所以我使用的解决方案是用我自己的Log类替换android.util.Log类:

public class Log {
    static final boolean LOG = BuildConfig.DEBUG;

    public static void i(String tag, String string) {
        if (LOG) android.util.Log.i(tag, string);
    }
    public static void e(String tag, String string) {
        if (LOG) android.util.Log.e(tag, string);
    }
    public static void d(String tag, String string) {
        if (LOG) android.util.Log.d(tag, string);
    }
    public static void v(String tag, String string) {
        if (LOG) android.util.Log.v(tag, string);
    }
    public static void w(String tag, String string) {
        if (LOG) android.util.Log.w(tag, string);
    }
}
Run Code Online (Sandbox Code Playgroud)

我在所有源文件中唯一要做的就是用我自己的类替换android.util.Log的导入.

  • 此方法的唯一问题是,如果您执行Log.d("tag","Processed:"+ new ItemCounter(blabla)+"items"),即使此日志消息未出现在您发布的版本中, StringBuilder用于创建消息,创建起来可能很昂贵. (142认同)
  • 只是一个FYI,如果你使用Android Studio和gradle构建系统,你可以使用`static final boolean LOG = BuildConfig.DEBUG`而不必修改这个文件. (21认同)
  • 这个解决方案有一个大问题.埃斯皮奇提到了冰山一角.问题是当你调用`Log.d("tag",someValue.toString());`时很容易忘记检查someValue是否为null,这意味着它可能会在生产中抛出`NullPointerException`.它建议一个安全的解决方案,但它会欺骗你.我们使用`private static boolean DEBUG`然后`if(DEBUG)Log.d(TAG,msg);` (9认同)
  • @espinchi您的关注似乎适用于所有日志库,如本答案http://stackoverflow.com/a/15452492/433718(Slf4j,backlog,...)中所述.是不是建议使用它们? (2认同)

hac*_*bod 60

我建议在某处使用静态布尔值来指示是否记录:

class MyDebug {
  static final boolean LOG = true;
}

然后,无论您想要在哪里登录代码,只需执行以下操作:

if (MyDebug.LOG) {
  if (condition) Log.i(...);
}

现在,当您将MyDebug.LOG设置为false时,编译器将删除此类检查中的所有代码(因为它是静态final,它在编译时知道代码未被使用.)

对于较大的项目,您可能希望在单个文件中开始使用布尔值,以便能够根据需要轻松启用或禁用其中的日志记录.例如,这些是我们在窗口管理器中具有的各种日志记录常量:

static final String TAG = "WindowManager";
static final boolean DEBUG = false;
static final boolean DEBUG_FOCUS = false;
static final boolean DEBUG_ANIM = false;
static final boolean DEBUG_LAYOUT = false;
static final boolean DEBUG_RESIZE = false;
static final boolean DEBUG_LAYERS = false;
static final boolean DEBUG_INPUT = false;
static final boolean DEBUG_INPUT_METHOD = false;
static final boolean DEBUG_VISIBILITY = false;
static final boolean DEBUG_WINDOW_MOVEMENT = false;
static final boolean DEBUG_ORIENTATION = false;
static final boolean DEBUG_APP_TRANSITIONS = false;
static final boolean DEBUG_STARTING_WINDOW = false;
static final boolean DEBUG_REORDER = false;
static final boolean DEBUG_WALLPAPER = false;
static final boolean SHOW_TRANSACTIONS = false;
static final boolean HIDE_STACK_CRAWLS = true;
static final boolean MEASURE_LATENCY = false;
Run Code Online (Sandbox Code Playgroud)

使用相应的代码:

    if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Log.v(
        TAG, "Adding window " + window + " at "
        + (i+1) + " of " + mWindows.size() + " (after " + pos + ")");
Run Code Online (Sandbox Code Playgroud)

  • 作为第一个参数传递条件不是不那么冗长吗? (4认同)
  • @Snicolas如何在不实现包装器的情况下将条件作为第一个参数传递?此外,如果将其添加为参数,则在进入方法之前,需要评估所有参数,即消息字符串.在构建参数之前需要测试条件.在没有外部工具的情况下,提出的解决方案可能是最好的. (2认同)
  • 明智的二进制代码,这是最好的。但是,对于简单的调试日志输出,像这样的编码就花了很多精力。代码的可读性大大下降。我猜赢了一些输了一些... (2认同)

Nic*_*oul 30

Christopher的Proguard解决方案是最好的,但如果出于任何原因你不喜欢Proguard,这是一个非常低技术的解决方案:

评论日志:

find . -name "*\.java" | xargs grep -l 'Log\.' | xargs sed -i 's/Log\./;\/\/ Log\./g'
Run Code Online (Sandbox Code Playgroud)

取消注释日志:

find . -name "*\.java" | xargs grep -l 'Log\.' | xargs sed -i 's/;\/\/ Log\./Log\./g'
Run Code Online (Sandbox Code Playgroud)

约束是您的日志记录指令不得跨越多行.

(在项目根目录的UNIX shell中执行这些行.如果使用Windows,获取UNIX层或使用等效的Windows命令)

  • @NicolasRaoul半结肠修复了这个问题(`//`vs.`; //`) (2认同)

Vin*_*ren 16

我想添加一些关于在Android Studio和gradle中使用Proguard的精度,因为我从最终的二进制文件中删除了日志行有很多问题.

为了使assumenosideeffectsProguard作品,有一个先决条件.

在gradle文件中,您必须指定proguard-android-optimize.txt默认文件的用法.

buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'

        // With the file below, it does not work!
        //proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}
Run Code Online (Sandbox Code Playgroud)

实际上,在默认proguard-android.txt文件中,使用两个标志禁用优化:

-dontoptimize
-dontpreverify
Run Code Online (Sandbox Code Playgroud)

proguard-android-optimize.txt文件不会添加这些行,所以现在assumenosideeffects可以工作.

然后,个人,我使用SLF4J,当我开发一些分发给其他人的库时,更是如此.优点是默认情况下没有输出.如果集成商想要一些日志输出,他可以使用Logback for Android并激活日志,因此可以将日志重定向到文件或LogCat.

如果我真的需要从最终库中删除日志,那么我会添加到我的Proguard文件中(proguard-android-optimize.txt当然启用了文件后):

-assumenosideeffects class * implements org.slf4j.Logger {
    public *** trace(...);
    public *** debug(...);
    public *** info(...);
    public *** warn(...);
    public *** error(...);
}
Run Code Online (Sandbox Code Playgroud)


And*_*cko 9

我强烈建议使用杰克沃顿的木材

https://github.com/JakeWharton/timber

它解决了启用/禁用问题以及自动添加标记类的问题

只是

public class MyApp extends Application {

  public void onCreate() {
    super.onCreate();
    //Timber
    if (BuildConfig.DEBUG) {
      Timber.plant(new DebugTree());
    }
    ...
Run Code Online (Sandbox Code Playgroud)

日志只会在您的调试版中使用,然后使用

Timber.d("lol");
Run Code Online (Sandbox Code Playgroud)

要么

Timber.i("lol says %s","lol");
Run Code Online (Sandbox Code Playgroud)

打印

"你的课程/ msg"没有指定标签

  • Timber非常好,但是如果您已经有一个现有项目 - 您可以尝试https://github.com/zserge/log.它是android.util.Log的直接替代品,具有Timber的大部分功能,甚至更多. (2认同)

Jos*_*phL 8

我使用了像Google IO示例应用程序中的LogUtils类.我将其修改为使用特定于应用程序的DEBUG常量而不是BuildConfig.DEBUG,因为BuildConfig.DEBUG不可靠.然后在我的课程中我有以下内容.

import static my.app.util.LogUtils.makeLogTag;
import static my.app.util.LogUtils.LOGV;

public class MyActivity extends FragmentActivity {
  private static final String TAG = makeLogTag(MyActivity.class);

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    LOGV(TAG, "my message");
  }
}
Run Code Online (Sandbox Code Playgroud)


小智 7

我会考虑使用roboguice的日志记录工具而不是内置的android.util.Log

他们的工具会自动禁用发布版本的调试和详细日志.此外,您还可以免费获得一些漂亮的功能(例如,可自定义的日志记录行为,每个日志的附加数据等)

使用proguard可能会非常麻烦,我不会遇到配置并使其与您的应用程序一起工作的麻烦,除非您有充分的理由(禁用日志不是很好)


Sim*_*mon 7

我发布的此解决方案专门适用于Android Studio用户.我最近还发现了Timber并通过以下方式将其成功导入我的应用程序:

将最新版本的库放入build.gradle:

compile 'com.jakewharton.timber:timber:4.1.1'
Run Code Online (Sandbox Code Playgroud)

然后在Android工作室中,转到编辑 - >查找 - >替换路径...

输入Log.e(TAG,或者您已将日志消息定义到"Text to find"文本框中.然后你只需用它替换它Timber.e(

在此输入图像描述

单击查找,然后单击全部替换.

Android工作室现在将浏览项目中的所有文件,并将所有日志替换为Timbers.

我使用这种方法的唯一问题是gradle后来出现了大量的错误消息,因为它无法在每个java文件的导入中找到"Timber".只需点击错误,Android Studios就会自动将"Timber"导入您的java.为所有错误文件完成后,gradle将再次编译.

您还需要将这段代码放在您onCreateApplication类的方法中:

    if (BuildConfig.DEBUG) {
        Timber.plant(new Timber.DebugTree());
    }
Run Code Online (Sandbox Code Playgroud)

这将导致应用程序仅在您处于开发模式而非生产模式时进行日志记录.您还可以BuildConfig.RELEASE登录发布模式.

  • 尝试为你的导入做同样的事情,并确保选中正则表达式框文本找到:```import android\.util\.Log \;```替换为:```import android\.util\.Log \; \nimport timber\.log\.Timber \;``` (3认同)

Ric*_*ard 6

每个android.util.Log提供了一种启用/禁用日志的方法:

public static native boolean isLoggable(String tag, int level);
Run Code Online (Sandbox Code Playgroud)

默认方法isLoggable(...)返回false,只有在设备中的setprop之后才会这样:

adb shell setprop log.tag.MyAppTag DEBUG
Run Code Online (Sandbox Code Playgroud)

这意味着可以打印任何DEBUG级别以上的日志.参考android doc:

检查指定标记的日志是否可以在指定级别进行记录.任何标记的默认级别都设置为INFO.这意味着将记录任何级别以上且包括INFO.在对日志记录方法进行任何调用之前,应检查是否应记录您的标记.您可以通过设置系统属性来更改默认级别:'setprop log.tag.'级别是VERBOSE,DEBUG,INFO,WARN,ERROR,ASSERT或SUPPRESS.SUPPRESS将关闭标签的所有日志记录.您还可以创建一个local.prop文件,其中包含以下内容:'log.tag.='并将其放在/data/local.prop中.

所以我们可以使用自定义日志工具:

public final class Dlog 
{
    public static void v(String tag, String msg)
    {
        if (Log.isLoggable(tag, Log.VERBOSE))
            Log.v(tag, msg);
    }

    public static void d(String tag, String msg)
    {
        if (Log.isLoggable(tag, Log.DEBUG))
            Log.d(tag, msg);
    }

    public static void i(String tag, String msg)
    {
        if (Log.isLoggable(tag, Log.INFO))
            Log.i(tag, msg);
    }

    public static void w(String tag, String msg)
    {
        if (Log.isLoggable(tag, Log.WARN))
            Log.w(tag, msg);
    }

    public static void e(String tag, String msg)
    {
        if (Log.isLoggable(tag, Log.ERROR))
            Log.e(tag, msg);
    }
}
Run Code Online (Sandbox Code Playgroud)


Man*_*tel 6

  1. Application->app->proguard-rules.pro 在此输入图像描述

  2. 在 proguard-rules.pro 中输入以下代码`

    -assumenosideeffects class android.util.Log {
        public static *** d(...);
        public static *** v(...);
        public static *** w(...);
        public static *** i(...);
        public static *** e(...);
    }
    
    Run Code Online (Sandbox Code Playgroud)

# 如果您希望日志中包含调试类型错误,您可以删除特定的调试类

  1. build.gradle(app) ->android 做这件事

    buildTypes {
            debug{
                debuggable false
                minifyEnabled true
                shrinkResources true
                proguardFiles getDefaultProguardFile('proguard-android- 
                optimize.txt'), 'proguard-rules.pro'
            }
            release {
                debuggable false
                minifyEnabled true
                shrinkResources true
                proguardFiles getDefaultProguardFile('proguard-android- 
                optimize.txt'), 'proguard-rules.pro'
            }
        }
    
     lintOptions {
               checkReleaseBuilds false
               // Or, if you prefer, you can continue to check for errors in release builds,
               // but continue the build even when errors are found:
               abortOnError false
      }
    
    Run Code Online (Sandbox Code Playgroud)


Ale*_*ohn 5

如果您可以运行一次全局替换,然后保留一些编码约定,则可以遵循Android 框架中经常使用的模式。

而不是写作

Log.d(TAG, string1 + string2 + arg3.toString());
Run Code Online (Sandbox Code Playgroud)

有它

if (BuildConfig.DEBUG) Log.d(TAG, string1 + String.format("%.2f", arg2) + arg3.toString());
Run Code Online (Sandbox Code Playgroud)

现在,proguard可以从优化的发行版DEX中删除StringBuilder及其使用的所有字符串和方法。使用proguard-android-optimize.txt,您无需担心android.util.Log登录您的proguard-rules.pro

android {
  …
  buildTypes {
    release {
      minifyEnabled true
      proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

有了Android Studio gradle插件,它是相当可靠的,因此您不需要额外的常量来控制剥离。BuildConfig.DEBUG