Skip to content

Commit

Permalink
Merge 31a271a into adee765
Browse files Browse the repository at this point in the history
  • Loading branch information
romtsn committed Oct 14, 2022
2 parents adee765 + 31a271a commit 920f6da
Show file tree
Hide file tree
Showing 22 changed files with 748 additions and 15 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -11,6 +11,10 @@
- Ensure potential callback exceptions are caught #2123 ([#2291](https://github.com/getsentry/sentry-java/pull/2291))
- Remove verbose FrameMetricsAggregator failure logging ([#2293](https://github.com/getsentry/sentry-java/pull/2293))

### Features

- Report Startup Crashes ([#2277](https://github.com/getsentry/sentry-java/pull/2277))

## 6.5.0

### Fixes
Expand Down
12 changes: 12 additions & 0 deletions sentry-android-core/api/sentry-android-core.api
Expand Up @@ -45,9 +45,12 @@ public final class io/sentry/android/core/AppLifecycleIntegration : io/sentry/In

public final class io/sentry/android/core/AppStartState {
public fun getAppStartInterval ()Ljava/lang/Long;
public fun getAppStartMillis ()Ljava/lang/Long;
public fun getAppStartTime ()Ljava/util/Date;
public static fun getInstance ()Lio/sentry/android/core/AppStartState;
public fun isColdStart ()Ljava/lang/Boolean;
public fun reset ()V
public fun setAppStartMillis (J)V
}

public final class io/sentry/android/core/BuildConfig {
Expand Down Expand Up @@ -128,6 +131,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun getDebugImagesLoader ()Lio/sentry/android/core/IDebugImagesLoader;
public fun getProfilingTracesHz ()I
public fun getProfilingTracesIntervalMillis ()I
public fun getStartupCrashDurationThresholdMillis ()J
public fun isAnrEnabled ()Z
public fun isAnrReportInDebug ()Z
public fun isAttachScreenshot ()Z
Expand All @@ -137,6 +141,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun isEnableAppComponentBreadcrumbs ()Z
public fun isEnableAppLifecycleBreadcrumbs ()Z
public fun isEnableAutoActivityLifecycleTracing ()Z
public fun isEnableStartupCrashDetection ()Z
public fun isEnableSystemEventBreadcrumbs ()Z
public fun isEnableUserInteractionBreadcrumbs ()Z
public fun isEnableUserInteractionTracing ()Z
Expand All @@ -151,6 +156,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun setEnableAppComponentBreadcrumbs (Z)V
public fun setEnableAppLifecycleBreadcrumbs (Z)V
public fun setEnableAutoActivityLifecycleTracing (Z)V
public fun setEnableStartupCrashDetection (Z)V
public fun setEnableSystemEventBreadcrumbs (Z)V
public fun setEnableUserInteractionBreadcrumbs (Z)V
public fun setEnableUserInteractionTracing (Z)V
Expand Down Expand Up @@ -216,3 +222,9 @@ public final class io/sentry/android/core/UserInteractionIntegration : android/a
public fun register (Lio/sentry/IHub;Lio/sentry/SentryOptions;)V
}

public final class io/sentry/android/core/cache/AndroidEnvelopeCache : io/sentry/cache/EnvelopeCache {
public fun <init> (Lio/sentry/android/core/SentryAndroidOptions;)V
public static fun hasStartupCrashMarker (Lio/sentry/SentryOptions;)Z
public fun store (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)V
}

Expand Up @@ -8,10 +8,10 @@
import android.content.res.AssetManager;
import android.os.Build;
import io.sentry.ILogger;
import io.sentry.SendCachedEnvelopeFireAndForgetIntegration;
import io.sentry.SendFireAndForgetEnvelopeSender;
import io.sentry.SendFireAndForgetOutboxSender;
import io.sentry.SentryLevel;
import io.sentry.android.core.cache.AndroidEnvelopeCache;
import io.sentry.android.fragment.FragmentLifecycleIntegration;
import io.sentry.android.timber.SentryTimberIntegration;
import io.sentry.util.Objects;
Expand Down Expand Up @@ -132,6 +132,7 @@ static void init(

ManifestMetadataReader.applyMetadata(context, options, buildInfoProvider);
initializeCacheDirs(context, options);
options.setEnvelopeDiskCache(new AndroidEnvelopeCache(options));

final ActivityFramesTracker activityFramesTracker =
new ActivityFramesTracker(loadClass, options.getLogger());
Expand Down Expand Up @@ -164,9 +165,14 @@ private static void installDefaultIntegrations(
final boolean isFragmentAvailable,
final boolean isTimberAvailable) {

// read the startup crash marker here to avoid doing double-IO for the SendCachedEnvelope
// integrations below
final boolean hasStartupCrashMarker = AndroidEnvelopeCache.hasStartupCrashMarker(options);

options.addIntegration(
new SendCachedEnvelopeFireAndForgetIntegration(
new SendFireAndForgetEnvelopeSender(() -> options.getCacheDirPath())));
new SendCachedEnvelopeIntegration(
new SendFireAndForgetEnvelopeSender(() -> options.getCacheDirPath()),
hasStartupCrashMarker));

// Integrations are registered in the same order. NDK before adding Watch outbox,
// because sentry-native move files around and we don't want to watch that.
Expand All @@ -184,8 +190,9 @@ private static void installDefaultIntegrations(
// this should be executed after NdkIntegration because sentry-native move files on init.
// and we'd like to send them right away
options.addIntegration(
new SendCachedEnvelopeFireAndForgetIntegration(
new SendFireAndForgetOutboxSender(() -> options.getOutboxPath())));
new SendCachedEnvelopeIntegration(
new SendFireAndForgetOutboxSender(() -> options.getOutboxPath()),
hasStartupCrashMarker));

options.addIntegration(new AnrIntegration(context));
options.addIntegration(new AppLifecycleIntegration());
Expand Down
Expand Up @@ -84,6 +84,11 @@ public Date getAppStartTime() {
return appStartTime;
}

@Nullable
public Long getAppStartMillis() {
return appStartMillis;
}

synchronized void setAppStartTime(final long appStartMillis, final @NotNull Date appStartTime) {
// method is synchronized because the SDK may by init. on a background thread.
if (this.appStartTime != null && this.appStartMillis != null) {
Expand All @@ -92,4 +97,16 @@ synchronized void setAppStartTime(final long appStartMillis, final @NotNull Date
this.appStartTime = appStartTime;
this.appStartMillis = appStartMillis;
}

@TestOnly
public synchronized void setAppStartMillis(final long appStartMillis) {
this.appStartMillis = appStartMillis;
}

@TestOnly
public synchronized void reset() {
appStartTime = null;
appStartMillis = null;
appStartEndMillis = null;
}
}
Expand Up @@ -78,6 +78,8 @@ final class ManifestMetadataReader {
static final String CLIENT_REPORTS_ENABLE = "io.sentry.send-client-reports";
static final String COLLECT_ADDITIONAL_CONTEXT = "io.sentry.additional-context";

static final String STARTUP_CRASH_ENABLE = "io.sentry.startup-crashes.enable";

/** ManifestMetadataReader ctor */
private ManifestMetadataReader() {}

Expand Down Expand Up @@ -226,6 +228,10 @@ static void applyMetadata(
COLLECT_ADDITIONAL_CONTEXT,
options.isCollectAdditionalContext()));

options.setEnableStartupCrashDetection(
readBool(
metadata, logger, STARTUP_CRASH_ENABLE, options.isEnableStartupCrashDetection()));

if (options.getTracesSampleRate() == null) {
final Double tracesSampleRate = readDouble(metadata, logger, TRACES_SAMPLE_RATE);
if (tracesSampleRate != -1) {
Expand Down
@@ -0,0 +1,84 @@
package io.sentry.android.core;

import io.sentry.IHub;
import io.sentry.Integration;
import io.sentry.SendCachedEnvelopeFireAndForgetIntegration;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.util.Objects;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.jetbrains.annotations.NotNull;

final class SendCachedEnvelopeIntegration implements Integration {

private final @NotNull SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory
factory;
private final boolean hasStartupCrashMarker;

public SendCachedEnvelopeIntegration(
final @NotNull SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory factory,
final boolean hasStartupCrashMarker) {
this.factory = Objects.requireNonNull(factory, "SendFireAndForgetFactory is required");
this.hasStartupCrashMarker = hasStartupCrashMarker;
}

@Override
public void register(@NotNull IHub hub, @NotNull SentryOptions options) {
Objects.requireNonNull(hub, "Hub is required");
final SentryAndroidOptions androidOptions =
Objects.requireNonNull(
(options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null,
"SentryAndroidOptions is required");

final String cachedDir = options.getCacheDirPath();
if (!factory.hasValidPath(cachedDir, options.getLogger())) {
options.getLogger().log(SentryLevel.ERROR, "No cache dir path is defined in options.");
return;
}

final SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForget sender =
factory.create(hub, androidOptions);

if (sender == null) {
androidOptions.getLogger().log(SentryLevel.ERROR, "SendFireAndForget factory is null.");
return;
}

try {
Future<?> future =
androidOptions
.getExecutorService()
.submit(
() -> {
try {
sender.send();
} catch (Throwable e) {
androidOptions
.getLogger()
.log(SentryLevel.ERROR, "Failed trying to send cached events.", e);
}
});

if (hasStartupCrashMarker) {
androidOptions
.getLogger()
.log(SentryLevel.DEBUG, "Startup Crash marker exists, blocking flush.");
try {
future.get(androidOptions.getStartupCrashFlushTimeoutMillis(), TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
androidOptions
.getLogger()
.log(SentryLevel.DEBUG, "Synchronous send timed out, continuing in the background.");
}
}

androidOptions.getLogger().log(SentryLevel.DEBUG, "SendCachedEnvelopeIntegration installed.");
} catch (Throwable e) {
androidOptions
.getLogger()
.log(SentryLevel.ERROR, "Failed to call the executor. Cached events will not be sent", e);
}
}
}
Expand Up @@ -9,8 +9,10 @@
import io.sentry.Sentry;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.android.core.cache.AndroidEnvelopeCache;
import io.sentry.android.fragment.FragmentLifecycleIntegration;
import io.sentry.android.timber.SentryTimberIntegration;
import io.sentry.transport.NoOpEnvelopeCache;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Date;
Expand Down Expand Up @@ -104,6 +106,7 @@ public static synchronized void init(
options, context, logger, isFragmentAvailable, isTimberAvailable);
configuration.configure(options);
deduplicateIntegrations(options, isFragmentAvailable, isTimberAvailable);
resetEnvelopeCacheIfNeeded(options);
},
true);
} catch (IllegalAccessException e) {
Expand Down Expand Up @@ -168,4 +171,18 @@ private static void deduplicateIntegrations(
}
}
}

/**
* Resets envelope cache if {@link SentryOptions#getCacheDirPath()} was set to null by the user
* and the IEnvelopCache implementation remained ours (AndroidEnvelopeCache), which relies on
* cacheDirPath set.
*
* @param options SentryOptions to retrieve cacheDirPath from
*/
private static void resetEnvelopeCacheIfNeeded(final @NotNull SentryAndroidOptions options) {
if (options.getCacheDirPath() == null
&& options.getEnvelopeDiskCache() instanceof AndroidEnvelopeCache) {
options.setEnvelopeDiskCache(NoOpEnvelopeCache.getInstance());
}
}
}
Expand Up @@ -8,6 +8,7 @@
import io.sentry.protocol.SdkVersion;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;

/** Sentry SDK options for Android */
public final class SentryAndroidOptions extends SentryOptions {
Expand Down Expand Up @@ -107,6 +108,38 @@ public final class SentryAndroidOptions extends SentryOptions {
*/
private boolean collectAdditionalContext = true;

/**
* Controls how many seconds to wait for sending events in case there were Startup Crashes in the
* previous run. Sentry SDKs normally send events from a background queue, but in the case of
* Startup Crashes, it blocks the execution of the {@link Sentry#init()} function for the amount
* of startupCrashFlushTimeoutMillis to make sure the events make it to Sentry.
*
* <p>When the timeout is reached, the execution will continue on background.
*
* <p>Default is 5000 = 5s.
*/
private long startupCrashFlushTimeoutMillis = 5000; // 5s

/**
* Controls the threshold after the application startup time, within which a crash should happen
* to be considered a Startup Crash.
*
* <p>Startup Crashes are sent on {@link Sentry#init()} in a blocking way, controlled by {@link
* SentryAndroidOptions#startupCrashFlushTimeoutMillis}.
*
* <p>Default is 2000 = 2s.
*/
private final long startupCrashDurationThresholdMillis = 2000; // 2s

/**
* Controls whether the SDK should detect Startup Crashes.
*
* <p>Startup Crashes are sent on {@link Sentry#init()} in a blocking way.
*
* <p>Default is true
*/
private boolean enableStartupCrashDetection = true;

public SentryAndroidOptions() {
setSentryClientName(BuildConfig.SENTRY_ANDROID_SDK_NAME + "/" + BuildConfig.VERSION_NAME);
setSdkVersion(createSdkVersion());
Expand Down Expand Up @@ -332,4 +365,42 @@ public boolean isCollectAdditionalContext() {
public void setCollectAdditionalContext(boolean collectAdditionalContext) {
this.collectAdditionalContext = collectAdditionalContext;
}

/**
* Returns the Startup Crash flush timeout in Millis
*
* @return the timeout in Millis
*/
@ApiStatus.Internal
long getStartupCrashFlushTimeoutMillis() {
return startupCrashFlushTimeoutMillis;
}

/**
* Sets the Startup Crash flush timeout in Millis
*
* @param startupCrashFlushTimeoutMillis the timeout in Millis
*/
@TestOnly
void setStartupCrashFlushTimeoutMillis(long startupCrashFlushTimeoutMillis) {
this.startupCrashFlushTimeoutMillis = startupCrashFlushTimeoutMillis;
}

/**
* Returns the Startup Crash duration threshold in Millis
*
* @return the threshold in Millis
*/
@ApiStatus.Internal
public long getStartupCrashDurationThresholdMillis() {
return startupCrashDurationThresholdMillis;
}

public boolean isEnableStartupCrashDetection() {
return enableStartupCrashDetection;
}

public void setEnableStartupCrashDetection(boolean enableStartupCrashDetection) {
this.enableStartupCrashDetection = enableStartupCrashDetection;
}
}

0 comments on commit 920f6da

Please sign in to comment.