Skip to content

Conversation

@denrase
Copy link
Collaborator

@denrase denrase commented Nov 26, 2025

📜 Description

  • Call captureLogs on batcher if app terminates
  • Call captureLogs on batcher if app resigns active

Introduces listeners on the AppState and observes it in a conditional (UIKit) integration.

💡 Motivation and Context

Implements missing behaviour from batch processor spec.

When the application shuts down gracefully, the BatchProcessor SHOULD forward all data in memory to the transport. The transport SHOULD keep its existing behavior, which usually stores the data to disk as an envelope. It is not required to call a transport flush. This is mostly relevant for mobile SDKs already subscribed to these hooks, such as applicationWillTerminate on iOS.

When the application moves to the background, the BatchProcessor SHOULD forward all the data in memory to the transport and stop the timer. The transport SHOULD keep its existing behavior, which usually stores the data to disk as an envelope. It is not required to call the transport flush. This is mostly relevant for mobile SDKs.

Closes #6478

💚 How did you test it?

Added tests and tested manually.

📝 Checklist

You have to check all boxes before merging:

  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.

@denrase denrase changed the title Flush Logs on Terminate or Resign Active State Flush Logs on WillTerminate or WillResignActive` App State Nov 26, 2025
@denrase denrase changed the title Flush Logs on WillTerminate or WillResignActive` App State Flush Logs on WillTerminate or WillResignActive App State Nov 26, 2025
@codecov
Copy link

codecov bot commented Nov 26, 2025

Codecov Report

❌ Patch coverage is 96.61017% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.077%. Comparing base (929c622) to head (7a57e7a).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
.../Swift/Integrations/Log/FlushLogsIntegration.swift 95.000% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@              Coverage Diff              @@
##              main     #6909       +/-   ##
=============================================
+ Coverage   85.069%   85.077%   +0.008%     
=============================================
  Files          453       454        +1     
  Lines        27681     27730       +49     
  Branches     12166     12178       +12     
=============================================
+ Hits         23548     23592       +44     
- Misses        3874      4094      +220     
+ Partials       259        44      -215     
Files with missing lines Coverage Δ
SentryTestUtils/Sources/TestClient.swift 85.321% <100.000%> (+0.415%) ⬆️
Sources/Sentry/SentryClient.m 97.623% <100.000%> (+0.206%) ⬆️
Sources/Swift/AppState/SentryAppState.swift 95.945% <ø> (ø)
Sources/Swift/AppState/SentryAppStateManager.swift 100.000% <ø> (ø)
...egrations/FramesTracking/SentryFramesTracker.swift 100.000% <ø> (ø)
Sources/Swift/Core/Integrations/Integrations.swift 100.000% <100.000%> (ø)
...es/Swift/Integrations/Session/SessionTracker.swift 100.000% <100.000%> (ø)
Sources/Swift/SentryDependencyContainer.swift 98.496% <ø> (ø)
.../Swift/Integrations/Log/FlushLogsIntegration.swift 95.000% <95.000%> (ø)

... and 40 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 929c622...7a57e7a. Read the comment docs.

@denrase
Copy link
Collaborator Author

denrase commented Nov 26, 2025

The app state manager is only enabled for UIKit, but the notification-equivalents for terminate/active should also be available for macOS. Is this intended behaviour?

@denrase denrase added the ready-to-merge Use this label to trigger all PR workflows label Nov 26, 2025
@denrase denrase marked this pull request as ready for review November 26, 2025 10:25
@github-actions
Copy link
Contributor

github-actions bot commented Nov 26, 2025

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1219.49 ms 1252.85 ms 33.36 ms
Size 24.14 KiB 1.02 MiB 1017.81 KiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
0ede342 1233.47 ms 1262.29 ms 28.82 ms
d05d866 1211.78 ms 1230.96 ms 19.18 ms
bbe6658 1221.00 ms 1248.51 ms 27.51 ms
bc0a04c 1226.83 ms 1255.04 ms 28.21 ms
15a6325 1209.43 ms 1233.43 ms 24.00 ms
25d9b58 1233.76 ms 1267.92 ms 34.16 ms
532bde4 1210.63 ms 1256.24 ms 45.61 ms
f83dcc4 1210.88 ms 1246.22 ms 35.35 ms
449d185 1216.31 ms 1251.94 ms 35.62 ms
dad68ad 1229.15 ms 1261.98 ms 32.83 ms

App size

Revision Plain With Sentry Diff
0ede342 23.75 KiB 928.15 KiB 904.40 KiB
d05d866 23.75 KiB 878.60 KiB 854.85 KiB
bbe6658 23.75 KiB 908.02 KiB 884.27 KiB
bc0a04c 23.75 KiB 933.32 KiB 909.57 KiB
15a6325 23.75 KiB 933.33 KiB 909.58 KiB
25d9b58 24.15 KiB 1.01 MiB 1014.91 KiB
532bde4 24.15 KiB 1.01 MiB 1014.89 KiB
f83dcc4 23.75 KiB 1.02 MiB 1019.11 KiB
449d185 23.75 KiB 980.81 KiB 957.06 KiB
dad68ad 23.75 KiB 912.37 KiB 888.63 KiB

Previous results on branch: feat/flush-logs-on-app-state-change

Startup times

