feat: add persistent logger and integrate it to the loggers initialization

This commit is contained in:
Harris 2021-09-23 10:27:57 +10:00
parent 5a290ddf68
commit 17b58b09e3
3 changed files with 258 additions and 12 deletions

View File

@ -153,7 +153,7 @@ dependencies {
testImplementation 'org.robolectric:shadows-multidex:4.4'
}
def canonicalVersionCode = 222
def canonicalVersionCode = 223
def canonicalVersionName = "1.11.10"
def postFixSize = 10

View File

@ -15,6 +15,9 @@
*/
package org.thoughtcrime.securesms;
import static nl.komponents.kovenant.android.KovenantAndroid.startKovenant;
import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
@ -48,24 +51,23 @@ import org.signal.aesgcmprovider.AesGcmProvider;
import org.thoughtcrime.securesms.components.TypingStatusSender;
import org.thoughtcrime.securesms.crypto.KeyPairUtilities;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.LokiAPIDatabase;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule;
import org.thoughtcrime.securesms.groups.OpenGroupManager;
import org.thoughtcrime.securesms.home.HomeActivity;
import org.thoughtcrime.securesms.jobmanager.DependencyInjector;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer;
import org.thoughtcrime.securesms.jobs.FastJobStorage;
import org.thoughtcrime.securesms.jobs.JobManagerFactories;
import org.thoughtcrime.securesms.logging.AndroidLogger;
import org.thoughtcrime.securesms.logging.PersistentLogger;
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
import org.thoughtcrime.securesms.home.HomeActivity;
import org.thoughtcrime.securesms.notifications.BackgroundPollWorker;
import org.thoughtcrime.securesms.notifications.LokiPushNotificationManager;
import org.thoughtcrime.securesms.groups.OpenGroupManager;
import org.thoughtcrime.securesms.database.LokiAPIDatabase;
import org.thoughtcrime.securesms.util.Broadcaster;
import org.thoughtcrime.securesms.notifications.FcmUtils;
import org.thoughtcrime.securesms.util.UiModeUtilities;
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier;
import org.thoughtcrime.securesms.notifications.FcmUtils;
import org.thoughtcrime.securesms.notifications.LokiPushNotificationManager;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier;
import org.thoughtcrime.securesms.providers.BlobProvider;
@ -75,6 +77,8 @@ import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
import org.thoughtcrime.securesms.sskenvironment.ProfileManager;
import org.thoughtcrime.securesms.sskenvironment.ReadReceiptManager;
import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository;
import org.thoughtcrime.securesms.util.Broadcaster;
import org.thoughtcrime.securesms.util.UiModeUtilities;
import org.thoughtcrime.securesms.util.dynamiclanguage.LocaleParseHelper;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.PeerConnectionFactory.InitializationOptions;
@ -93,9 +97,6 @@ import kotlin.Unit;
import kotlinx.coroutines.Job;
import network.loki.messenger.BuildConfig;
import static nl.komponents.kovenant.android.KovenantAndroid.startKovenant;
import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant;
/**
* Will be called once when the TextSecure process is created.
* <p>
@ -276,7 +277,7 @@ public class ApplicationContext extends Application implements DependencyInjecto
}
private void initializeLogging() {
Log.initialize(new AndroidLogger());
Log.initialize(new AndroidLogger(), new PersistentLogger(this));
}
private void initializeCrashHandling() {

View File

@ -0,0 +1,245 @@
package org.thoughtcrime.securesms.logging;
import android.content.Context;
import androidx.annotation.AnyThread;
import androidx.annotation.WorkerThread;
import org.session.libsignal.utilities.ListenableFuture;
import org.session.libsignal.utilities.Log;
import org.session.libsignal.utilities.NoExternalStorageException;
import org.session.libsignal.utilities.SettableFuture;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class PersistentLogger extends Log.Logger {
private static final String TAG = PersistentLogger.class.getSimpleName();
private static final String LOG_V = "V";
private static final String LOG_D = "D";
private static final String LOG_I = "I";
private static final String LOG_W = "W";
private static final String LOG_E = "E";
private static final String LOG_WTF = "A";
private static final String LOG_DIRECTORY = "log";
private static final String FILENAME_PREFIX = "log-";
private static final int MAX_LOG_FILES = 5;
private static final int MAX_LOG_SIZE = 300 * 1024;
private static final int MAX_LOG_EXPORT = 10_000;
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS zzz");
private final Context context;
private final Executor executor;
private final byte[] secret;
private LogFile.Writer writer;
public PersistentLogger(Context context) {
this.context = context.getApplicationContext();
this.secret = LogSecretProvider.getOrCreateAttachmentSecret(context);
this.executor = Executors.newSingleThreadExecutor(r -> {
Thread thread = new Thread(r, "PersistentLogger");
thread.setPriority(Thread.MIN_PRIORITY);
return thread;
});
executor.execute(this::initializeWriter);
}
@Override
public void v(String tag, String message, Throwable t) {
write(LOG_V, tag, message, t);
}
@Override
public void d(String tag, String message, Throwable t) {
write(LOG_D, tag, message, t);
}
@Override
public void i(String tag, String message, Throwable t) {
write(LOG_I, tag, message, t);
}
@Override
public void w(String tag, String message, Throwable t) {
write(LOG_W, tag, message, t);
}
@Override
public void e(String tag, String message, Throwable t) {
write(LOG_E, tag, message, t);
}
@Override
public void wtf(String tag, String message, Throwable t) {
write(LOG_WTF, tag, message, t);
}
@Override
public void blockUntilAllWritesFinished() {
CountDownLatch latch = new CountDownLatch(1);
executor.execute(latch::countDown);
try {
latch.await();
} catch (InterruptedException e) {
android.util.Log.w(TAG, "Failed to wait for all writes.");
}
}
@WorkerThread
public ListenableFuture<String> getLogs() {
final SettableFuture<String> future = new SettableFuture<>();
executor.execute(() -> {
StringBuilder builder = new StringBuilder();
try {
File[] logs = getSortedLogFiles();
for (int i = logs.length - 1; i >= 0; i--) {
try {
LogFile.Reader reader = new LogFile.Reader(secret, logs[i]);
builder.append(reader.readAll());
} catch (IOException e) {
android.util.Log.w(TAG, "Failed to read log at index " + i + ". Removing reference.");
logs[i].delete();
}
}
future.set(builder.toString());
} catch (NoExternalStorageException e) {
future.setException(e);
}
});
return future;
}
@WorkerThread
private void initializeWriter() {
try {
writer = new LogFile.Writer(secret, getOrCreateActiveLogFile());
} catch (NoExternalStorageException | IOException e) {
android.util.Log.e(TAG, "Failed to initialize writer.", e);
}
}
@AnyThread
private void write(String level, String tag, String message, Throwable t) {
executor.execute(() -> {
try {
if (writer == null) {
return;
}
if (writer.getLogSize() >= MAX_LOG_SIZE) {
writer.close();
writer = new LogFile.Writer(secret, createNewLogFile());
trimLogFilesOverMax();
}
for (String entry : buildLogEntries(level, tag, message, t)) {
writer.writeEntry(entry);
}
} catch (NoExternalStorageException e) {
android.util.Log.w(TAG, "Cannot persist logs.", e);
} catch (IOException e) {
android.util.Log.w(TAG, "Failed to write line. Deleting all logs and starting over.");
deleteAllLogs();
initializeWriter();
}
});
}
private void trimLogFilesOverMax() throws NoExternalStorageException {
File[] logs = getSortedLogFiles();
if (logs.length > MAX_LOG_FILES) {
for (int i = MAX_LOG_FILES; i < logs.length; i++) {
logs[i].delete();
}
}
}
private void deleteAllLogs() {
try {
File[] logs = getSortedLogFiles();
for (File log : logs) {
log.delete();
}
} catch (NoExternalStorageException e) {
android.util.Log.w(TAG, "Was unable to delete logs.", e);
}
}
private File getOrCreateActiveLogFile() throws NoExternalStorageException {
File[] logs = getSortedLogFiles();
if (logs.length > 0) {
return logs[0];
}
return createNewLogFile();
}
private File createNewLogFile() throws NoExternalStorageException {
return new File(getOrCreateLogDirectory(), FILENAME_PREFIX + System.currentTimeMillis());
}
private File[] getSortedLogFiles() throws NoExternalStorageException {
File[] logs = getOrCreateLogDirectory().listFiles();
if (logs != null) {
Arrays.sort(logs, (o1, o2) -> o2.getName().compareTo(o1.getName()));
return logs;
}
return new File[0];
}
private File getOrCreateLogDirectory() throws NoExternalStorageException {
File logDir = new File(context.getCacheDir(), LOG_DIRECTORY);
if (!logDir.exists() && !logDir.mkdir()) {
throw new NoExternalStorageException("Unable to create log directory.");
}
return logDir;
}
private List<String> buildLogEntries(String level, String tag, String message, Throwable t) {
List<String> entries = new LinkedList<>();
Date date = new Date();
entries.add(buildEntry(level, tag, message, date));
if (t != null) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
t.printStackTrace(new PrintStream(outputStream));
String trace = new String(outputStream.toByteArray());
String[] lines = trace.split("\\n");
for (String line : lines) {
entries.add(buildEntry(level, tag, line, date));
}
}
return entries;
}
private String buildEntry(String level, String tag, String message, Date date) {
return DATE_FORMAT.format(date) + ' ' + level + ' ' + tag + ": " + message;
}
}