Upload trace file as separate debuglogs item.

This commit is contained in:
Greyson Parrelli
2020-11-19 10:37:34 -05:00
committed by Alex Hart
parent 39f1aea8e3
commit 7bb1262571
7 changed files with 89 additions and 45 deletions

View File

@@ -29,4 +29,9 @@ public class CompleteLogLine implements LogLine {
public @NonNull Style getStyle() { public @NonNull Style getStyle() {
return line.getStyle(); return line.getStyle();
} }
@Override
public @NonNull Placeholder getPlaceholderType() {
return line.getPlaceholderType();
}
} }

View File

@@ -12,15 +12,13 @@ public interface LogLine {
long getId(); long getId();
@NonNull String getText(); @NonNull String getText();
@NonNull Style getStyle(); @NonNull Style getStyle();
@NonNull Placeholder getPlaceholderType();
static List<LogLine> fromText(@NonNull CharSequence text) {
return Stream.of(Pattern.compile("\\n").split(text))
.map(s -> new SimpleLogLine(s, Style.NONE))
.map(line -> (LogLine) line)
.toList();
}
enum Style { enum Style {
NONE, VERBOSE, DEBUG, INFO, WARNING, ERROR NONE, VERBOSE, DEBUG, INFO, WARNING, ERROR
} }
enum Placeholder {
NONE, TRACE
}
} }

View File

@@ -20,19 +20,6 @@ public class LogSectionTrace implements LogSection {
@Override @Override
public @NonNull CharSequence getContent(@NonNull Context context) { public @NonNull CharSequence getContent(@NonNull Context context) {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); return LogStyleParser.TRACE_PLACEHOLDER;
GZIPOutputStream compressedStream = new GZIPOutputStream(outputStream))
{
compressedStream.write(Tracer.getInstance().serialize());
compressedStream.flush();
compressedStream.close();
outputStream.flush();
outputStream.close();
return Base64.encodeBytes(outputStream.toByteArray());
} catch (IOException e) {
return "";
}
} }
} }

View File

