Skip to content

Commit 7c8a416

Browse files
authored
Merge branch 'main' into markushi/chore/speedup-agp-test-matrix
2 parents 32e0a23 + e2b7f44 commit 7c8a416

File tree

48 files changed

+793
-172
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+793
-172
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
- Session Replay: Use main thread looper to schedule replay capture ([#4542](https://github.com/getsentry/sentry-java/pull/4542))
88
- Use single `LifecycleObserver` and multi-cast it to the integrations interested in lifecycle states ([#4567](https://github.com/getsentry/sentry-java/pull/4567))
9+
- Add `sentry.origin` attribute to logs ([#4618](https://github.com/getsentry/sentry-java/pull/4618))
10+
- This helps identify which integration captured a log event
11+
- Prewarm `SentryExecutorService` for better performance at runtime ([#4606](https://github.com/getsentry/sentry-java/pull/4606))
912

1013
### Fixes
1114

@@ -22,6 +25,9 @@
2225
}
2326
```
2427
- Fix abstract method error in `SentrySupportSQLiteDatabase` ([#4597](https://github.com/getsentry/sentry-java/pull/4597))
28+
- Ensure frame metrics listeners are registered/unregistered on the main thread ([#4582](https://github.com/getsentry/sentry-java/pull/4582))
29+
- Do not report cached events as lost ([#4575](https://github.com/getsentry/sentry-java/pull/4575))
30+
- Previously events were recorded as lost early despite being retried later through the cache
2531

2632
## 8.18.0
2733

sentry-android-core/api/sentry-android-core.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,7 @@ public final class io/sentry/android/core/cache/AndroidEnvelopeCache : io/sentry
483483
public static fun hasStartupCrashMarker (Lio/sentry/SentryOptions;)Z
484484
public static fun lastReportedAnr (Lio/sentry/SentryOptions;)Ljava/lang/Long;
485485
public fun store (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)V
486+
public fun storeEnvelope (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)Z
486487
}
487488

488489
public class io/sentry/android/core/performance/ActivityLifecycleCallbacksAdapter : android/app/Application$ActivityLifecycleCallbacks {

sentry-android-core/src/main/java/io/sentry/android/core/ContextUtils.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import android.content.pm.PackageInfo;
1616
import android.content.pm.PackageManager;
1717
import android.os.Build;
18+
import android.os.Handler;
1819
import android.util.DisplayMetrics;
1920
import io.sentry.ILogger;
2021
import io.sentry.SentryLevel;
@@ -455,8 +456,10 @@ public static boolean appIsLibraryForComposePreview(final @NotNull Context conte
455456
final @NotNull Context context,
456457
final @NotNull SentryOptions options,
457458
final @Nullable BroadcastReceiver receiver,
458-
final @NotNull IntentFilter filter) {
459-
return registerReceiver(context, new BuildInfoProvider(options.getLogger()), receiver, filter);
459+
final @NotNull IntentFilter filter,
460+
final @Nullable Handler handler) {
461+
return registerReceiver(
462+
context, new BuildInfoProvider(options.getLogger()), receiver, filter, handler);
460463
}
461464

462465
/** Register an exported BroadcastReceiver, independently from platform version. */
@@ -465,15 +468,17 @@ public static boolean appIsLibraryForComposePreview(final @NotNull Context conte
465468
final @NotNull Context context,
466469
final @NotNull BuildInfoProvider buildInfoProvider,
467470
final @Nullable BroadcastReceiver receiver,
468-
final @NotNull IntentFilter filter) {
471+
final @NotNull IntentFilter filter,
472+
final @Nullable Handler handler) {
469473
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.TIRAMISU) {
470474
// From https://developer.android.com/guide/components/broadcasts#context-registered-receivers
471475
// If this receiver is listening for broadcasts sent from the system or from other apps, even
472476
// other apps that you own—use the RECEIVER_EXPORTED flag. If instead this receiver is
473477
// listening only for broadcasts sent by your app, use the RECEIVER_NOT_EXPORTED flag.
474-
return context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED);
478+
return context.registerReceiver(
479+
receiver, filter, null, handler, Context.RECEIVER_NOT_EXPORTED);
475480
} else {
476-
return context.registerReceiver(receiver, filter);
481+
return context.registerReceiver(receiver, filter, null, handler);
477482
}
478483
}
479484

sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ private Date getBootTime() {
275275
@Nullable
276276
private Intent getBatteryIntent() {
277277
return ContextUtils.registerReceiver(
278-
context, buildInfoProvider, null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
278+
context, buildInfoProvider, null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED), null);
279279
}
280280

281281
/**

sentry-android-core/src/main/java/io/sentry/android/core/LifecycleWatcher.java

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import io.sentry.transport.CurrentDateProvider;
99
import io.sentry.transport.ICurrentDateProvider;
1010
import io.sentry.util.AutoClosableReentrantLock;
11+
import io.sentry.util.LazyEvaluator;
1112
import java.util.Timer;
1213
import java.util.TimerTask;
1314
import java.util.concurrent.atomic.AtomicLong;
@@ -22,7 +23,7 @@ final class LifecycleWatcher implements AppState.AppStateListener {
2223
private final long sessionIntervalMillis;
2324

2425
private @Nullable TimerTask timerTask;
25-
private final @NotNull Timer timer = new Timer(true);
26+
private final @NotNull LazyEvaluator<Timer> timer = new LazyEvaluator<>(() -> new Timer(true));
2627
private final @NotNull AutoClosableReentrantLock timerLock = new AutoClosableReentrantLock();
2728
private final @NotNull IScopes scopes;
2829
private final boolean enableSessionTracking;
@@ -105,21 +106,19 @@ public void onBackground() {
105106
private void scheduleEndSession() {
106107
try (final @NotNull ISentryLifecycleToken ignored = timerLock.acquire()) {
107108
cancelTask();
108-
if (timer != null) {
109-
timerTask =
110-
new TimerTask() {
111-
@Override
112-
public void run() {
113-
if (enableSessionTracking) {
114-
scopes.endSession();
115-
}
116-
scopes.getOptions().getReplayController().stop();
117-
scopes.getOptions().getContinuousProfiler().close(false);
109+
timerTask =
110+
new TimerTask() {
111+
@Override
112+
public void run() {
113+
if (enableSessionTracking) {
114+
scopes.endSession();
118115
}
119-
};
116+
scopes.getOptions().getReplayController().stop();
117+
scopes.getOptions().getContinuousProfiler().close(false);
118+
}
119+
};
120120

121-
timer.schedule(timerTask, sessionIntervalMillis);
122-
}
121+
timer.getValue().schedule(timerTask, sessionIntervalMillis);
123122
}
124123
}
125124

@@ -152,6 +151,6 @@ TimerTask getTimerTask() {
152151
@TestOnly
153152
@NotNull
154153
Timer getTimer() {
155-
return timer;
154+
return timer.getValue();
156155
}
157156
}

sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.sentry.Sentry;
77
import io.sentry.SentryLevel;
88
import io.sentry.SentryLogLevel;
9+
import io.sentry.logger.SentryLogParameters;
910
import org.jetbrains.annotations.ApiStatus;
1011
import org.jetbrains.annotations.NotNull;
1112
import org.jetbrains.annotations.Nullable;
@@ -56,10 +57,13 @@ private static void addAsLog(
5657
return;
5758
}
5859
final @Nullable String trMessage = tr != null ? tr.getMessage() : null;
60+
final @NotNull SentryLogParameters params = new SentryLogParameters();
61+
params.setOrigin("auto.log.logcat");
62+
5963
if (tr == null || trMessage == null) {
60-
scopes.logger().log(level, msg);
64+
scopes.logger().log(level, params, msg);
6165
} else {
62-
scopes.logger().log(level, msg != null ? (msg + "\n" + trMessage) : trMessage);
66+
scopes.logger().log(level, params, msg != null ? (msg + "\n" + trMessage) : trMessage);
6367
}
6468
}
6569

sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
import android.content.Intent;
2626
import android.content.IntentFilter;
2727
import android.os.Bundle;
28+
import android.os.Handler;
29+
import android.os.HandlerThread;
30+
import android.os.Process;
2831
import io.sentry.Breadcrumb;
2932
import io.sentry.Hint;
3033
import io.sentry.IScopes;
@@ -63,6 +66,7 @@ public final class SystemEventsBreadcrumbsIntegration
6366
private volatile boolean isClosed = false;
6467
private volatile boolean isStopped = false;
6568
private volatile IntentFilter filter = null;
69+
private volatile HandlerThread handlerThread = null;
6670
private final @NotNull AtomicBoolean isReceiverRegistered = new AtomicBoolean(false);
6771
private final @NotNull AutoClosableReentrantLock receiverLock = new AutoClosableReentrantLock();
6872
// Track previous battery state to avoid duplicate breadcrumbs when values haven't changed
@@ -138,10 +142,19 @@ private void registerReceiver(
138142
filter.addAction(item);
139143
}
140144
}
145+
if (handlerThread == null) {
146+
handlerThread =
147+
new HandlerThread(
148+
"SystemEventsReceiver", Process.THREAD_PRIORITY_BACKGROUND);
149+
handlerThread.start();
150+
}
141151
try {
142152
// registerReceiver can throw SecurityException but it's not documented in the
143153
// official docs
144-
ContextUtils.registerReceiver(context, options, receiver, filter);
154+
155+
// onReceive will be called on this handler thread
156+
final @NotNull Handler handler = new Handler(handlerThread.getLooper());
157+
ContextUtils.registerReceiver(context, options, receiver, filter, handler);
145158
if (!isReceiverRegistered.getAndSet(true)) {
146159
options
147160
.getLogger()
@@ -195,6 +208,10 @@ public void close() throws IOException {
195208
try (final @NotNull ISentryLifecycleToken ignored = receiverLock.acquire()) {
196209
isClosed = true;
197210
filter = null;
211+
if (handlerThread != null) {
212+
handlerThread.quit();
213+
}
214+
handlerThread = null;
198215
}
199216

200217
AppState.getInstance().removeAppStateListener(this);
@@ -293,25 +310,15 @@ public void onReceive(final Context context, final @NotNull Intent intent) {
293310

294311
final BatteryState state = batteryState;
295312
final long now = System.currentTimeMillis();
296-
try {
297-
options
298-
.getExecutorService()
299-
.submit(
300-
() -> {
301-
final Breadcrumb breadcrumb = createBreadcrumb(now, intent, action, state);
302-
final Hint hint = new Hint();
303-
hint.set(ANDROID_INTENT, intent);
304-
scopes.addBreadcrumb(breadcrumb, hint);
305-
});
306-
} catch (Throwable t) {
307-
// ignored
308-
}
313+
final Breadcrumb breadcrumb = createBreadcrumb(now, intent, action, state);
314+
final Hint hint = new Hint();
315+
hint.set(ANDROID_INTENT, intent);
316+
scopes.addBreadcrumb(breadcrumb, hint);
309317
}
310318

311319
// in theory this should be ThreadLocal, but we won't have more than 1 thread accessing it,
312320
// so we save some memory here and CPU cycles. 64 is because all intent actions we subscribe for
313321
// are less than 64 chars. We also don't care about encoding as those are always UTF.
314-
// TODO: _MULTI_THREADED_EXECUTOR_
315322
private final char[] buf = new char[64];
316323

317324
@TestOnly
@@ -365,8 +372,8 @@ String getStringAfterDotFast(final @Nullable String str) {
365372
}
366373
} else {
367374
final Bundle extras = intent.getExtras();
368-
final Map<String, String> newExtras = new HashMap<>();
369375
if (extras != null && !extras.isEmpty()) {
376+
final Map<String, String> newExtras = new HashMap<>(extras.size());
370377
for (String item : extras.keySet()) {
371378
try {
372379
@SuppressWarnings("deprecation")

sentry-android-core/src/main/java/io/sentry/android/core/cache/AndroidEnvelopeCache.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,19 @@ public AndroidEnvelopeCache(final @NotNull SentryAndroidOptions options) {
4747
this.currentDateProvider = currentDateProvider;
4848
}
4949

50+
@SuppressWarnings("deprecation")
5051
@Override
5152
public void store(@NotNull SentryEnvelope envelope, @NotNull Hint hint) {
52-
super.store(envelope, hint);
53+
storeInternalAndroid(envelope, hint);
54+
}
55+
56+
@Override
57+
public boolean storeEnvelope(@NotNull SentryEnvelope envelope, @NotNull Hint hint) {
58+
return storeInternalAndroid(envelope, hint);
59+
}
60+
61+
private boolean storeInternalAndroid(@NotNull SentryEnvelope envelope, @NotNull Hint hint) {
62+
final boolean didStore = super.storeEnvelope(envelope, hint);
5363

5464
final SentryAndroidOptions options = (SentryAndroidOptions) this.options;
5565
final TimeSpan sdkInitTimeSpan = AppStartMetrics.getInstance().getSdkInitTimeSpan();
@@ -83,6 +93,7 @@ public void store(@NotNull SentryEnvelope envelope, @NotNull Hint hint) {
8393

8494
writeLastReportedAnrMarker(timestamp);
8595
});
96+
return didStore;
8697
}
8798

8899
@TestOnly

sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public final class SentryFrameMetricsCollector implements Application.ActivityLi
3838

3939
private final @NotNull BuildInfoProvider buildInfoProvider;
4040
private final @NotNull Set<Window> trackedWindows = new CopyOnWriteArraySet<>();
41+
4142
private final @NotNull ILogger logger;
4243
private @Nullable Handler handler;
4344
private @Nullable WeakReference<Window> currentWindow;
@@ -282,17 +283,20 @@ public void stopCollection(final @Nullable String listenerId) {
282283

283284
@SuppressLint("NewApi")
284285
private void stopTrackingWindow(final @NotNull Window window) {
285-
if (trackedWindows.contains(window)) {
286-
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.N) {
287-
try {
288-
windowFrameMetricsManager.removeOnFrameMetricsAvailableListener(
289-
window, frameMetricsAvailableListener);
290-
} catch (Exception e) {
291-
logger.log(SentryLevel.ERROR, "Failed to remove frameMetricsAvailableListener", e);
292-
}
293-
}
294-
trackedWindows.remove(window);
295-
}
286+
new Handler(Looper.getMainLooper())
287+
.post(
288+
() -> {
289+
try {
290+
// Re-check if we should still remove the listener for this window
291+
// in case trackCurrentWindow was called in the meantime
292+
if (trackedWindows.remove(window)) {
293+
windowFrameMetricsManager.removeOnFrameMetricsAvailableListener(
294+
window, frameMetricsAvailableListener);
295+
}
296+
} catch (Throwable e) {
297+
logger.log(SentryLevel.ERROR, "Failed to remove frameMetricsAvailableListener", e);
298+
}
299+
});
296300
}
297301

298302
private void setCurrentWindow(final @NotNull Window window) {
@@ -305,18 +309,29 @@ private void setCurrentWindow(final @NotNull Window window) {
305309

306310
@SuppressLint("NewApi")
307311
private void trackCurrentWindow() {
308-
Window window = currentWindow != null ? currentWindow.get() : null;
312+
@Nullable Window window = currentWindow != null ? currentWindow.get() : null;
309313
if (window == null || !isAvailable) {
310314
return;
311315
}
312316

313-
if (!trackedWindows.contains(window) && !listenerMap.isEmpty()) {
317+
if (listenerMap.isEmpty()) {
318+
return;
319+
}
314320

315-
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.N && handler != null) {
316-
trackedWindows.add(window);
317-
windowFrameMetricsManager.addOnFrameMetricsAvailableListener(
318-
window, frameMetricsAvailableListener, handler);
319-
}
321+
if (handler != null) {
322+
// Ensure the addOnFrameMetricsAvailableListener is called on the main thread
323+
new Handler(Looper.getMainLooper())
324+
.post(
325+
() -> {
326+
if (trackedWindows.add(window)) {
327+
try {
328+
windowFrameMetricsManager.addOnFrameMetricsAvailableListener(
329+
window, frameMetricsAvailableListener, handler);
330+
} catch (Throwable e) {
331+
logger.log(SentryLevel.ERROR, "Failed to add frameMetricsAvailableListener", e);
332+
}
333+
}
334+
});
320335
}
321336
}
322337

@@ -373,13 +388,19 @@ default void addOnFrameMetricsAvailableListener(
373388
final @NotNull Window window,
374389
final @Nullable Window.OnFrameMetricsAvailableListener frameMetricsAvailableListener,
375390
final @Nullable Handler handler) {
391+
if (frameMetricsAvailableListener == null) {
392+
return;
393+
}
376394
window.addOnFrameMetricsAvailableListener(frameMetricsAvailableListener, handler);
377395
}
378396

379397
@RequiresApi(api = Build.VERSION_CODES.N)
380398
default void removeOnFrameMetricsAvailableListener(
381399
final @NotNull Window window,
382400
final @Nullable Window.OnFrameMetricsAvailableListener frameMetricsAvailableListener) {
401+
if (frameMetricsAvailableListener == null) {
402+
return;
403+
}
383404
window.removeOnFrameMetricsAvailableListener(frameMetricsAvailableListener);
384405
}
385406
}

sentry-android-core/src/test/java/io/sentry/android/core/AndroidProfilerTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ class AndroidProfilerTest {
8080
override fun close(timeoutMillis: Long) {}
8181

8282
override fun isClosed() = false
83+
84+
override fun prewarm() = Unit
8385
}
8486

8587
val options =

0 commit comments

Comments
 (0)