How to remove all debug logging calls before building the release version of an Android app?
Asked Answered
S

31

449

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?

Shipowner answered 15/3, 2010 at 10:0 Comment(8)
+1 because I didn't remember this was in the publication checklist.Tarentarentum
To comment out a non-blocked line, I use ";//" instead of "//".Murtha
If you need to be able to undo this, you will probably want to use sed 's_^\(\s*Log\.\)_;//'`date|tr -s \ -`'\1_g' instead.Murtha
Possible duplicate: https://mcmap.net/q/81611/-how-do-i-enable-disable-log-levels-in-android/2291Uncial
The link that Dimitar added does not work any more. I found this instead source.android.com/source/code-style.html#log-sparingly.Lines
this is why it's not recommended to use if statement without {}, especially when you move expression to the next line; use Sonar Luke.Doty
Is there any effects on the performance is the logging is on or this note is on the security side purposes only?Tripos
@mboy: Probably for performance mainly nowadays, but on old Android versions it has security benefits too.Shipowner
T
536

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.

Tc answered 17/3, 2010 at 23:30 Comment(35)
For Android right? Is your project open-source? Could I get your Ant file somewhere? :-) Thanks a lot!Shipowner
I added a basic config file which you can use when calling ProGuard from an Ant target.Tc
And why isn't that in the default proguard file?Tarentarentum
+rds since it will make production stacktraces line numbers different than the ones in your code, as lines are removed.Cecilla
ProGuard acts on the bytecode, not the Java source, so line numbers shouldn't be affected. But if you're optimising, line numbers are removed anyway; you need to keep then with a -keepattributes flag.Tc
I can confirm that stripping out Log calls will shift line numbers in stacktraces. It won't always be out of sync (I did several quick tests but can't exactly pinpoint what the cause is, possibly if you concatenate a string in the Log call), but sometimes it will be a few lines off. Worth the trouble IMO for the ability to easily remove Log calls.Rally
In ADT19 optimization is turned off by default and therefore -assumenosideeffects will be ignored. Guess it's the same for ADT18 and 19.Cuneo
Can't we just edit proguard.cfg in Eclipse and be done with it (no need for Ant for us simple Eclipse build folks right?). Also what up with @tidebeck's comment that assumenosideeffects will be ignored?Liebfraumilch
@Liebfraumilch From proguard-android.txt in ADT tools: "Note that if you want to enable optimization, you cannot just include optimization flags in your own project configuration file; instead you will need to point to the "proguard-android-optimize.txt" file instead of this one from your" # project.properties file.Cabinda
I added that lines in proguard-project.txt and proguard.config=proguard-project.txt to project.properties and with device connected into PC with Eclipse opened i can always retrieve Logs. Is that normal ?Azaleeazan
@Azaleeazan Yes. ProGuard is only run if you do a release build of your app.Tc
Doesn't this keep the logging in the "-keep" classes, such as those that extend android.app.Activity in this example?Gunnar
@Gunnar No, the logging is removed.Tc
Is it safe? It works only if in proguard configuration I turn on optimization, and - as proguard-android-optimize.txt says - some proguard's optimizations don't work with some verions of Dalvik.Carrigan
@Carrigan Yes, though you should test your app thoroughly once you've applied ProGuard optimisations.Tc
@LucasTan I asked this exact question here: https://mcmap.net/q/81612/-removing-unused-strings-during-proguard-optimisation/234938Tc
Any directions on how to set this up in Android Studio?Susannahsusanne
This is not working for me. The logs still show up, probably due to optimization not being enabled. Can someone explain how to enable it? I have this line in my proguard-rules.txt: -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*, is that part of the problem? Also, is Raanan right, that you shouldn't include those in your own custom proguard file?Intratelluric
Ugh. I don't like that I have to call Timber.tag before every log call. Are you doing something to avoid this? Or you just don't use tags?Hyrup
@Hyrup As I mention in the answer, Timber automatically adds tags; I've never used Timber.tag.Tc
In eclipse the proguard config works as expected and none of the logs are displayed.But in Android studio the logs still appears.Is anyone else facing the same issue.?Pullover
its not working... still showing all logs after releasing with proguard. I used default android Log classToothwort
Though its an old post, out of curiosity, was thinking what if there is an uncaught exception in the app? Such exceptions will be logged in console, how to avoid uncaught exception being thrown to the console?Aphaeresis
@kaps That's unrelated to this question, but Java lets you register a handler to catch otherwise unhandled exceptions.Tc
As espinchi said in below answer. "The only problem with this approach is that, if you do Log.d("tag", "Processed: " + new ItemCounter(blabla) + " items "), even if this log message does not appear in your released version, a StringBuilder is used to create the message, which could be expensive to create. " Is this true in Timber case too?Exoenzyme
@Exoenzyme If you use Timber properly (i.e. using format parameters), only one String constant will be created, and no StringBuilders. Any methods called in the parameters wouldn't be executed for builds where Timber is a no-op. If you really want to ensure that the String constants are removed from the APK, I imagine you could use ProGuard with an assumenosideeffects declaration for Timber.x.Tc
@ChristopherOrr With the tree planted in the Application class, is there a need to uproot it? If so, could you guide on where the uproot should be placed?Denys
What if we accidentally left the log statements and publish the apk? does this crash the application?Facient
Quick note that tripped me up-- The Jack compiler does not currently support stripping out the logs. See https://mcmap.net/q/81613/-android-jack-compiler-not-removing-logs-from-proguard-rulesBarham
Use BuildConfig.DEBUG to check for Debug Build is risky, the more reliable way is use this: boolean isDebug = ((getContext().getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0); please check this for more detail.Sammer
@Sammer That's irrelevant to this answer, as BuildConfig.DEBUG is only being called once, during Application creation.Tc
@ChristopherOrr problem with proguard approach is , it leaves the string constants of logs in the code and it make reverse engineering and tampering with your apk easier for those who have such intentions. For me it is also important to remove the strings from release code, and seems it is possible just by if(DEBUG) {Log.d(...)} approachHarpy
Using timber like libraries: when okhttp logs the data; it still is visible in logcat.Kerns
In the timber case, If I'm using a somewhat expensive call to be printed in debug code such as (kotlin) Timber.d("${printDebugOutputIteratingStuff()"), I guess timber wouldn't protect me from that call being executed anyways, isn't it?Emmer
why dontobfuscate? shouldn't this be enabled for release?Chabazite
P
135

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.

Piece answered 4/1, 2011 at 11:12 Comment(11)
The only problem with this approach is that, if you do Log.d("tag", "Processed: " + new ItemCounter(blabla) + " items "), even if this log message does not appear in your released version, a StringBuilder is used to create the message, which could be expensive to create.Triage
This solution has a big problem. espinchi mentioned just the tip of the iceberg. The problem is that when you call Log.d("tag", someValue.toString()); that it's very easy to forget to check someValue for not being null what means that it might throw a NullPointerException in production. It suggest a secure solution but it will trick you. We us a private static boolean DEBUG and then if(DEBUG)Log.d(TAG, msg);Eggcup
Wouldn't ProGuard (with defaut settings) detect these blank Log.d() calls as unused code and remove them?Duenas
Yes Proguard does remove them. Simple check : see how dfferent the size of the APK is with and without because the strings were removed (assuming you have lots fo debugging strings)Dupion
@Triage Your concern seems to apply to all logging libraries like discussed in this answer https://mcmap.net/q/81611/-how-do-i-enable-disable-log-levels-in-android (Slf4j, backlog,...). Is it not suggested to use them?Alumina
To answer my own comment: When using those libraries, if statements should be used also to check the log level before logging something. See also #4959360Alumina
This solution is good if you're not using any libraries. but if you are, you'd have to change the Log class in all of them provided they're open source and not already compiled. Basically if you're using any jar you will still see a log from itCattier
The only way to minimize overheads mentioned in the 1-st comment by @Triage is to change logging methods to accept varargs instead of String. Complete solution is decribed here. This apparently has another drawback: every call should be edited (not only one import line).Short
Just an FYI, if you're using Android Studio and gradle build system, you can use static final boolean LOG = BuildConfig.DEBUG and not have to modify this file ever.Ski
Another advantage I see, you can plan another Tree which log into file or cache in memory, and can share these logs for debugging purpose.Figureground
@ashishduh, better is private static final boolean IS_DEBUGGABLE = com.name.app.BuildConfig.DEBUG.Osprey
S
63

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 + ")");
Shandra answered 15/3, 2010 at 15:35 Comment(7)
I would vote for such approach also. It also used in the official Google's in-app billing sample.Shorttempered
Wouldn't it be less verbose to pass the condition as first parameter ?Educatee
This appears to be the best solution although it requires additional code on each log statement: Line numbers are preserved (weakness of ProGuard approach), No code to create log message is executed (weakness of wrapper class approach and apparently of logging library approach also). The use of this approach in Googles in app billing sample according to @Shorttempered supports my thoughts also.Alumina
@Educatee How can you pass the condition as first parameter without implementing a wrapper? Moreover if you add it as parameter, then before entering the method, all the parameters needs to be evaluated that is, also the message string. The condition needs to be tested before building the parameters. The solution proposed is possibly the best one given no external tool.Squalid
@Squalid you are right. I realize now that it's more optimal. Never the less, I never met a library that would match my needs exactly to log stuff on android.Educatee
Binary code wise, this is best. But coding like this just a lot of effort for a simple debug log output. Code readability drops significantly. Win some, lose some, I guess...Tangle
I know this answer is old, but for the sake of Lint I would replace LOG = true with LOG = Boolean.parseBoolean("true")Washout
S
32

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)