Revision Plain With Sentry Diff
a6be137 1221.02 ms 1260.93 ms 39.91 ms
27d2fb4 1228.62 ms 1260.15 ms 31.53 ms
2cb9aef 1228.32 ms 1265.86 ms 37.54 ms
862c32d 1212.94 ms 1246.50 ms 33.56 ms
58c01c4 1193.19 ms 1242.47 ms 49.28 ms
5a96fbe 1223.24 ms 1239.82 ms 16.57 ms
e1bf267 1215.20 ms 1241.04 ms 25.84 ms
6b6ef93 1192.06 ms 1212.08 ms 20.02 ms
9ee58f1 1231.78 ms 1268.67 ms 36.89 ms

App size

Revision Plain With Sentry Diff
a6be137 24.14 KiB 1.02 MiB 1017.81 KiB
27d2fb4 24.14 KiB 1.02 MiB 1017.82 KiB
2cb9aef 24.14 KiB 1.02 MiB 1017.76 KiB
862c32d 24.14 KiB 1.02 MiB 1017.77 KiB
58c01c4 24.14 KiB 1.01 MiB 1013.67 KiB
5a96fbe 24.14 KiB 1.01 MiB 1015.46 KiB
e1bf267 24.14 KiB 1.01 MiB 1015.57 KiB
6b6ef93 24.14 KiB 1.01 MiB 1015.51 KiB
9ee58f1 24.14 KiB 1.01 MiB 1013.68 KiB

Copy link
Member

@philipphofmann philipphofmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this. I found a few high-level issues we need to address before I give this a closer look.

@denrase denrase changed the title Flush Logs on WillTerminate or WillResignActive App State Flush Logs on WillTerminate or WillResignActive Notifications Dec 3, 2025

// Uses DEFAULT priority (not LOW) because captureLogs() is called synchronously during
// app lifecycle events (willResignActive, willTerminate) and needs to complete quickly.
dispatch_queue_attr_t attributes = dispatch_queue_attr_make_with_qos_class(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: Consider moving this to SentryDispatchFactory.m available via SentryDependencyContainer.sharedInstance.dispatchFactory so the creating of queues is consolidated to one position (as it should be everywhere)

Comment on lines 37 to 44
let commonIntegrations: [AnyIntegration] = [.init(SwiftAsyncIntegration.self)]

var integrations: [AnyIntegration] = [.init(SwiftAsyncIntegration.self)]
#if os(iOS) && !SENTRY_NO_UIKIT
let integrations: [AnyIntegration] = commonIntegrations + [.init(UserFeedbackIntegration<SentryDependencyContainer>.self)]
#else
let integrations: [AnyIntegration] = commonIntegrations
integrations.append(.init(UserFeedbackIntegration<SentryDependencyContainer>.self))
#endif

#if ((os(iOS) || os(tvOS) || (swift(>=5.9) && os(visionOS))) && !SENTRY_NO_UIKIT) || ((os(macOS) || targetEnvironment(macCatalyst)) && !SENTRY_NO_UIKIT)
integrations.append(.init(FlushLogsIntegration<SentryDependencyContainer>.self))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: What's your reason for refactoring this logic to not use "commonIntegrations" and added ones?

@denrase denrase requested a review from philprime December 4, 2025 10:38
Copy link
Member

@philipphofmann philipphofmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @denrase, we're getting closer to an LGTM.

Comment on lines +118 to +128
// Uses DEFAULT priority (not LOW) because captureLogs() is called synchronously during
// app lifecycle events (willResignActive, willTerminate) and needs to complete quickly.
dispatch_queue_attr_t attributes
= dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0);
SentryDispatchQueueWrapper *logBatcherQueue =
[[SentryDispatchQueueWrapper alloc] initWithName:"io.sentry.log-batcher"
attributes:attributes];

self.logBatcher = [[SentryLogBatcher alloc] initWithOptions:options
dispatchQueue:logBatcherQueue
delegate:self];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: Thanks for adding an extra dispatch queue. What bugs me a bit is that the SentryLogBatcher specifically needs the dispatch queue above to work correctly, so the init of this specific DispatchQueueWrapper should be in the SentryLogBatcher.swift file if possible, IMO. What about removing the SentryDispatchQueueWrapper param from the convenience init and let the convenience init create the SentryDispatchQueueWrapper. We anyways use the other init method for tests.

import UIKit
private typealias CrossPlatformApplication = UIApplication
#elseif (os(macOS) || targetEnvironment(macCatalyst)) && !SENTRY_NO_UIKIT
#elseif os(macOS)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: Is this change still intended?


@objc private func willResignActive() {
guard let client = SentrySDKInternal.currentHub().getClient() else {
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: Maybe worth adding a log message that the client is nil so we don't have to call captureLogs

import UIKit
typealias Application = UIApplication
#elseif (os(macOS) || targetEnvironment(macCatalyst)) && !SENTRY_NO_UIKIT
#elseif os(macOS)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: Are these changes here intended?

}
}

private var fixture: Fixture!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: We didn't write this down yet somewhere, but we're trying to get rid of the fixture pattern. Please define the fixture variables as properties of the test instead.


override func tearDown() {
super.tearDown()
clearTestState()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: Please avoid using the clearTestState if you can because it does so many things. If you can, consider only unsetting the things you need to unset. In this case, it should be SentrySDKInternal.setCurrentHub(nil).

Comment on lines +22 to +24
dependencies = SentryDependencyContainer.sharedInstance()
notificationCenterWrapper = TestNSNotificationCenterWrapper()
dependencies.notificationCenterWrapper = notificationCenterWrapper
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: Please don't use the SentryDependencyContainer for the test dependencies. Do it like this instead

private struct TestDependencies: ScreenshotSourceProvider {
let screenshotSource: SentryScreenshotSource?
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Use this label to trigger all PR workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Logs: Batch Processor Minimize Data Loss

4 participants