@@ -7,6 +7,8 @@ import java.util.Map;
public class LogStyleParser { public class LogStyleParser {
public static final String TRACE_PLACEHOLDER = "<binary trace data>";
private static final Map<String, LogLine.Style> STYLE_MARKERS = new HashMap<String, LogLine.Style>() {{ private static final Map<String, LogLine.Style> STYLE_MARKERS = new HashMap<String, LogLine.Style>() {{
put(" V ", LogLine.Style.VERBOSE); put(" V ", LogLine.Style.VERBOSE);
put(" D ", LogLine.Style.DEBUG); put(" D ", LogLine.Style.DEBUG);
@@ -15,7 +17,7 @@ public class LogStyleParser {
put(" E ", LogLine.Style.ERROR); put(" E ", LogLine.Style.ERROR);
}}; }};
public static LogLine.Style parseStyle(@NonNull String text) { public static @NonNull LogLine.Style parseStyle(@NonNull String text) {
for (Map.Entry<String, LogLine.Style> entry : STYLE_MARKERS.entrySet()) { for (Map.Entry<String, LogLine.Style> entry : STYLE_MARKERS.entrySet()) {
if (text.contains(entry.getKey())) { if (text.contains(entry.getKey())) {
return entry.getValue(); return entry.getValue();
@@ -23,4 +25,12 @@ public class LogStyleParser {
} }
return LogLine.Style.NONE; return LogLine.Style.NONE;
} }
public static @NonNull LogLine.Placeholder parsePlaceholderType(@NonNull String text) {
if (text.equals(TRACE_PLACEHOLDER)) {
return LogLine.Placeholder.TRACE;
} else {
return LogLine.Placeholder.NONE;
}
}
} }

View File

@@ -7,14 +7,16 @@ import androidx.annotation.NonNull;
*/ */
class SimpleLogLine implements LogLine { class SimpleLogLine implements LogLine {
static final SimpleLogLine EMPTY = new SimpleLogLine("", Style.NONE); static final SimpleLogLine EMPTY = new SimpleLogLine("", Style.NONE, Placeholder.NONE);
private final String text; private final String text;
private final Style style; private final Style style;
private final Placeholder placeholder;
SimpleLogLine(@NonNull String text, @NonNull Style style) { SimpleLogLine(@NonNull String text, @NonNull Style style, @NonNull Placeholder placeholder) {
this.text = text; this.text = text;
this.style = style; this.style = style;
this.placeholder = placeholder;
} }
@Override @Override
@@ -22,11 +24,18 @@ class SimpleLogLine implements LogLine {
return -1; return -1;
} }
@Override
public @NonNull String getText() { public @NonNull String getText() {
return text; return text;
} }
@Override
public @NonNull Style getStyle() { public @NonNull Style getStyle() {
return style; return style;
} }
@Override
public @NonNull Placeholder getPlaceholderType() {
return placeholder;
}
} }

View File

@@ -4,6 +4,7 @@ import android.content.Context;
import android.os.Build; import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
@@ -82,16 +83,48 @@ public class SubmitDebugLogRepository {
} }
public void submitLog(@NonNull List<LogLine> lines, Callback<Optional<String>> callback) { public void submitLog(@NonNull List<LogLine> lines, Callback<Optional<String>> callback) {
SignalExecutors.UNBOUNDED.execute(() -> callback.onResult(submitLogInternal(lines))); SignalExecutors.UNBOUNDED.execute(() -> callback.onResult(submitLogInternal(lines, null)));
}
public void submitLog(@NonNull List<LogLine> lines, @Nullable byte[] trace, Callback<Optional<String>> callback) {
SignalExecutors.UNBOUNDED.execute(() -> callback.onResult(submitLogInternal(lines, trace)));
} }
@WorkerThread @WorkerThread
private @NonNull Optional<String> submitLogInternal(@NonNull List<LogLine> lines) { private @NonNull Optional<String> submitLogInternal(@NonNull List<LogLine> lines, @Nullable byte[] trace) {
StringBuilder bodyBuilder = new StringBuilder(); String traceUrl = null;
for (LogLine line : lines) { if (trace != null) {
bodyBuilder.append(line.getText()).append('\n'); try {
traceUrl = uploadContent("application/octet-stream", trace);
} catch (IOException e) {
Log.w(TAG, "Error during trace upload.", e);
return Optional.absent();
}
} }
StringBuilder bodyBuilder = new StringBuilder();
for (LogLine line : lines) {
switch (line.getPlaceholderType()) {
case NONE:
bodyBuilder.append(line.getText()).append('\n');
break;
case TRACE:
bodyBuilder.append(traceUrl).append('\n');
break;
}
}
try {
String logUrl = uploadContent("text/plain", bodyBuilder.toString().getBytes());
return Optional.of(logUrl);
} catch (IOException e) {
Log.w(TAG, "Error during log upload.", e);
return Optional.absent();
}
}
@WorkerThread
private @NonNull String uploadContent(@NonNull String contentType, @NonNull byte[] content) throws IOException {
try { try {
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new StandardUserAgentInterceptor()).dns(SignalServiceNetworkAccess.DNS).build(); OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new StandardUserAgentInterceptor()).dns(SignalServiceNetworkAccess.DNS).build();
Response response = client.newCall(new Request.Builder().url(API_ENDPOINT).get().build()).execute(); Response response = client.newCall(new Request.Builder().url(API_ENDPOINT).get().build()).execute();
@@ -108,14 +141,14 @@ public class SubmitDebugLogRepository {
MultipartBody.Builder post = new MultipartBody.Builder(); MultipartBody.Builder post = new MultipartBody.Builder();
Iterator<String> keys = fields.keys(); Iterator<String> keys = fields.keys();
post.addFormDataPart("Content-Type", "text/plain"); post.addFormDataPart("Content-Type", contentType);
while (keys.hasNext()) { while (keys.hasNext()) {
String key = keys.next(); String key = keys.next();
post.addFormDataPart(key, fields.getString(key)); post.addFormDataPart(key, fields.getString(key));
} }
post.addFormDataPart("file", "file", RequestBody.create(MediaType.parse("text/plain"), bodyBuilder.toString())); post.addFormDataPart("file", "file", RequestBody.create(MediaType.parse(contentType), content));
Response postResponse = client.newCall(new Request.Builder().url(url).post(post.build()).build()).execute(); Response postResponse = client.newCall(new Request.Builder().url(url).post(post.build()).build()).execute();
@@ -123,10 +156,10 @@ public class SubmitDebugLogRepository {
throw new IOException("Bad response: " + postResponse); throw new IOException("Bad response: " + postResponse);
} }
return Optional.of(API_ENDPOINT + "/" + item); return API_ENDPOINT + "/" + item;
} catch (IOException | JSONException e) { } catch (JSONException e) {
Log.w(TAG, "Error during upload.", e); Log.w(TAG, "Error during upload.", e);
return Optional.absent(); throw new IOException(e);
} }
} }
@@ -166,12 +199,12 @@ public class SubmitDebugLogRepository {
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
List<LogLine> out = new ArrayList<>(); List<LogLine> out = new ArrayList<>();
out.add(new SimpleLogLine(formatTitle(section.getTitle(), maxTitleLength), LogLine.Style.NONE)); out.add(new SimpleLogLine(formatTitle(section.getTitle(), maxTitleLength), LogLine.Style.NONE, LogLine.Placeholder.NONE));
CharSequence content = Scrubber.scrub(section.getContent(context)); CharSequence content = Scrubber.scrub(section.getContent(context));
List<LogLine> lines = Stream.of(Pattern.compile("\\n").split(content)) List<LogLine> lines = Stream.of(Pattern.compile("\\n").split(content))
.map(s -> new SimpleLogLine(s, LogStyleParser.parseStyle(s))) .map(s -> new SimpleLogLine(s, LogStyleParser.parseStyle(s), LogStyleParser.parsePlaceholderType(s)))
.map(line -> (LogLine) line) .map(line -> (LogLine) line)
.toList(); .toList();

View File

@@ -10,6 +10,7 @@ import androidx.lifecycle.ViewModelProvider;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.tracing.Tracer;
import org.thoughtcrime.securesms.util.DefaultValueLiveData; import org.thoughtcrime.securesms.util.DefaultValueLiveData;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
@@ -23,12 +24,17 @@ public class SubmitDebugLogViewModel extends ViewModel {
private final MutableLiveData<Mode> mode; private final MutableLiveData<Mode> mode;
private List<LogLine> sourceLines; private List<LogLine> sourceLines;
private byte[] trace;
private SubmitDebugLogViewModel() { private SubmitDebugLogViewModel() {
this.repo = new SubmitDebugLogRepository(); this.repo = new SubmitDebugLogRepository();
this.lines = new DefaultValueLiveData<>(Collections.emptyList()); this.lines = new DefaultValueLiveData<>(Collections.emptyList());
this.mode = new MutableLiveData<>(); this.mode = new MutableLiveData<>();
if (Tracer.getInstance().isEnabled()) {
this.trace = Tracer.getInstance().serialize();
}
repo.getLogLines(result -> { repo.getLogLines(result -> {
sourceLines = result; sourceLines = result;
mode.postValue(Mode.NORMAL); mode.postValue(Mode.NORMAL);
@@ -40,10 +46,6 @@ public class SubmitDebugLogViewModel extends ViewModel {
return lines; return lines;
} }
boolean hasLines() {
return lines.getValue().size() > 0;
}
@NonNull LiveData<Mode> getMode() { @NonNull LiveData<Mode> getMode() {
return mode; return mode;
} }
@@ -53,7 +55,7 @@ public class SubmitDebugLogViewModel extends ViewModel {
MutableLiveData<Optional<String>> result = new MutableLiveData<>(); MutableLiveData<Optional<String>> result = new MutableLiveData<>();
repo.submitLog(lines.getValue(), value -> { repo.submitLog(lines.getValue(), trace, value -> {
mode.postValue(Mode.NORMAL); mode.postValue(Mode.NORMAL);
result.postValue(value); result.postValue(value);
}); });