Skip to content

Commit 7a7d63f

Browse files
buenaflorclaude
andcommitted
feat(extend-app-start): Add IAppStartExtender bridge and SentryOptions wiring
Introduces the @ApiStatus.Internal IAppStartExtender contract (extendAppStart / finishAppStart / getExtendedAppStartSpan) and a NoOp default, wired into SentryOptions. This is the naming-stable core bridge for the app start extension API; it is inert (returns NoOpSpan / no-ops) until the Android implementation and public Sentry facade land later in the stack. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 0c118e9 commit 7a7d63f

6 files changed

Lines changed: 129 additions & 0 deletions

File tree

sentry/api/sentry.api

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,12 @@ public final class io/sentry/HubScopesWrapper : io/sentry/IHub {
796796
public fun withScope (Lio/sentry/ScopeCallback;)V
797797
}
798798

799+
public abstract interface class io/sentry/IAppStartExtender {
800+
public abstract fun extendAppStart ()V
801+
public abstract fun finishAppStart ()V
802+
public abstract fun getExtendedAppStartSpan ()Lio/sentry/ISpan;
803+
}
804+
799805
public abstract interface class io/sentry/IConnectionStatusProvider : java/io/Closeable {
800806
public abstract fun addConnectionStatusObserver (Lio/sentry/IConnectionStatusProvider$IConnectionStatusObserver;)Z
801807
public abstract fun getConnectionStatus ()Lio/sentry/IConnectionStatusProvider$ConnectionStatus;
@@ -1543,6 +1549,13 @@ public final class io/sentry/MonitorScheduleUnit : java/lang/Enum {
15431549
public static fun values ()[Lio/sentry/MonitorScheduleUnit;
15441550
}
15451551

1552+
public final class io/sentry/NoOpAppStartExtender : io/sentry/IAppStartExtender {
1553+
public fun extendAppStart ()V
1554+
public fun finishAppStart ()V
1555+
public fun getExtendedAppStartSpan ()Lio/sentry/ISpan;
1556+
public static fun getInstance ()Lio/sentry/NoOpAppStartExtender;
1557+
}
1558+
15461559
public final class io/sentry/NoOpCompositePerformanceCollector : io/sentry/CompositePerformanceCollector {
15471560
public fun close ()V
15481561
public static fun getInstance ()Lio/sentry/NoOpCompositePerformanceCollector;
@@ -3603,6 +3616,7 @@ public class io/sentry/SentryOptions {
36033616
public fun addScopeObserver (Lio/sentry/IScopeObserver;)V
36043617
public static fun empty ()Lio/sentry/SentryOptions;
36053618
public fun findPersistingScopeObserver ()Lio/sentry/cache/PersistingScopeObserver;
3619+
public fun getAppStartExtender ()Lio/sentry/IAppStartExtender;
36063620
public fun getBackpressureMonitor ()Lio/sentry/backpressure/IBackpressureMonitor;
36073621
public fun getBeforeBreadcrumb ()Lio/sentry/SentryOptions$BeforeBreadcrumbCallback;
36083622
public fun getBeforeEnvelopeCallback ()Lio/sentry/SentryOptions$BeforeEnvelopeCallback;
@@ -3749,6 +3763,7 @@ public class io/sentry/SentryOptions {
37493763
public fun isTraceSampling ()Z
37503764
public fun isTracingEnabled ()Z
37513765
public fun merge (Lio/sentry/ExternalOptions;)V
3766+
public fun setAppStartExtender (Lio/sentry/IAppStartExtender;)V
37523767
public fun setAttachServerName (Z)V
37533768
public fun setAttachStacktrace (Z)V
37543769
public fun setAttachThreads (Z)V
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.sentry;
2+
3+
import org.jetbrains.annotations.ApiStatus;
4+
import org.jetbrains.annotations.NotNull;
5+
6+
/**
7+
* Bridges the {@code Sentry.extendAppStart()} / {@code Sentry.finishAppStart()} / {@code
8+
* Sentry.getExtendedAppStartSpan()} static API to the Android implementation. The default
9+
* implementation ({@link NoOpAppStartExtender}) does nothing, so the API is a no-op on platforms
10+
* that don't provide an app start measurement.
11+
*/
12+
@ApiStatus.Internal
13+
public interface IAppStartExtender {
14+
15+
/**
16+
* Begins extending the app start. Intended to be called from {@code Application.onCreate} right
17+
* after SDK init. No-ops if the app start already finished, none is in progress, or it was
18+
* already extended (first call wins).
19+
*/
20+
void extendAppStart();
21+
22+
/**
23+
* Finishes the extended app start, allowing the app start transaction to complete. No-ops if the
24+
* app start was not extended or this was already called.
25+
*/
26+
void finishAppStart();
27+
28+
/**
29+
* Returns the active extended app start span to attach child spans to, or a no-op span when no
30+
* extension is active.
31+
*/
32+
@NotNull
33+
ISpan getExtendedAppStartSpan();
34+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.sentry;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
5+
public final class NoOpAppStartExtender implements IAppStartExtender {
6+
7+
private static final @NotNull NoOpAppStartExtender instance = new NoOpAppStartExtender();
8+
9+
private NoOpAppStartExtender() {}
10+
11+
public static @NotNull NoOpAppStartExtender getInstance() {
12+
return instance;
13+
}
14+
15+
@Override
16+
public void extendAppStart() {}
17+
18+
@Override
19+
public void finishAppStart() {}
20+
21+
@Override
22+
public @NotNull ISpan getExtendedAppStartSpan() {
23+
return NoOpSpan.getInstance();
24+
}
25+
}

sentry/src/main/java/io/sentry/SentryOptions.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,9 @@ public class SentryOptions {
529529
private @NotNull FullyDisplayedReporter fullyDisplayedReporter =
530530
FullyDisplayedReporter.getInstance();
531531

532+
/** Bridges the app start extension API to the Android implementation. */
533+
private @NotNull IAppStartExtender appStartExtender = NoOpAppStartExtender.getInstance();
534+
532535
private @NotNull IConnectionStatusProvider connectionStatusProvider =
533536
new NoOpConnectionStatusProvider();
534537

@@ -2653,6 +2656,21 @@ public void setFullyDisplayedReporter(
26532656
this.fullyDisplayedReporter = fullyDisplayedReporter;
26542657
}
26552658

2659+
/**
2660+
* Gets the app start extender, which bridges the app start extension API to its implementation.
2661+
*
2662+
* @return the app start extender.
2663+
*/
2664+
@ApiStatus.Internal
2665+
public @NotNull IAppStartExtender getAppStartExtender() {
2666+
return appStartExtender;
2667+
}
2668+
2669+
@ApiStatus.Internal
2670+
public void setAppStartExtender(final @NotNull IAppStartExtender appStartExtender) {
2671+
this.appStartExtender = appStartExtender;
2672+
}
2673+
26562674
/**
26572675
* Whether OPTIONS requests should be traced.
26582676
*
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.sentry
2+
3+
import kotlin.test.Test
4+
import kotlin.test.assertSame
5+
6+
class NoOpAppStartExtenderTest {
7+
private val extender = NoOpAppStartExtender.getInstance()
8+
9+
@Test fun `extendAppStart does not throw`() = extender.extendAppStart()
10+
11+
@Test fun `finishAppStart does not throw`() = extender.finishAppStart()
12+
13+
@Test
14+
fun `getExtendedAppStartSpan returns NoOpSpan`() {
15+
assertSame(NoOpSpan.getInstance(), extender.extendedAppStartSpan)
16+
}
17+
}

sentry/src/test/java/io/sentry/SentryOptionsTest.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,26 @@ class SentryOptionsTest {
645645
assertEquals(FullyDisplayedReporter.getInstance(), SentryOptions().fullyDisplayedReporter)
646646
}
647647

648+
@Test
649+
fun `when options are initialized, appStartExtender defaults to noop`() {
650+
assertEquals(NoOpAppStartExtender.getInstance(), SentryOptions().appStartExtender)
651+
}
652+
653+
@Test
654+
fun `when appStartExtender is set, its returned as well`() {
655+
val options = SentryOptions()
656+
val customExtender =
657+
object : IAppStartExtender {
658+
override fun extendAppStart() = Unit
659+
660+
override fun finishAppStart() = Unit
661+
662+
override fun getExtendedAppStartSpan(): ISpan = NoOpSpan.getInstance()
663+
}
664+
options.appStartExtender = customExtender
665+
assertEquals(customExtender, options.appStartExtender)
666+
}
667+
648668
@Test
649669
fun `when options are initialized, connectionStatusProvider is not null and default to noop`() {
650670
assertNotNull(SentryOptions().connectionStatusProvider)

0 commit comments

Comments
 (0)