Removing Logging from Production Code in Android? - android

What is the best way to remove logging from production android application. It appears that proguard does not do this completely, strings still get written, so I was wondering what is best solution to remove the logging from production code?

The IMO best solution is to write code like below wherever you call your logging methods.
if (SOME_LOG_CONSTANT) Log.d(TAG, "event:" + someSlowToEvaluateMethod());
By changing 1 constant you strip away every logging that you don't want. That way the part behind if (false) should not even get into your .class file since the compiler can completely remove it (it is unreachable code).
This also means that you can exclude whole code blocks from your release software if you wrap them in that if. That's something even proguard can't do.
the SOME_LOG_CONSTANT can be BuildConfig.DEBUG if you use SDK Tools r17 and above. That constant is automatically changed for you depending on build type. Thx #Christopher

Use Timber, it's a nice library to configure your logs:
https://github.com/JakeWharton/timber
Example of use: Timber.d("Activity Created");
Example of configuration:
public class ExampleApp extends Application {
#Override public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) {
Timber.plant(new DebugTree());
} else {
Timber.plant(new CrashReportingTree());
}
}
/** A tree which logs important information for crash reporting. */
private static class CrashReportingTree extends Timber.Tree {
#Override protected void log(int priority, String tag, String message, Throwable t) {
if (priority == Log.VERBOSE || priority == Log.DEBUG) {
return;
}
FakeCrashLibrary.log(priority, tag, message);
if (t != null) {
if (priority == Log.ERROR) {
FakeCrashLibrary.logError(t);
} else if (priority == Log.WARN) {
FakeCrashLibrary.logWarning(t);
}
}
}
}

Configuring Your Application for Release tells you to simply remove Log commands before releasing your application.
You can deactivate logging by removing calls to Log methods in your source files.
What I do is to create a proprietary static log method that reads a global boolean of something like this:
class MyLog {
private static final boolean LOGGING = true; //false to disable logging
public static void d(String tag, String message) {
if (LOGGING) {
Log.d(tag, message);
}
}
/* ... same for v, e, w, i */
}
Use this everywhere you want to log.
MyLog.d("Tag", "This will only work with LOGGING true");

