Question

According to Google, I must "deactivate any calls to Log methods in the source code" before publishing my Android app to Google Play. Extract from section 3 of the publication checklist:

Make sure you deactivate logging and disable the debugging option before you build your application for release. You can deactivate logging by removing calls to Log methods in your source files.

My open-source project is large and it is a pain to do it manually every time I release. Additionally, removing a Log line is potentially tricky, for instance:

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

If I comment the Log line, then the condition applies to the next line, and chances are load() is not called. Are such situations rare enough that I can decide it should not exist?

So, is there a better source code-level way to do that? Or maybe some clever ProGuard syntax to efficiently but safely remove all Log lines?

Was it helpful?

Solution

I find a far easier solution is to forget all the if checks all over the place and just use ProGuard to strip out any Log.d() or Log.v() method calls when we call our Ant release target.

That way, we always have the debug info being output for regular builds and don't have to make any code changes for release builds. ProGuard can also do multiple passes over the bytecode to remove other undesired statements, empty blocks and can automatically inline short methods where appropriate.

For example, here's a very basic ProGuard config for Android:

-dontskipnonpubliclibraryclasses
-dontobfuscate
-forceprocessing
-optimizationpasses 5

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

So you would save that to a file, then call ProGuard from Ant, passing in your just-compiled JAR and the Android platform JAR you're using.

See also the examples in the ProGuard manual.


Update (4.5 years later): Nowadays I used Timber for Android logging.

Not only is it a bit nicer than the default Log implementation — the log tag is set automatically, and it's easy to log formatted strings and exceptions — but you can also specify different logging behaviours at runtime.

In this example, logging statements will only be written to logcat in debug builds of my app:

Timber is set up in my Application onCreate() method:

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

Then anywhere else in my code I can log easily:

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

See the Timber sample app for a more advanced example, where all log statements are sent to logcat during development and, in production, no debug statements are logged, but errors are silently reported to Crashlytics.

OTHER TIPS

All good answers, but when I was finished with my development I didn´t want to either use if statements around all the Log calls, nor did I want to use external tools.

