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

时间:2010-03-15 10:00:29

标签: android logging proguard android-log

根据Google的说法,在发布我的Android应用之前,我必须“停用对源代码中的Log方法的任何调用”。摘自publication checklist的第5部分:

  

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

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

if(condition)
  Log.d(LOG_TAG, "Something");
data.load();
data.show();

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

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

26 个答案:

答案 0 :(得分:456)

我发现更简单的解决方案是忘记所有if点检查,只需使用ProGuard删除任何Log.d()Log.v()方法调用我们称之为Ant 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(...);
}

所以你要将它保存到一个文件中,然后从Ant调用ProGuard,传入刚编译好的JAR和你正在使用的Android平台JAR。

另请参阅ProGuard手册中的the examples


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

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

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

Timber已在我的Application onCreate()方法中设置:

if (BuildConfig.DEBUG) {
  Timber.plant(new Timber.DebugTree());
}

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

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

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

答案 1 :(得分:110)

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

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

public class Log {
    static final boolean LOG = false;

    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);
    }
}

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

答案 2 :(得分:59)

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

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;

使用相应的代码:

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

答案 3 :(得分:30)

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

评论日志:

find . -name "*\.java" | xargs grep -l 'Log\.' | xargs sed -i 's/Log\./;\/\/ Log\./g'

取消注释日志:

find . -name "*\.java" | xargs grep -l 'Log\.' | xargs sed -i 's/;\/\/ Log\./Log\./g'

约束是您的记录指令不得跨越多行。

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

答案 4 :(得分:8)

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

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");
  }
}

答案 5 :(得分:8)

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

https://github.com/JakeWharton/timber

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

只是

public class MyApp extends Application {

  public void onCreate() {
    super.onCreate();
    //Timber
    if (BuildConfig.DEBUG) {
      Timber.plant(new DebugTree());
    }
    ...

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

Timber.d("lol");

Timber.i("lol says %s","lol");

打印

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

答案 6 :(得分:7)

我会考虑使用roboguice的logging facility而不是内置的android.util.Log

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

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

答案 7 :(得分:6)

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

public static native boolean isLoggable(String tag, int level);

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

adb shell setprop log.tag.MyAppTag DEBUG

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

  

检查指定标记的日志是否可以在指定级别进行记录。设置任何标记的默认级别   信息。这意味着任何级别都包括INFO   登录。在对日志记录方法进行任何调用之前,您应该检查   看看你的标签是否应该被记录下来。您可以更改默认级别   通过设置系统属性:'setprop log.tag。 “   级别是VERBOSE,DEBUG,INFO,WARN,ERROR,ASSERT或   压制。 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);
    }
}

答案 8 :(得分:5)

如果您可以运行全局替换(一次),之后保留一些编码约定,则可以遵循Android framework中常用的模式。

而不是写

Log.d(TAG, string1 + string2 + arg3.toString());

将其作为

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

现在,proguard可以从优化的版本DEX中删除StringBuilder以及它在途中使用的所有字符串和方法。使用proguard-android-optimize.txt,您不必担心proguard-rules.pro中的 android.util.Log

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

使用Android Studio gradle插件,BuildConfig.DEBUG非常可靠,因此您不需要额外的常量来控制剥离。

答案 9 :(得分:5)

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

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

compile 'com.jakewharton.timber:timber:4.1.1'

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

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

enter image description here

单击“查找”,然后单击全部替换。

Android Studios现在将遍历项目中的所有文件,并将所有日志替换为Timbers。

我使用这种方法的唯一问题是gradle确实出现了数百万条错误消息,因为它无法找到" Timber"在每个java文件的导入中。只需点击错误,Android工作室就会自动导入" Timber"进入你的java。完成所有错误文件后,gradle将再次编译。

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

    if (BuildConfig.DEBUG) {
        Timber.plant(new Timber.DebugTree());
    }