The best way is to handle this in from my experience,
firstly while logging check for BuildConfig.DEBUG flag to print log.
Class LogUtils{
public static void log(Class clazz, String message) {
if (BuildConfig.DEBUG) {//log only in debug mode
Log.d(clazz.getSimpleName(), message);
}
}
}
then enable minify to your app module gradle file, this will enable proguard to optimize your binary
android {
buildTypes {
release {
minifyEnabled true
//shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
}
this will keep debug logs in your debug builds.
But in your release builds the if (BuildConfig.DEBUG) condition is never true, hence proguard will strip off the unreachable log code from your binary.

I suggest you to see this Log extension class.
It enables you to have a fine control on logs.
For example, you can disable all logs (thanks to a configuration file) or just the logs of some packages or classes.
Moreover, it adds some useful functionalities (for instance you don't have to pass a tag for each log).

Simply add this method to your code and use Logd instead of Log.d
private static final String TAG = "your own tag to recognize log entries from this app";
public static void Logd(String txt) {
if (BuildConfig.DEBUG&&BuildConfig.BUILD_TYPE.equals("debug")) Log.d(TAG,txt);
}
I know that the string will still be evaluated, but we are talking microseconds in extra processing time. Noone will ever notice a difference in app speed, unless you are doing many thousands of loggings in a loop.
The DEBUG and BUILD_TYPE are available and working in Android Studio.
The DEBUG boolean is the "debuggable" option in the build.gradle file:
buildTypes {
debug {
debuggable true
runProguard false
proguardFile getDefaultProguardFile('proguard-android.txt')
}
release {
debuggable false
runProguard true
proguardFile getDefaultProguardFile('proguard-android.txt')
}
}
The BUILD_TYPE is set automatically to "debug" when you run in Android Studio.
So if you set debuggable to false OR the user has a release version, then logs will be gone.

I'd recommend you look at Roboguice - http://code.google.com/p/roboguice/ - if you use the built-in Ln function, it will automatically not log on a signed APK. It simplifies a ton of other Android boilerplate as well. You can see it in action with Basedroid - https://github.com/achuinard/basedroid

You can use DebugLog class. All logs are disabled by DebugLog when the app is released. And it provides more understandable DDMS logs for developers.
Ex;
DebugLog.e("your message");

Related

How to disallow emulator, isdebuggable check for debug build

I am resolving some security defects for my app.
Defect is:
Should not allow release app to be run in emulator
Release app should not be debuggable
Should not connect to debugger
Release app should be installed from play store not from other resource
And app signature verification
Code 1)
private static boolean isEmulator() {
try {
boolean goldfish = getSystemProperty("ro.hardware").contains("goldfish");
boolean emu = getSystemProperty("ro.kernel.qemu").length() > 0;
boolean sdk = getSystemProperty("ro.product.model").equals("sdk");
if (emu || goldfish || sdk) {
return true;
}
} catch (Exception e) {
}
return false;
}
Code 2)
public static boolean isDebuggable(Context context) {
if (IdscProperties.getIsDebug()) {
return true;
}
if (isDebuggableEnabled(context) || detectDebugger() || detectThreadCpuTimeNanos()) {
return true;
}
return false;
}
private static boolean isDebuggableEnabled(Context context) {
return (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
Code 3)
private static boolean detectDebugger() {
return Debug.isDebuggerConnected();
}
Code 4)
private static boolean isInstallerPlayStore(Context context) {
final String installer = context.getPackageManager().getInstallerPackageName(context.getPackageName());
return installer != null && installer.startsWith(PLAY_STORE_APP_ID);
}
Code 5)
private static boolean isAppSignatureMatches(Context context) {
String signature = PackageVerifier.getCertificateHash(context, context.getPackageName());
return SIGNATURE.equals(signature);
}
So, My aim is to not allow these checks in debug builds.
We shall create a flag in some prob file and read it when these checks happen and disallow the function execution.
But the flag shall be modified by the hacker and re-pack the APK to dis-allow these checks.
My expectation is allow these checks in release build and not in debug build without any modifiable flag checks.
Before you start on this path, I want to make sure that you realize 2 things:
Every precaution you implement can be (relatively easily) circumvented on a rooted device
Your app can be easily decompiled into bytecode offline, and any security-related code will be plainly visible.
So, knowing this, if your aim is to prevent piracy, you also need to realize that anything you do will only add some extra steps for the attacker to go through. But if someone really wants to do it, it will not stop them.
If you still want advice on how to implement this, let me know

How do I save app logs locally on Android?

I want to save the logs generated by my application locally on the android device and view them in an instance of a crash.
Using the "Take Bug Report" under the developer options gives the entire system logs which are irrelevant to me. I am looking only for those logs created by my application when it runs.
Is there any application that does this? Or are there any libraries I could include in my application code to satisfy my requirement?
You may just add firebase to your project, and everything will be done automatically.
Or if need it to be "locally", can use the Thread.UncaughtExceptionHandler to save crash log. Register it when your application onCreate.
private static UncaughtExceptionHandler mDefaultUncaughtExceptionHandler;
public static void registerUncaughtExceptionHandler() {
mDefaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
public void uncaughtException(Thread thread, Throwable ex) {
// Save Log
saveLog(ex);
// Throw system
mDefaultUncaughtExceptionHandler.uncaughtException(thread, ex);
}
});
}
private static void saveLog(Throwable exception) {
try {
String stackTrace = Log.getStackTraceString(exception);
// Save it to SharedPreferences or DB as you like
} catch (Exception e) {
}
}
Then can extract the last crash log, submit to your server or display in logcat when app starts.
It is much better to use Third Party libraries such as Firebase Crashlytics or Sentry Crash Report or AppMetrica for crash reports.
just add these libraries and make an account on one of these sites, then you can have a full report of crashes if happen.
but if you want to save the logs on the device, you can refer to this question :
Saving Logcat to a text file in Android Device
You can try this
fun writeLog(context: Context) {
try {
val path = File(context.filesDir, "log_files")
if (!path.exists()) {
path.mkdir()
}
val fileName = "your_filename.txt"
Runtime.getRuntime().exec("logcat -v time -f $fileName")
} catch (e: IOException) {
}
}
Or you can change logcat command based on your requirements: refer to this https://developer.android.com/studio/command-line/logcat
You can check it at data/data/{applicationId}/files/log_files/