Shipowner answered 15/3, 2010 at 13:34 Comment(9)
need a "" after the -i in Sed if running on Mac (as per this ) Thanks.Velasquez
I feel that this might be what I end up using for something I'm working on because I didn't have much luck doing it with Proguard at allIndex
And what if you have a Log after a non-bracketed while branch, as you suggested in your first post?Squalid
@type-a1pha: If you adopt this solution, then you have to consider bracket blocks as mandatory.Shipowner
@NicolasRaoul The semi colon fixes this issue (// vs. ;//)Gaby
where to execute these lines in android studio?Toothwort
@GopalSinghSirvi: I added the last paragraph explaining how to execute. Not in Android Studio.Shipowner
Actually i am trying it in Android Studio.. Is there any method for it in Studio?Toothwort
@GopalSinghSirvi: All of the solutions above mine are achievable within Android Studio, try them.Shipowner
F
20

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(...);
}
Flogging answered 9/9, 2015 at 15:8 Comment(2)
This doesn't work with the new Jack compiler-- #37932614Barham
This helped me; both proguard-android-optimize.txt as default Proguard file and -assumenosideeffects in custom Proguard file were needed! I'm using R8 shinker (the default nowadays) and default Android logging.Titleholder
I
12

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

Inhospitality answered 20/10, 2014 at 9:13 Comment(2)
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.Domel
zserge, your log solution looks good. Lots of features. Have you considered adding Lint rules like Timber has?Topliffe
L
8

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");
  }
}
Lines answered 30/3, 2013 at 4:52 Comment(1)
+1 for bug report on Build.DEBUG that I used to use. I also gave up with the various "correct" workarounds and use a similar style solution to you.Tangle
T
7

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)

