Skip to content

Commit

Permalink
Merge 0f902df into 6f8b473
Browse files Browse the repository at this point in the history
  • Loading branch information
romtsn authored Apr 20, 2023
2 parents 6f8b473 + 0f902df commit f383e87
Show file tree
Hide file tree
Showing 29 changed files with 1,965 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,18 @@
import io.sentry.android.core.internal.util.CpuInfoUtils;
import io.sentry.cache.PersistingOptionsObserver;
import io.sentry.cache.PersistingScopeObserver;
import io.sentry.hints.AbnormalExit;
import io.sentry.hints.Backfillable;
import io.sentry.protocol.App;
import io.sentry.protocol.Contexts;
import io.sentry.protocol.DebugImage;
import io.sentry.protocol.DebugMeta;
import io.sentry.protocol.Device;
import io.sentry.protocol.Mechanism;
import io.sentry.protocol.OperatingSystem;
import io.sentry.protocol.Request;
import io.sentry.protocol.SdkVersion;
import io.sentry.protocol.SentryThread;
import io.sentry.protocol.User;
import io.sentry.util.HintUtils;
import java.util.ArrayList;
Expand Down Expand Up @@ -88,8 +91,7 @@ public AnrV2EventProcessor(
this.buildInfoProvider = buildInfoProvider;

final SentryStackTraceFactory sentryStackTraceFactory =
new SentryStackTraceFactory(
this.options.getInAppExcludes(), this.options.getInAppIncludes());
new SentryStackTraceFactory(this.options);

sentryExceptionFactory = new SentryExceptionFactory(sentryStackTraceFactory);
}
Expand All @@ -109,7 +111,7 @@ public AnrV2EventProcessor(
// we always set exception values, platform, os and device even if the ANR is not enrich-able
// even though the OS context may change in the meantime (OS update), we consider this an
// edge-case
setExceptions(event);
setExceptions(event, unwrappedHint);
setPlatform(event);
mergeOS(event);
setDevice(event);
Expand All @@ -125,7 +127,7 @@ public AnrV2EventProcessor(

backfillScope(event);

backfillOptions(event);
backfillOptions(event, unwrappedHint);

setStaticValues(event);

Expand Down Expand Up @@ -264,22 +266,26 @@ private void setRequest(final @NotNull SentryBaseEvent event) {
// endregion

// region options persisted values
private void backfillOptions(final @NotNull SentryEvent event) {
private void backfillOptions(final @NotNull SentryEvent event, final @NotNull Object hint) {
setRelease(event);
setEnvironment(event);
setDist(event);
setDebugMeta(event);
setSdk(event);
setApp(event);
setApp(event, hint);
setOptionsTags(event);
}

private void setApp(final @NotNull SentryBaseEvent event) {
private void setApp(final @NotNull SentryBaseEvent event, final @NotNull Object hint) {
App app = event.getContexts().getApp();
if (app == null) {
app = new App();
}
app.setAppName(ContextUtils.getApplicationName(context, options.getLogger()));
// TODO: not entirely correct, because we define background ANRs as not the ones of
// IMPORTANCE_FOREGROUND, but this doesn't mean the app was in foreground when an ANR happened
// but it's our best effort for now. We could serialize AppState in theory.
app.setInForeground(!isBackgroundAnr(hint));

final PackageInfo packageInfo =
ContextUtils.getPackageInfo(context, options.getLogger(), buildInfoProvider);
Expand Down Expand Up @@ -339,10 +345,12 @@ private void setDebugMeta(final @NotNull SentryBaseEvent event) {
final String proguardUuid =
PersistingOptionsObserver.read(options, PROGUARD_UUID_FILENAME, String.class);

final DebugImage debugImage = new DebugImage();
debugImage.setType(DebugImage.PROGUARD);
debugImage.setUuid(proguardUuid);
images.add(debugImage);
if (proguardUuid != null) {
final DebugImage debugImage = new DebugImage();
debugImage.setType(DebugImage.PROGUARD);
debugImage.setUuid(proguardUuid);
images.add(debugImage);
}
event.setDebugMeta(debugMeta);
}
}
Expand Down Expand Up @@ -411,10 +419,53 @@ private void setPlatform(final @NotNull SentryBaseEvent event) {
}
}

private void setExceptions(final @NotNull SentryEvent event) {
final Throwable throwable = event.getThrowableMechanism();
if (throwable != null) {
event.setExceptions(sentryExceptionFactory.getSentryExceptions(throwable));
@Nullable
private SentryThread findMainThread(final @Nullable List<SentryThread> threads) {
if (threads != null) {
for (SentryThread thread : threads) {
final String name = thread.getName();
if (name != null && name.equals("main")) {
return thread;
}
}
}
return null;
}

// by default we assume that the ANR is foreground, unless abnormalMechanism is "anr_background"
private boolean isBackgroundAnr(final @NotNull Object hint) {
if (hint instanceof AbnormalExit) {
final String abnormalMechanism = ((AbnormalExit) hint).mechanism();
if (abnormalMechanism == null) {
return false;
}

if (abnormalMechanism.equals("anr_foreground")) {
return false;
}

return abnormalMechanism.equals("anr_background");
}
return false;
}

private void setExceptions(final @NotNull SentryEvent event, final @NotNull Object hint) {
// AnrV2 threads contain a thread dump from the OS, so we just search for the main thread dump
// and make an exception out of its stacktrace
final SentryThread mainThread = findMainThread(event.getThreads());
if (mainThread != null) {
final Mechanism mechanism = new Mechanism();
mechanism.setType("ANRv2");

final boolean isBackgroundAnr = isBackgroundAnr(hint);
String message = "ANR";
if (isBackgroundAnr) {
message = "Background " + message;
}
final ApplicationNotResponding anr =
new ApplicationNotResponding(message, Thread.currentThread());
event.setExceptions(
sentryExceptionFactory.getSentryExceptionsFromThread(mainThread, mechanism, anr));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import android.app.ActivityManager;
import android.app.ApplicationExitInfo;
import android.content.Context;
import android.os.Looper;
import io.sentry.DateUtils;
import io.sentry.Hint;
import io.sentry.IHub;
Expand All @@ -14,20 +13,23 @@
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.android.core.cache.AndroidEnvelopeCache;
import io.sentry.android.core.internal.threaddump.Lines;
import io.sentry.android.core.internal.threaddump.ThreadDumpParser;
import io.sentry.cache.EnvelopeCache;
import io.sentry.cache.IEnvelopeCache;
import io.sentry.exception.ExceptionMechanismException;
import io.sentry.hints.AbnormalExit;
import io.sentry.hints.Backfillable;
import io.sentry.hints.BlockingFlushHint;
import io.sentry.protocol.Mechanism;
import io.sentry.protocol.SentryId;
import io.sentry.protocol.SentryThread;
import io.sentry.transport.CurrentDateProvider;
import io.sentry.transport.ICurrentDateProvider;
import io.sentry.util.HintUtils;
import io.sentry.util.Objects;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -221,7 +223,8 @@ private void reportAsSentryEvent(
final long anrTimestamp = exitInfo.getTimestamp();
final boolean isBackground =
exitInfo.getImportance() != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
final Throwable anrThrowable = buildAnrThrowable(exitInfo, isBackground);

final List<SentryThread> threads = parseThreadDump(exitInfo, isBackground);
final AnrV2Hint anrHint =
new AnrV2Hint(
options.getFlushTimeoutMillis(),
Expand All @@ -232,7 +235,8 @@ private void reportAsSentryEvent(

final Hint hint = HintUtils.createWithTypeCheckHint(anrHint);

final SentryEvent event = new SentryEvent(anrThrowable);
final SentryEvent event = new SentryEvent();
event.setThreads(threads);
event.setTimestamp(DateUtils.getDateTime(anrTimestamp));
event.setLevel(SentryLevel.FATAL);

Expand All @@ -251,20 +255,20 @@ private void reportAsSentryEvent(
}
}

private @NotNull Throwable buildAnrThrowable(
private @Nullable List<SentryThread> parseThreadDump(
final @NotNull ApplicationExitInfo exitInfo, final boolean isBackground) {
String message = "ANR";
if (isBackground) {
message = "Background " + message;
List<SentryThread> threads = null;
try (final BufferedReader reader =
new BufferedReader(new InputStreamReader(exitInfo.getTraceInputStream()))) {
final Lines lines = Lines.readLines(reader);

final ThreadDumpParser threadDumpParser = new ThreadDumpParser(options, isBackground);
threads = threadDumpParser.parse(lines);
} catch (Throwable e) {
options.getLogger().log(SentryLevel.WARNING, "Failed to parse ANR thread dump", e);
}

// TODO: here we should actually parse the trace file and extract the thread dump from there
// and then we could properly get the main thread stracktrace and construct a proper exception
final ApplicationNotResponding error =
new ApplicationNotResponding(message, Looper.getMainLooper().getThread());
final Mechanism mechanism = new Mechanism();
mechanism.setType("ANRv2");
return new ExceptionMechanismException(mechanism, error, error.getThread(), true);
return threads;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Adapted from https://cs.android.com/android/platform/superproject/+/master:development/tools/bugreport/src/com/android/bugreport/util/Line.java
*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.sentry.android.core.internal.threaddump;

import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

@ApiStatus.Internal
public final class Line {
public int lineno;
public @NotNull String text;

public Line(final int lineno, final @NotNull String text) {
this.lineno = lineno;
this.text = text;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Adapted from https://cs.android.com/android/platform/superproject/+/master:development/tools/bugreport/src/com/android/bugreport/util/Lines.java
*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.sentry.android.core.internal.threaddump;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* A stream of parsed lines. Can be rewound, and sub-regions cloned for recursive descent parsing.
*/
@ApiStatus.Internal
public final class Lines {
private final @NotNull ArrayList<? extends Line> mList;
private final int mMin;
private final int mMax;

/** The read position inside the list. */
public int pos;

/** Read the whole file into a Lines object. */
public static Lines readLines(final @NotNull File file) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
return Lines.readLines(reader);
}
}

/** Read the whole file into a Lines object. */
public static Lines readLines(final @NotNull BufferedReader in) throws IOException {
final ArrayList<Line> list = new ArrayList<>();

int lineno = 0;
String text;
while ((text = in.readLine()) != null) {
lineno++;
list.add(new Line(lineno, text));
}

return new Lines(list);
}

/** Construct with a list of lines. */
public Lines(final @NotNull ArrayList<? extends Line> list) {
this.mList = list;
mMin = 0;
mMax = mList.size();
}

/** If there are more lines to read within the current range. */
public boolean hasNext() {
return pos < mMax;
}

/**
* Return the next line, or null if there are no more lines to read. Also returns null in the
* error condition where pos is before the beginning.
*/
@Nullable
public Line next() {
if (pos >= mMin && pos < mMax) {
return this.mList.get(pos++);
} else {
return null;
}
}

/** Move the read position back by one line. */
public void rewind() {
pos--;
}
}
Loading

0 comments on commit f383e87

Please sign in to comment.