App is working fine in debug build but not working in production

I am facing problem with my app in live environment. Everything works fine when I generate a debug build for testing and test it thoroughly, but when I uploaded my application on google play console and then in the production app I am facing many problems. For example when I try to login into the app, after entering the correct credentials and clicking the login button a loader is generated and after loading for few seconds it stops on the login screen itself. Ideally the user should be able to login, but the screen is not redirecting the user to home screen.
I have checked hitting the login api url in postman and it works fine. I have also checked the logcat by attaching the production app through usb cable and the logcat shows that api is being called and i can see the response also.
What could be the possible reason please suggest..
This is the retrofit code which is being used for calling a rest api for login purpose.
private void userLoginApi() {
if (Util.isConnectingToInternet(getActivity())) {
CommonMethods.showLoading(getActivity());
MultipartBody.Builder builder = new MultipartBody.Builder();
builder.setType(MultipartBody.FORM);
builder.addFormDataPart(Constants.MOBILE, mobileEt.getText().toString());
builder.addFormDataPart(Constants.PASSWORD, passwordEt.getText().toString());
builder.addFormDataPart(Constants.DEVICE_ID, token);
builder.addFormDataPart(Constants.LANGUAGE, SharedPref.getSharedPreferences(getActivity(), Constants.LANGUAGE));
MultipartBody requestBody = builder.build();
RetrofitClient.getAPIService().user_login(requestBody).enqueue(new Callback<RetroResponse>() {
#Override
public void onResponse(Call<RetroResponse> call, Response<RetroResponse> response) {
CommonMethods.dismissLoading();
try {
if (response.body().getStatus() == 200) {
Pref.with(getApplicationContext()).getSharedPreferences().edit().putBoolean("isLogin", false)
.putString("admin_id", response.body().getId())
.putString("first_name", response.body().getData().getFirstName())
.putString("email", response.body().getData().getEmail())
.apply();
userID = response.body().getData().getUser_id();
SharedPref.setSharedPreference(getActivity(), Constants.USER_ID, response.body().getData().getUser_id());
SharedPref.setSharedPreference(getActivity(), Constants.ADMIN_ID, response.body().getId());
SharedPref.setSharedPreference(getActivity(), Constants.FIRST_NAME, response.body().getData().getFirstName());
SharedPref.setSharedPreference(getActivity(), Constants.LAST_NAME, response.body().getData().getLastName());
SharedPref.setSharedPreference(getActivity(), Constants.EMAIL, response.body().getData().getEmail());
SharedPref.setSharedPreference(getActivity(), Constants.MOBILE, response.body().getData().getMobile());
SharedPref.setSharedPreference(getActivity(), Constants.USER_MOBILE, response.body().getData().getMobile());
SharedPref.setSharedPreference(getActivity(), Constants.PROFILE_CITY, response.body().getData().getCity());
startActivity(new Intent(getActivity(), UserHomeActivity.class)
.putExtra("screen_type", "")
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK));
getActivity().finish();
} else if (response.body().getStatus() == 204) {
Util.ShowToastMessage(getActivity(), response.body().getMessage());
mobileEt.setText("");
passwordEt.setText("");
captchaCheck.setChecked(false);
captchaVerified = false;
} else if (response.body().getStatus() == 401) {
Util.ShowToastMessage(getActivity(), "Something went wrong");
}
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Call<RetroResponse> call, Throwable t) {
CommonMethods.dismissLoading();
}
});
} else {
Util.ShowToastMessage(getActivity(), R.string.internet_connection);
}
}
This api is getting called successfully, I have checked it in logcat. But after it the user is not redirected to intended screen.
The possible reason for this behavior could be because of proguard rules set in the build.gradle file
Change minifyEnabled true to minifyEnabled false
Note:
Proguard is a free Java class file shrinker, optimizer, obfuscator, and preverifier. It detects and removes unused classes, fields, methods, and attributes. Mobile app development companies use proguard in android , it optimizes bytecode and removes unused instructions.
This is a temporary solution and you need to manually figure out the error when minifiyEnable is true.