Throwback answered 27/7, 2011 at 23:46 Comment(2)
A very nice approach when you can't use Obfuscation....particularly because of roboguice breaking because of proguard LOLEducatee
Updated link for robojuice's logging facility: github.com/roboguice/roboguice/wiki/Logging-via-LnTabathatabb
O
7

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.

Offoffbroadway answered 21/3, 2016 at 17:29 Comment(6)
Try doing the same thing for your imports, and make sure the Regular Expression box is checked Text to find: import android\.util\.Log\; Replace with: import android\.util\.Log\;\nimport timber\.log\.Timber\;Dunnock
or you can use structual search and replace like Chike Mgbemena shows in his postBeitz
@MaksimTuraev Your link is no longer relevant. Now it is a blog about hairstyles.Lighterage
Looks like post is removed =( can't find it anywhere.Beitz
@MaksimTuraev here is a copy from Wayback machine, but the images are broken - web.archive.org/web/20161004161318/http://chikemgbemena.com/…Lighterage
Replacing with : Timber.e(TAG + ": " + would be a better practice.Jermainejerman
F
7

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.

Fructose answered 29/5, 2017 at 12:19 Comment(0)
M
6

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);
    }
}
Monegasque answered 6/12, 2013 at 13:21 Comment(0)
P
6
  1. Go to Application->app->proguard-rules.pro enter image description here

  2. Enter below code inside proguard-rules.pro`

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

# You can remove the particular debug class if you want that debug type bug in log

  1. In build.gradle(app) ->android do this thing

    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
      }
    
Palish answered 7/10, 2021 at 7:22 Comment(0)
S
4

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(...);
}
Soraya answered 2/4, 2017 at 13:37 Comment(0)
E
3

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.

Edmund answered 20/12, 2014 at 18:38 Comment(3)
Please read the "If I comment the Log line" paragraph in my question.Shipowner
OK, yes I should re-read more often after browsing the answers :). If you have such cases, you might want a different solution such as suggested before such as putting all your logs behind another interface. My suggestion perhaps works better for smaller teams and projects, where people wish to avoid overhead of extra logging libs, you know the people and code well, etc.Edmund
Replacing Log.d with ;//Log.d takes care of that "If" scenario too.Pentameter
S
3

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

Stannwood answered 11/12, 2015 at 11:6 Comment(7)
This seems to open work with eclipse projects. The search option is not even available on android studios.Offoffbroadway
in Android Studio you can do similar search with Ctrl+Shift+F shortcutStannwood
The example code in the question explains why this is not reliable.Shipowner
It could cause problems removing any command that contains in Log. For example chocolateLog.recipie();Mutilate
Unable to find this option for Android Studio 2.1. Also, I can use this trick on 1 file at a time by normal search/replace.Liberty
in Android Studio you can do similar search with Ctrl+Shift+F shortcut and Replace by Ctrl+Shift+RStannwood
Simplest and safe solution.Hardy
M
3

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
Mcnally answered 21/6, 2016 at 6:57 Comment(2)
Do you have to put that line of code in each Activity/Fragment, or just in one place?Purl
@NoahTernullo // in derived Application file. DefaultApplication.javaMcnally
R
3

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(...);
}
Router answered 6/7, 2020 at 15:39 Comment(0)
A
1

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


}
Adele answered 29/3, 2012 at 10:12 Comment(1)
Same problem as the previous solution. If the string parameter is build using expensive calls, it still wastes resources. The check for calling needs to be done before building the parameters passed.Squalid
P
1

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

Platinotype answered 26/3, 2013 at 7:39 Comment(1)
It does not seem to remove debug logging by default, though. So Christopher's answer sounds better.Shipowner
T
1

If you want to use a programmatic approach instead of using ProGuard, then by creating your own class with two instances, one for debug and one for release, you can choose what to log in either circumstances.

So, if you don't want to log anything when in release, simply implement a Logger that does nothing, like the example below:

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
        }

    }
}

Then to use your logger class:

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

== UPDATE ==

If we want to avoid string concatenations for better performances, we can add an inline function with a lambda that will be called only in debug config:

// Add this function to the Logger class.
inline fun commit(block: Logger.() -> Unit) {
    if(this is LoggerDebug)
        block.invoke(this)
}

And then:

 logger.commit {
     log("Logging without $myVar waste of resources"+ "My fancy concat")
 }

Since we are using an inline function, there are no extra object allocation and no extra virtual method calls.

Torin answered 18/12, 2020 at 12:14 Comment(2)
if you do Log.d("tag", "Processed: " + new ItemCounter(blabla) + " items "), even if this log message does not appear in your released version, a StringBuilder is used to create the message, which could be expensive to create.Shipowner
In a performance-critical code situation, you are right, a string concatenation could be expensive to create, expecially inside loops. In these cases i would completely remove the logging code with PorGuard or some other method. Otherwise, if we still want to avoid string concats but we do want to solve the problem programmatically, we can use an inline function block that would be called only if we are in debug configuration.Torin
T
0

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!

Tarpan answered 14/1, 2015 at 13:53 Comment(1)
the problem with this and other solutions apart form proguard or commenting them is that you're leaving in the code, causing possibly a large amount of string builds. in an average app not a problem, but if you're trying to optimize it becomes a problem.Interplead
C
0

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.

Catercornered answered 12/12, 2016 at 10:42 Comment(0)
R
0

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 :)

Runyan answered 6/9, 2017 at 23:40 Comment(2)
Interesting, hopefully such lines are then ignored by the compiler/optimizer. The variable name would need to be unique, though, because some methods have several log calls, and you can't declare the same variable twice.Shipowner
You can declare the variable on the top on activity and remove the boolean declaration from this line ;)Runyan
F
0

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.

Fondafondant answered 12/9, 2019 at 10:46 Comment(2)
An inconvenient is that it is more verbose than just writing Log.d("tag","msg");, and also it is easy to forget writing the if(BuildConfig.DEBUG) part.Shipowner
Another problem with this is the strings remain in the packed release.Mariannamarianne
F
0

my Way:

1) enable Column Selection Mode (alt+shift+insert)

2) select on one Log.d(TAG, "text"); the part 'Log.'

3) then do shift + ctrl + alt + j

4) click left arrow

5) do shift+end

6) hit delete.

this removes all LOG calls at once in a java file.

Fukien answered 26/3, 2020 at 18:35 Comment(0)
D
0

Easy with kotlin, just declare a few top level functions

val isDebug: Boolean
    get() = BuildConfig.DEBUG

fun logE(tag: String, message: String) {
    if (isDebug) Log.e(tag, message)
}

fun logD(tag: String, message: String) {
    if (isDebug) Log.d(tag, message)
}
Doomsday answered 8/5, 2020 at 8:52 Comment(0)
M
0

I have used below approach in my project

Created custom logger class:

public class LoggerData 
{
   
    public static void showLog(String type, Object object) {
        try {
            Log.d("loggerData:" + type + "-", "showLog: " + new Gson().toJson(object));
        } catch (Exception e) {
            Log.d("TAG", "showLog: " + e.getLocalizedMessage());
            Log.d("loggerData:" + type + "-", "showLog: " + object);
        }

    }

    public static void showLog(Object object) {
        
            try {
                Log.d("loggerData:" + "-", "showLog: +" + new Gson().toJson(object));
            } catch (Exception e) {
                Log.d("TAG", "showLog: " + e.getLocalizedMessage());
                Log.d("loggerData:" + "-", "showLog: " + object);
            }
        
    }
}

Then whenever required logs in code use like this way

  LoggerData.showLog("Refreshed token: ", token);

before building release APK, disable logs only one place in LoggerData class

example

public class LoggerData {
    

    public static void showLog(String type, Object object) {
        try {
            //Log.d("loggerData:" + type + "-", "showLog: " + new Gson().toJson(object));
        } catch (Exception e) {
            //Log.d("TAG", "showLog: " + e.getLocalizedMessage());
            //Log.d("loggerData:" + type + "-", "showLog: " + object);
        }

    }

    public static void showLog(Object object) {
       
            try {
              //  Log.d("loggerData:" + "-", "showLog: +" + new Gson().toJson(object));
            } catch (Exception e) {
                //Log.d("TAG", "showLog: " + e.getLocalizedMessage());
                //Log.d("loggerData:" + "-", "showLog: " + object);
            }
        }
    }

Hope it will help you as well.

Mucronate answered 17/12, 2021 at 13:51 Comment(0)
N
0

Here's a simple Kotlin solution that isn't Android or logging API-specific:

Set up some helper object LoggingUtils:

object LoggingUtils {
  const val DEBUG_LOGGING_ENABLED = false

  /** Wraps log lines that should be removed from the prod binary. */
  inline fun debugLog(logBlock: () -> Unit) {
    if (DEBUG_LOGGING_ENABLED) logBlock()
  }
}

then wrap lines in that method:

fun handleRequest(req: Request) {
  debugLog { logger.atFinest().log("This is a high-volume debug log! %s", request) }

  // ...

  try {
    // ...
  } catch (e: Exception) {
    logger.atSevere().withCause(e).log("I want this to appear in prod logs!")
  }
}

Since the debugLog method is marked as inline and the variable DEBUG_LOGGING_ENABLED is a constant, the log line is simply included or optimized away at compile time. No lambdas are allocated, no method calls.

It's a little cleaner and easier to refactor than wrapping each log line with if() {} statements individually, and the tempting option of making wrappers for your loggers can have significant downsides in terms of compiler and logging server optimizations, safeguards against logging user data inappropriately, etc.

Newark answered 12/2, 2023 at 15:14 Comment(0)
R
-1

the simplest way;

use DebugLog

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

https://github.com/MustafaFerhan/DebugLog

Rank answered 1/7, 2015 at 8:55 Comment(1)
This is absolutely wrong. This only causes the logs to not be logged, it doesn't remove them from the code, so they're still there to help people reverse-engineer your code, and it still has the cost of formatting the strings of all those logs.Feature
M
-2

Here is my solution if you don't want to mess with additional libraries or edit your code manually. I created this Jupyter notebook to go over all java files and comment out all the Log messages. Not perfect but it got the job done for me.

Market answered 29/2, 2020 at 20:40 Comment(0)
B
-3

You can try use this simple conventional method:

Ctrl+Shift+R

replace

Log.e(

With

// Log.e(
Bacterin answered 27/4, 2020 at 17:58 Comment(1)
That would not work well with the example code given in the question.Shipowner

© 2022 - 2024 — McMap. All rights reserved.