Skip to content

Commit 24b758e

Browse files
maskaravivekmisaochan
authored andcommitted
Fix log reporting for release builds (#1916) (#1952)
* Fix log reporting for release builds * Fix logs for release builds * wip * Clean up the branch to exclude unrelated changes * With java docs * Uncomment quiz checker * Check for external storage permissions before sending logs * With more java docs * Fix crash while zipping log files * Do not log token and cookies * Add instruction to restart app
1 parent 9b708db commit 24b758e

27 files changed

+824
-135
lines changed

app/build.gradle

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,24 @@ dependencies {
1313
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
1414
implementation 'in.yuvi:http.fluent:1.3'
1515
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
16+
1617
implementation 'ch.acra:acra:4.9.2'
18+
1719
implementation 'org.mediawiki:api:1.3'
1820
implementation 'commons-codec:commons-codec:1.10'
1921
implementation 'com.github.pedrovgs:renderers:3.3.3'
20-
implementation 'com.google.code.gson:gson:2.8.1'
21-
implementation 'com.jakewharton.timber:timber:4.5.1'
22+
implementation 'com.google.code.gson:gson:2.8.5'
23+
implementation 'com.jakewharton.timber:timber:4.4.0'
2224
implementation 'info.debatty:java-string-similarity:0.24'
2325
implementation 'com.borjabravo:readmoretextview:2.1.0'
2426

25-
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
27+
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
28+
29+
implementation 'org.slf4j:slf4j-api:1.7.25'
30+
api ("com.github.tony19:logback-android-classic:1.1.1-6") {
31+
exclude group: 'com.google.android', module: 'android'
32+
}
33+
2634
implementation('com.mapbox.mapboxsdk:mapbox-android-sdk:5.5.0@aar') {
2735
transitive = true
2836
}
18.7 MB
Binary file not shown.

app/prod/release/output.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":90,"versionName":"2.8.3","enabled":true,"outputFile":"app-commons-v2.8.3-acra-prod-release.apk","fullName":"prodRelease","baseName":"prod-release"},"path":"app-commons-v2.8.3-acra-prod-release.apk","properties":{}}]

app/proguard-rules.txt

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,70 @@
11
-dontobfuscate
22
-keep class org.apache.http.** { *; }
33
-dontwarn org.apache.http.**
4-
-keep class android.support.v7.widget.ShareActionProvider { *; }
4+
-keep class android.support.v7.widget.ShareActionProvider { *; }
5+
6+
# --- Butter Knife ---
7+
# Finder.castParam() is stripped when not needed and ProGuard notes it
8+
# unnecessarily. When castParam() is needed, it's not stripped. e.g.:
9+
#
10+
# @OnItemSelected(value = R.id.history_entry_list)
11+
# void foo(ListView bar) {
12+
# L.d("baz");
13+
# }
14+
15+
-dontnote butterknife.internal.**
16+
# --- /Butter Knife ---
17+
18+
# --- Retrofit2 ---
19+
# Platform calls Class.forName on types which do not exist on Android to determine platform.
20+
-dontnote retrofit2.Platform
21+
# Platform used when running on Java 8 VMs. Will not be used at runtime.
22+
-dontwarn retrofit2.Platform$Java8
23+
# Retain generic type information for use by reflection by converters and adapters.
24+
-keepattributes Signature
25+
# Retain declared checked exceptions for use by a Proxy instance.
26+
-keepattributes Exceptions
27+
# --- /Retrofit ---
28+
29+
# --- OkHttp + Okio ---
30+
-dontwarn okhttp3.**
31+
-dontwarn okio.**
32+
# --- /OkHttp + Okio ---
33+
34+
# --- Gson ---
35+
# https://github.com/google/gson/blob/master/examples/android-proguard-example/proguard.cfg
36+
37+
# Gson uses generic type information stored in a class file when working with fields. Proguard
38+
# removes such information by default, so configure it to keep all of it.
39+
-keepattributes Signature
40+
41+
# For using GSON @Expose annotation
42+
-keepattributes *Annotation*
43+
44+
# Gson specific classes
45+
-dontwarn sun.misc.**
46+
#-keep class com.google.gson.stream.** { *; }
47+
48+
# Application classes that will be serialized/deserialized over Gson
49+
-keep class com.google.gson.examples.android.model.** { *; }
50+
51+
# Prevent proguard from stripping interface information from TypeAdapterFactory,
52+
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
53+
-keep class * implements com.google.gson.TypeAdapterFactory
54+
-keep class * implements com.google.gson.JsonSerializer
55+
-keep class * implements com.google.gson.JsonDeserializer
56+
# --- /Gson ---
57+
58+
59+
# --- /logback ---
60+
61+
-keep class ch.qos.** { *; }
62+
-keep class org.slf4j.** { *; }
63+
-keepattributes *Annotation*
64+
65+
-dontwarn ch.qos.logback.core.net.*
66+
67+
# --- /acra ---
68+
-keep class org.acra.** { *; }
69+
-keepattributes SourceFile,LineNumberTable
70+
-keepattributes *Annotation*

app/src/main/AndroidManifest.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@
122122
android:name=".achievements.AchievementsActivity"
123123
android:label="@string/Achievements" />
124124

125+
<activity android:name="com.github.pedrovgs.lynx.LynxActivity"/>
126+
125127
<service android:name=".upload.UploadService" />
126128
<service
127129
android:name=".auth.WikiAccountAuthenticatorService"
@@ -157,6 +159,11 @@
157159
android:resource="@xml/modifications_sync_adapter" />
158160
</service>
159161

162+
<service
163+
android:name="org.acra.sender.SenderService"
164+
android:exported="false"
165+
android:process=":acra" />
166+
160167
<provider
161168
android:name="android.support.v4.content.FileProvider"
162169
android:authorities="${applicationId}.provider"

app/src/main/java/fr/free/nrw/commons/CommonsApplication.java

Lines changed: 84 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
package fr.free.nrw.commons;
22

33
import android.annotation.SuppressLint;
4+
import android.app.Application;
5+
import android.app.NotificationChannel;
6+
import android.app.NotificationManager;
47
import android.content.Context;
58
import android.content.SharedPreferences;
69
import android.database.sqlite.SQLiteDatabase;
7-
import android.support.multidex.MultiDexApplication;
10+
import android.os.Build;
11+
import android.os.Process;
12+
import android.support.annotation.NonNull;
13+
import android.util.Log;
814

915
import com.facebook.drawee.backends.pipeline.Fresco;
1016
import com.facebook.imagepipeline.core.ImagePipelineConfig;
@@ -25,17 +31,20 @@
2531

2632
import fr.free.nrw.commons.auth.SessionManager;
2733
import fr.free.nrw.commons.category.CategoryDao;
34+
import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler;
35+
import fr.free.nrw.commons.concurrency.ThreadPoolService;
2836
import fr.free.nrw.commons.contributions.ContributionDao;
2937
import fr.free.nrw.commons.data.DBOpenHelper;
3038
import fr.free.nrw.commons.di.ApplicationlessInjection;
39+
import fr.free.nrw.commons.logging.FileLoggingTree;
40+
import fr.free.nrw.commons.logging.LogUtils;
3141
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
3242
import fr.free.nrw.commons.upload.FileUtils;
3343
import fr.free.nrw.commons.utils.ContributionUtils;
3444
import io.reactivex.android.schedulers.AndroidSchedulers;
3545
import io.reactivex.schedulers.Schedulers;
3646
import timber.log.Timber;
3747

38-
// TODO: Use ProGuard to rip out reporting when publishing
3948
@ReportsCrashes(
4049
mailTo = "[email protected]",
4150
mode = ReportingInteractionMode.DIALOG,
@@ -44,24 +53,28 @@
4453
resDialogCommentPrompt = R.string.crash_dialog_comment_prompt,
4554
resDialogOkToast = R.string.crash_dialog_ok_toast
4655
)
47-
public class CommonsApplication extends MultiDexApplication {
48-
56+
public class CommonsApplication extends Application {
4957
@Inject SessionManager sessionManager;
5058
@Inject DBOpenHelper dbOpenHelper;
5159

5260
@Inject @Named("default_preferences") SharedPreferences defaultPrefs;
5361
@Inject @Named("application_preferences") SharedPreferences applicationPrefs;
5462
@Inject @Named("prefs") SharedPreferences otherPrefs;
63+
@Inject
64+
@Named("isBeta")
65+
boolean isBeta;
5566

5667
public static final String DEFAULT_EDIT_SUMMARY = "Uploaded using [[COM:MOA|Commons Mobile App]]";
5768

5869
public static final String FEEDBACK_EMAIL = "[email protected]";
5970

6071
public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback";
6172

62-
public static final String LOGS_PRIVATE_EMAIL = "[email protected]";
73+
public static final String NOTIFICATION_CHANNEL_ID_ALL = "CommonsNotificationAll";
6374

64-
public static final String LOGS_PRIVATE_EMAIL_SUBJECT = "Commons Android App (%s) Logs";
75+
/**
76+
* Constants End
77+
*/
6578

6679
private RefWatcher refWatcher;
6780

@@ -82,29 +95,86 @@ public void onCreate() {
8295
.getInstance(this)
8396
.getCommonsApplicationComponent()
8497
.inject(this);
98+
99+
initTimber();
100+
85101
// Set DownsampleEnabled to True to downsample the image in case it's heavy
86102
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)
87103
.setDownsampleEnabled(true)
88104
.build();
89-
Fresco.initialize(this,config);
90-
if (setupLeakCanary() == RefWatcher.DISABLED) {
91-
return;
105+
try {
106+
Fresco.initialize(this, config);
107+
} catch (Exception e) {
108+
Timber.e(e);
109+
// TODO: Remove when we're able to initialize Fresco in test builds.
92110
}
111+
93112
// Empty temp directory in case some temp files are created and never removed.
94113
ContributionUtils.emptyTemporaryDirectory();
95114

96-
Timber.plant(new Timber.DebugTree());
97-
98-
if (!BuildConfig.DEBUG) {
99-
ACRA.init(this);
100-
} else {
115+
initAcra();
116+
if (BuildConfig.DEBUG) {
101117
Stetho.initializeWithDefaults(this);
102118
}
103119

120+
createNotificationChannel(this);
121+
122+
123+
if (setupLeakCanary() == RefWatcher.DISABLED) {
124+
return;
125+
}
104126
// Fire progress callbacks for every 3% of uploaded content
105127
System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0");
106128
}
107129

130+
/**
131+
* Plants debug and file logging tree.
132+
* Timber lets you plant your own logging trees.
133+
*
134+
*/
135+
private void initTimber() {
136+
String logFileName = isBeta ? "CommonsBetaAppLogs" : "CommonsAppLogs";
137+
String logDirectory = LogUtils.getLogDirectory(isBeta);
138+
FileLoggingTree tree = new FileLoggingTree(
139+
Log.DEBUG,
140+
logFileName,
141+
logDirectory,
142+
1000,
143+
getFileLoggingThreadPool());
144+
145+
Timber.plant(tree);
146+
Timber.plant(new Timber.DebugTree());
147+
}
148+
149+
/**
150+
* Remove ACRA's UncaughtExceptionHandler
151+
* We do this because ACRA's handler spawns a new process possibly screwing up with a few things
152+
*/
153+
private void initAcra() {
154+
Thread.UncaughtExceptionHandler exceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
155+
ACRA.init(this);
156+
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
157+
}
158+
159+
private ThreadPoolService getFileLoggingThreadPool() {
160+
return new ThreadPoolService.Builder("file-logging-thread")
161+
.setPriority(Process.THREAD_PRIORITY_LOWEST)
162+
.setPoolSize(1)
163+
.setExceptionHandler(new BackgroundPoolExceptionHandler())
164+
.build();
165+
}
166+
167+
public static void createNotificationChannel(@NonNull Context context) {
168+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
169+
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
170+
NotificationChannel channel = manager.getNotificationChannel(NOTIFICATION_CHANNEL_ID_ALL);
171+
if (channel == null) {
172+
channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID_ALL,
173+
context.getString(R.string.notifications_channel_name_all), NotificationManager.IMPORTANCE_DEFAULT);
174+
manager.createNotificationChannel(channel);
175+
}
176+
}
177+
}
108178

109179
/**
110180
* Helps in setting up LeakCanary library

app/src/main/java/fr/free/nrw/commons/Utils.java

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -148,39 +148,6 @@ public static boolean isDarkTheme(Context context) {
148148
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("theme", false);
149149
}
150150

151-
/**
152-
* Will be used to fetch the logs generated by the app ever since the beginning of times....
153-
* i.e. since the time the app started.
154-
*
155-
* @return String containing all the logs since the time the app started
156-
*/
157-
public static String getAppLogs() {
158-
final String processId = Integer.toString(android.os.Process.myPid());
159-
160-
StringBuilder stringBuilder = new StringBuilder();
161-
162-
try {
163-
String[] command = new String[]{"logcat","-d","-v","threadtime"};
164-
165-
Process process = Runtime.getRuntime().exec(command);
166-
167-
BufferedReader bufferedReader = new BufferedReader(
168-
new InputStreamReader(process.getInputStream())
169-
);
170-
171-
String line;
172-
while ((line = bufferedReader.readLine()) != null) {
173-
if (line.contains(processId)) {
174-
stringBuilder.append(line);
175-
}
176-
}
177-
} catch (IOException ioe) {
178-
Timber.e("getAppLogs failed", ioe);
179-
}
180-
181-
return stringBuilder.toString();
182-
}
183-
184151
public static void rateApp(Context context) {
185152
final String appPackageName = BuildConfig.class.getPackage().getName();
186153
try {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package fr.free.nrw.commons.concurrency;
2+
3+
import android.support.annotation.NonNull;
4+
5+
import fr.free.nrw.commons.BuildConfig;
6+
7+
public class BackgroundPoolExceptionHandler implements ExceptionHandler {
8+
/**
9+
* If an exception occurs on a background thread, this handler will crash for debug builds
10+
* but fail silently for release builds.
11+
* @param t
12+
*/
13+
@Override
14+
public void onException(@NonNull final Throwable t) {
15+
//Crash for debug build
16+
if (BuildConfig.DEBUG) {
17+
Thread thread = new Thread(() -> {
18+
throw new RuntimeException(t);
19+
});
20+
thread.start();
21+
}
22+
}
23+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package fr.free.nrw.commons.concurrency;
2+
3+
import java.util.concurrent.CancellationException;
4+
import java.util.concurrent.ExecutionException;
5+
import java.util.concurrent.Future;
6+
import java.util.concurrent.ScheduledThreadPoolExecutor;
7+
import java.util.concurrent.ThreadFactory;
8+
9+
class ExceptionAwareThreadPoolExecutor extends ScheduledThreadPoolExecutor {
10+
11+
private final ExceptionHandler exceptionHandler;
12+
13+
public ExceptionAwareThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory,
14+
ExceptionHandler exceptionHandler) {
15+
super(corePoolSize, threadFactory);
16+
this.exceptionHandler = exceptionHandler;
17+
}
18+
19+
@Override
20+
protected void afterExecute(Runnable r, Throwable t) {
21+
super.afterExecute(r, t);
22+
if (t == null && r instanceof Future<?>) {
23+
try {
24+
Future<?> future = (Future<?>) r;
25+
if (future.isDone()) future.get();
26+
} catch (CancellationException | InterruptedException e) {
27+
//ignore
28+
} catch (ExecutionException e) {
29+
t = e.getCause() != null ? e.getCause() : e;
30+
} catch (Exception e) {
31+
t = e;
32+
}
33+
}
34+
35+
if (t != null) {
36+
exceptionHandler.onException(t);
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)