Ensure uploaded logs match debug log viewer.

This commit is contained in:
Greyson Parrelli
2025-08-13 21:20:44 -04:00
parent fc1cd6d262
commit a34ccd8ce7
6 changed files with 132 additions and 21 deletions

View File

@@ -376,18 +376,28 @@ public class SubmitDebugLogActivity extends BaseActivity {
switch (mode) {
case LOADING:
searchNav.setVisibility(View.GONE);
saveMenuItem.setVisible(false);
searchMenuItem.setVisible(false);
progressCard.setVisibility(View.VISIBLE);
submitButton.setEnabled(false);
logWebView.setAlpha(0.25f);
break;
case NORMAL:
searchNav.setVisibility(View.GONE);
saveMenuItem.setVisible(true);
searchMenuItem.setVisible(true);
progressCard.setVisibility(View.GONE);
submitButton.setEnabled(true);
logWebView.setAlpha(1f);
break;
case SUBMITTING:
searchMenuItem.setVisible(false);
searchNav.setVisibility(View.GONE);
saveMenuItem.setVisible(false);
progressCard.setVisibility(View.VISIBLE);
searchMenuItem.setVisible(false);
progressCard.setVisibility(View.GONE);
submitButton.setSpinning();
logWebView.setAlpha(1f);
break;
}
}
@@ -447,7 +457,7 @@ public class SubmitDebugLogActivity extends BaseActivity {
private void onSubmitClicked() {
submitButton.setSpinning();
viewModel.onSubmitClicked().observe(this, result -> {
viewModel.onSubmitClicked(DebugLogsViewer.readLogs(logWebView)).observe(this, result -> {
if (result.isPresent()) {
presentResultDialog(result.get());
} else {

View File

@@ -19,6 +19,7 @@ import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.signal.core.util.logging.Scrubber;
import org.signal.core.util.tracing.Tracer;
import org.signal.debuglogsviewer.DebugLogsViewer;
import org.thoughtcrime.securesms.database.LogDatabase;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor;
@@ -125,15 +126,8 @@ public class SubmitDebugLogRepository {
});
}
/**
* Submits a log with the provided prefix lines.
*
* @param untilTime Only submit logs from {@link LogDatabase} if they were created before this time. This is our way of making sure that the logs we submit
* only include the logs that we've already shown the user. It's possible some old logs may have been trimmed off in the meantime, but no
* new ones could pop up.
*/
public void submitLogWithPrefixLines(long untilTime, @NonNull List<LogLine> prefixLines, @Nullable byte[] trace, Callback<Optional<String>> callback) {
SignalExecutors.UNBOUNDED.execute(() -> callback.onResult(submitLogInternal(untilTime, prefixLines, trace)));
public void submitLogFromReader(DebugLogsViewer.LogReader logReader, @Nullable byte[] trace, Callback<Optional<String>> callback) {
SignalExecutors.UNBOUNDED.execute(() -> callback.onResult(submitLogFromReaderInternal(logReader, trace)));
}
public void writeLogToDisk(@NonNull Uri uri, long untilTime, Callback<Boolean> callback) {
@@ -168,6 +162,79 @@ public class SubmitDebugLogRepository {
});
}
@WorkerThread
private @NonNull Optional<String> submitLogFromReaderInternal(DebugLogsViewer.LogReader logReader, @Nullable byte[] trace) {
Stopwatch stopwatch = new Stopwatch("log-upload");
String traceUrl = null;
if (trace != null) {
try {
traceUrl = uploadContent("application/octet-stream", RequestBody.create(MediaType.get("application/octet-stream"), trace));
} catch (IOException e) {
Log.w(TAG, "Error during trace upload.", e);
return Optional.empty();
}
}
stopwatch.split("trace");
try {
ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
Future<Uri> futureUri = BlobProvider.getInstance()
.forData(new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), 0)
.withMimeType("application/gzip")
.createForSingleSessionOnDiskAsync(context);
OutputStream gzipOutput = new GZIPOutputStream(new ParcelFileDescriptor.AutoCloseOutputStream(fds[1]));
boolean traceFound = trace == null;
String next;
while ((next = logReader.nextChunk(10_000)) != null) {
if (!traceFound) {
int traceIndex = next.indexOf("<binary trace data>");
if (traceIndex != -1) {
next = next.replace("<binary trace data>", traceUrl);
traceFound = true;
}
}
gzipOutput.write(next.getBytes());
gzipOutput.write("\n".getBytes());
}
StreamUtil.close(gzipOutput);
Uri gzipUri = futureUri.get();
stopwatch.split("body");
String logUrl = uploadContent("application/gzip", new RequestBody() {
@Override
public @NonNull MediaType contentType() {
return MediaType.get("application/gzip");
}
@Override public long contentLength() {
return BlobProvider.getInstance().calculateFileSize(context, gzipUri);
}
@Override
public void writeTo(@NonNull BufferedSink sink) throws IOException {
Source source = Okio.source(BlobProvider.getInstance().getStream(context, gzipUri));
sink.writeAll(source);
}
});
stopwatch.split("upload");
stopwatch.stop(TAG);
BlobProvider.getInstance().delete(context, gzipUri);
return Optional.of(logUrl);
} catch (IOException | RuntimeException | ExecutionException | InterruptedException e) {
Log.w(TAG, "Error during log upload.", e);
return Optional.empty();
}
}
@WorkerThread
private @NonNull Optional<String> submitLogInternal(long untilTime, @NonNull List<LogLine> prefixLines, @Nullable byte[] trace) {
String traceUrl = null;

View File

@@ -10,10 +10,9 @@ import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.signal.core.util.Stopwatch;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.signal.core.util.tracing.Tracer;
import org.signal.debuglogsviewer.DebugLogsViewer;
import org.thoughtcrime.securesms.database.LogDatabase;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
@@ -120,17 +119,14 @@ public class SubmitDebugLogViewModel extends ViewModel {
return mode;
}
@NonNull LiveData<Optional<String>> onSubmitClicked() {
@NonNull LiveData<Optional<String>> onSubmitClicked(DebugLogsViewer.LogReader logReader) {
mode.postValue(Mode.SUBMITTING);
MutableLiveData<Optional<String>> result = new MutableLiveData<>();
// Get prefix lines for submission - this is a quick operation so it's ok to do it here
repo.getPrefixLogLines(prefixLines -> {
repo.submitLogWithPrefixLines(firstViewTime, prefixLines, trace, value -> {
mode.postValue(Mode.NORMAL);
result.postValue(value);
});
repo.submitLogFromReader(logReader, trace, value -> {
mode.postValue(Mode.NORMAL);
result.postValue(value);
});
return result;