只有当您处于开发模式而不是生产模式时,才会导致应用程序记录。您还可以BuildConfig.RELEASE登录发布模式。

答案 10 :(得分:3)

enter image description here

这就是我以前对我的android项目所做的事情..

在Android Studio中,我们可以通过Ctrl + Shift + F进行类似的操作,从整个项目中查找(MacO中的Command + Shift + F)和Ctrl + Shift + R替换((MacO中的Command + Shift + R) )

答案 11 :(得分:3)

将以下内容添加到 proguard-rules.txt 文件

-assumenosideeffects class android.util.Log {
  public static *** d(...);
  public static *** w(...);
  public static *** v(...);
  public static *** i(...);
}

答案 12 :(得分:2)

我有一个非常简单的解决方案。我使用IntelliJ进行开发,因此细节会有所不同,但这个想法应该适用于所有IDE。

我选择了源树的根,右键单击并选择执行"替换"。然后我选择更换所有" Log。"用" //记录。"。这将删除所有日志语句。为了让它们稍后再回来,我重复相同的替换,但这次更换所有" // Log。"用" Log。"。

对我来说非常棒。只需记住将替换设置为区分大小写,以避免发生诸如对话等事故。"。为了更好的保证,您也可以使用"登录&#34。作为要搜索的字符串。

辉煌。

答案 13 :(得分:2)

zserge's comment所示,

  

Timber非常好,但如果你已经有一个现有的项目 - 你可以试试github.com/zserge/log。它是android.util.Log的直接替代品,具有Timber的大部分功能,甚至更多。

his log library提供简单的启用/禁用日志打印开关,如下所示。

此外,需要更改import行, 需要更改Log.d(...);语句。

if (!BuildConfig.DEBUG)
    Log.usePrinter(Log.ANDROID, false); // from now on Log.d etc do nothing and is likely to be optimized with JIT

答案 14 :(得分:1)

ProGuard将在您的发布版本上为您完成,现在是来自android.com的好消息:

http://developer.android.com/tools/help/proguard.html

ProGuard工具通过删除未使用的代码并使用语义模糊的名称重命名类,字段和方法来缩小,优化和混淆代码。结果是较小的.apk文件,更难以进行逆向工程。由于ProGuard使您的应用程序更难以进行逆向工程,因此当您的应用程序使用对安全性敏感的功能时(例如,在许可您的应用程序时),使用它非常重要。

ProGuard已集成到Android构建系统中,因此您无需手动调用它。只有在发布模式下构建应用程序时,ProGuard才会运行,因此在调试模式下构建应用程序时,不必处理混淆代码。运行ProGuard是完全可选的,但强烈建议。

本文档介绍了如何启用和配置ProGuard以及如何使用回扫工具解码混淆的堆栈跟踪

答案 15 :(得分:1)

我通过提供对不同日志级别的支持以及根据代码是在实时设备上运行还是在模拟器上自动更改日志级别来改进上述解决方案。

public class Log {

final static int WARN = 1;
final static int INFO = 2;
final static int DEBUG = 3;
final static int VERB = 4;

static int LOG_LEVEL;

static
{
    if ("google_sdk".equals(Build.PRODUCT) || "sdk".equals(Build.PRODUCT)) {
        LOG_LEVEL = VERB;
    } else {
        LOG_LEVEL = INFO;
    }

}


/**
 *Error
 */
public static void e(String tag, String string)
{
        android.util.Log.e(tag, string);
}

/**
 * Warn
 */
public static void w(String tag, String string)
{
        android.util.Log.w(tag, string);
}

/**
 * Info
 */
public static void i(String tag, String string)
{
    if(LOG_LEVEL >= INFO)
    {
        android.util.Log.i(tag, string);
    }
}

/**
 * Debug
 */
public static void d(String tag, String string)
{
    if(LOG_LEVEL >= DEBUG)
    {
        android.util.Log.d(tag, string);
    }
}

/**
 * Verbose
 */
public static void v(String tag, String string)
{
    if(LOG_LEVEL >= VERB)
    {
        android.util.Log.v(tag, string);
    }
}


}

