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 th
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(...);
}
This is how I solve it in my Kotlin Project before going to production:
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(...);
}
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 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!
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);
}
}
}
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(
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.