Android - queryPurchase() returns an empty list but I purchased an in app product

I have implemented in app products in my app. I can make successful purchase and everything works fine. But in one device, purchase doesn't work although when I try to make a purchase again, I get the response "Product already owned" which shows that the purchase is fine. But queryPurchase() returns an empty purchase list.
I've only one email on that device.
public void queryPurchase() {
Runnable queryPurchaseRequest = getQueryPurchaseRequest();
executeRequest(queryPurchaseRequest);
}
private Runnable getQueryPurchaseRequest() {
return new Runnable() {
#Override
public void run() {
Purchase.PurchasesResult purchasesResult = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
if(purchasesResult.getResponseCode() == BillingResponseCode.OK) {
ArrayList<Purchase> purchaseList = new ArrayList<>();
purchaseList.addAll(purchasesResult.getPurchasesList());
if(isSubscriptionSupported()) {
Purchase.PurchasesResult subscriptionResult
= mBillingClient.queryPurchases(BillingClient.SkuType.SUBS);
if (subscriptionResult.getResponseCode() == BillingResponseCode.OK) {
purchaseList.addAll(subscriptionResult.getPurchasesList());
} else {
Log.e(TAG, "Got an error response trying to query subscription purchases");
}
}
onQueryPurchasesFinished(purchaseList);
} else {
Utilities.setPurchaseLog("onBilling manager on query purchase request: result unknown\n");
}
}
};
}
Note: Billing result, subscription response code everything seems successful. And everything is working perfectly on other devices.
Have anyone any solution? Thanks for your help!
It is a known bug that has been happening for ages now, since billing libraries version 2.x (Priority: P1 , Severity S2)
https://issuetracker.google.com/issues/160473001
I think the best thing you can do is going there and leaving your star/comment to try to speed things up.
However, there's something we can miss sometimes: It turns out that you need to wait for the billingClient.startConnection() call to finish doing it's thing and onBillingSetupFinished() callback gets called on the provided BillingClientStateListener. Only after that you can call the queryPurchases method. If you call queryPurchases beforee finishing the connection to the service, you will get an empty list.
I got my answer to this same question via
this answer
I had to comment out the debug suffix in the .app build.gradle
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
}
debug {
// applicationIdSuffix ".debug"
debuggable true
}
}

Are Log messages and System.out.println calls visible once an Android app is published?

I have many calls to Log.d() and System.out.println(), which I have used for debugging, in my Android application. Are these log messages still visible to anyone who runs the production apk?
Yes. it is visible to others and retrievable using tools.
You can pro grammatically check if it is in debug mode by following code,
if (BuildConfig.DEBUG) {
Log.d(TAG, "xxxx");
}
Thank you
Yes, all log messages are still logged into standard Android logcat, even in release builds.
All Log messages still visible to anyone who runs the production apk so you can create a custom class for print all the Log
public class MyLog {
public final boolean ENABLE_LOG = true; // or ENABLE_LOG = BuildConfig.DEBUG
public static void d(String tag, String msg) {
if (ENABLE_LOG){
Log.d(tag, msg);
}
}
public static void e(String tag, String msg){
...
}
...
}
and use
MyLog.d("TAG","test"); // instead of Log.d("TAG","test")
Yes they are. They are indeed.

Categories

Resources