So the solution I`m using is to replace the android.util.Log class with my own Log class:

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

The only thing I had to do in all the source files was to replace the import of android.util.Log with my own class.

I suggest having a static boolean somewhere indicating whether or not to log:

class MyDebug {
  static final boolean LOG = true;
}

Then wherever you want to log in your code, just do this:

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

Now when you set MyDebug.LOG to false, the compiler will strip out all code inside such checks (since it is a static final, it knows at compile time that code is not used.)

For larger projects, you may want to start having booleans in individual files to be able to easily enable or disable logging there as needed. For example, these are the various logging constants we have in the window manager:

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;

With corresponding code like:

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

Christopher's Proguard solution is the best, but if for any reason you don't like Proguard, here is a very low-tech solution:

Comment logs:

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

Uncomment logs:

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

A constraint is that your logging instructions must not span over multiple lines.

(Execute these lines in a UNIX shell at the root of your project. If using Windows, get a UNIX layer or use equivalent Windows commands)

I would like to add some precisions about using Proguard with Android Studio and gradle, since I had lots of problems to remove log lines from the final binary.

In order to make assumenosideeffects in Proguard works, there is a prerequisite.

In your gradle file, you have to specify the usage of the proguard-android-optimize.txt as default file.

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'
    }
}

Actually, in the default proguard-android.txt file, optimization is disabled with the two flags:

-dontoptimize
-dontpreverify

The proguard-android-optimize.txt file does not add those lines, so now assumenosideeffects can work.

Then, personnally, I use SLF4J, all the more when I develop some libraries that are distributed to others. The advantage is that by default there is no output. And if the integrator wants some log outputs, he can uses Logback for Android and activate the logs, so logs can be redirected to a file or to LogCat.

If I really need to strip the logs from the final library, I then add to my Proguard file (after having enabled the proguard-android-optimize.txt file of course):

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

I highly suggest using Timber from Jake Wharton

https://github.com/JakeWharton/timber

it solves your issue with enabling/disabling plus adds tag class automagically

just

public class MyApp extends Application {

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

logs will only be used in your debug ver, and then use

Timber.d("lol");

or

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

to print

"Your class / msg" without specyfing the tag

I have used a LogUtils class like in the Google IO example application. I modified this to use an application specific DEBUG constant instead of BuildConfig.DEBUG because BuildConfig.DEBUG is unreliable. Then in my Classes I have the following.

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

I would consider using roboguice's logging facility instead of the built-in android.util.Log

Their facility automatically disables debug and verbose logs for release builds. Plus, you get some nifty features for free (e.g. customizable logging behavior, additional data for every log and more)

Using proguard could be quite a hassle and I wouldn't go through the trouble of configuring and making it work with your application unless you have a good reason for that (disabling logs isn't a good one)

I'm posting this solution which applies specifically for Android Studio users. I also recently discovered Timber and have imported it successfully into my app by doing the following:

Put the latest version of the library into your build.gradle:

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

Then in Android Studios, go to Edit -> Find -> Replace in Path...

Type in Log.e(TAG, or however you have defined your Log messages into the "Text to find" textbox. Then you just replace it with Timber.e(

enter image description here

Click Find and then replace all.

Android Studios will now go through all your files in your project and replace all the Logs with Timbers.

The only problem I had with this method is that gradle does come up witha million error messages afterwards because it cannot find "Timber" in the imports for each of your java files. Just click on the errors and Android Studios will automatically import "Timber" into your java. Once you have done it for all your errors files, gradle will compile again.

You also need to put this piece of code in your onCreate method of your Application class:

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

This will result in the app logging only when you are in development mode not in production. You can also have BuildConfig.RELEASE for logging in release mode.

Per android.util.Log provides a way to enable/disable log:

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

Default the method isLoggable(...) returns false, only after you setprop in device likes this:

adb shell setprop log.tag.MyAppTag DEBUG

It means any log above DEBUG level can be printed out. Reference android doc:

Checks to see whether or not a log for the specified tag is loggable at the specified level. The default level of any tag is set to INFO. This means that any level above and including INFO will be logged. Before you make any calls to a logging method you should check to see if your tag should be logged. You can change the default level by setting a system property: 'setprop log.tag. ' Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will turn off all logging for your tag. You can also create a local.prop file that with the following in it: 'log.tag.=' and place that in /data/local.prop.

So we could use custom log util:

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

If you can run a global replace (once), and after that preserve some coding convention, you can follow the pattern often used in Android framework.

Instead of writing

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

have it as

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

Now proguard can remove the StringBuilder and all strings and methods it uses on the way, from optimized release DEX. Use proguard-android-optimize.txt and you don't need to worry about android.util.Log in your proguard-rules.pro:

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

With Android Studio gradle plugin, BuildConfig.DEBUG is quite reliable, so you don't need extra constants to control the stripping.

I have a very simple solution. I use IntelliJ for development, so the details vary but the idea should apply across all IDE's.

I pick to root of my source tree, right-click and select to do "replace". I then choose to replace all "Log." with "//Log.". This removes all log statements. To put them back later I repeat the same replace but this time as replace all "//Log." with "Log.".

Works just great for me. Just remember to set the replace as case sensitive to avoid accidents such as "Dialog.". For added assurance you can also do the first step with " Log." as the string to search.

Brilliant.

As zserge's comment suggested,

Timber is very nice, but if you already have an existing project - you may try github.com/zserge/log . It's a drop-in replacement for android.util.Log and has most of the the features that Timber has and even more.

his log library provides simple enable/disable log printing switch as below.

In addition, it only requires to change import lines, and nothing needs to change for Log.d(...); statement.

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

Add following to your proguard-rules.txt file

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

enter image description here

This is what i used to do on my android projects..

In Android Studio we can do similar operation by, Ctrl+Shift+F to find from whole project (Command+Shift+F in MacOs) and Ctrl+Shift+R to Replace ((Command+Shift+R in MacOs))

I have improved on the solution above by providing support for different log levels and by changing the log levels automatically depending on if the code is being run on a live device or on the emulator.

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


}

ProGuard will do it for you on your release build and now the good news from android.com:

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

The ProGuard tool shrinks, optimizes, and obfuscates your code by removing unused code and renaming classes, fields, and methods with semantically obscure names. The result is a smaller sized .apk file that is more difficult to reverse engineer. Because ProGuard makes your application harder to reverse engineer, it is important that you use it when your application utilizes features that are sensitive to security like when you are Licensing Your Applications.

ProGuard is integrated into the Android build system, so you do not have to invoke it manually. ProGuard runs only when you build your application in release mode, so you do not have to deal with obfuscated code when you build your application in debug mode. Having ProGuard run is completely optional, but highly recommended.

This document describes how to enable and configure ProGuard as well as use the retrace tool to decode obfuscated stack traces

I like to use Log.d(TAG, some string, often a String.format ()).

TAG is always the class name

Transform Log.d(TAG, --> Logd( in the text of your class

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

In this way when you are ready to make a release version, set MainClass.debug to false!

Logs can be removed using bash in linux and sed:

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

Works for multiline logs. In this solution you can be sure, that logs are not present in production code.

I know this is an old question, but why didn't you replace all your log calls with something like Boolean logCallWasHere=true; //---rest of your log here

This why you will know when you want to put them back, and they won't affect your if statement call :)

Why not just do

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

? No additional libraries needed, no proguard rules which tend to screw up the project and java compiler will just leave out bytecode for for this call when you make release build.

the simplest way;

use DebugLog

All logs are disabled by DebugLog when the app is released.

https://github.com/MustafaFerhan/DebugLog

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top