答案 16 :(得分:1)

这是我在投产前在Kotlin项目中解决问题的方式:

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

-assumenosideeffects class android.util.Log {
    public static boolean isLoggable(java.lang.String, int);
    public static int d(...);
    public static int w(...);
    public static int v(...);
    public static int i(...);
    public static int e(...);
}

答案 17 :(得分:0)

我喜欢使用Log.d(TAG,一些字符串,通常是String.format())。

TAG始终是班级名称

转换Log.d(TAG, - > Logd(在班级文本中

private void Logd(String str){
    if (MainClass.debug) Log.d(className, str);
}

以这种方式准备好发布版本时,将MainClass.debug设置为false!

答案 18 :(得分:0)

可以使用linux中的bash和sed:

删除日志
find . -name "*\.java" | xargs sed -ri ':a; s%Log\.[ivdwe].*\);%;%; ta; /Log\.[ivdwe]/ !b; N; ba'

适用于多行日志。在此解决方案中,您可以确定生产代码中不存在日志。

答案 19 :(得分:0)

我知道这是一个老问题,但为什么你没有用类似的东西替换你所有的日志调用 Boolean logCallWasHere = true; // ---你的日志其余部分

这就是为什么你会知道什么时候想要把它们放回去,并且它们不会影响你的if语句调用:)

答案 20 :(得分:0)

为什么不做

if(BuildConfig.DEBUG)
  Log.d("tag","msg");

?不需要额外的库,没有倾向于使项目搞砸的proguard规则,并且在您进行发行版本构建时,java编译器只会为此调用忽略字节码。

答案 21 :(得分:0)

如果您不想弄乱其他库或手动编辑代码,这是我的解决方案。我创建了this Jupyter notebook来遍历所有Java文件并注释掉所有Log消息。虽然不完美,但为我完成了工作。

答案 22 :(得分:0)

我的方式:

1)启用列选择模式(alt + shift + insert)

2)在一个Log.d(TAG,“ text”)上选择; “日志”部分。

3)然后执行shift + ctrl + alt + j

4)单击向左箭头

5)Shift + End

6)点击删除。

这会一次删除Java文件中的所有LOG调用。

答案 23 :(得分:0)

您可以尝试使用这种简单的常规方法:

Ctrl + Shift + R

替换

Log.e(

使用

// Log.e(

答案 24 :(得分:0)

如果要使用编程方法而不是ProGuard,则通过创建带有两个实例(一个用于调试,一个用于发布)的类来创建自己的类,可以选择在两种情况下登录的方式。

因此,如果您不想在发布时记录任何内容,只需实现一个不执行任何操作的Logger,如下例所示:

import android.util.Log

sealed class Logger(defaultTag: String? = null) {
    protected val defaultTag: String = defaultTag ?: "[APP-DEBUG]"

    abstract fun log(string: String, tag: String = defaultTag)

    object LoggerDebug : Logger() {
        override fun log(string: String, tag: String) {
            Log.d(tag, string)
        }
    }

    object LoggerRelease : Logger() {
        override fun log(string: String, tag: String) {}
    }

    companion object {
        private val isDebugConfig = BuildConfig.DEBUG

        val instance: Logger by lazy {
            if(isDebugConfig)
            LoggerDebug
            else
                LoggerRelease
        }

    }
}

然后使用记录器类:

class MainActivity : AppCompatActivity() {

private val logger = Logger.instance

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    logger.log("Activity launched...")
    ...
    myView.setOnClickListener {
        ...

        logger.log("My View clicked!", "View-click")
    }
}

答案 25 :(得分:-1)

最简单的方法;

使用DebugLog

应用程序发布时,DebugLog会禁用所有日志。

https://github.com/MustafaFerhan/DebugLog