From 2ec1edfeb44e9c110e3e1ef8c32b6d52cb99be58 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Tue, 9 Sep 2025 13:54:00 +0200 Subject: [PATCH 1/6] feat: Add significant time change breadcrumb When iOS posts a UIApplicationSignificantTimeChangeNotification the Cocoa SDK nows adds a breadcrumb. Fixes GH-5161 --- CHANGELOG.md | 1 + Sources/Sentry/SentrySystemEventBreadcrumbs.m | 25 ++++++++++++++++ .../SentrySystemEventBreadcrumbsTest.swift | 30 ++++++++++++++++++- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cf97a33a60..56c6d9495e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Structured Logs: Flush logs on SDK flush/close (#5834) - Add masking options for screenshots (#5401) +- Add significant time change breadcrumb (#6112) ### Fixes diff --git a/Sources/Sentry/SentrySystemEventBreadcrumbs.m b/Sources/Sentry/SentrySystemEventBreadcrumbs.m index 5495faf253b..593e0982dd8 100644 --- a/Sources/Sentry/SentrySystemEventBreadcrumbs.m +++ b/Sources/Sentry/SentrySystemEventBreadcrumbs.m @@ -56,6 +56,9 @@ - (void)stop [self.notificationCenterWrapper removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; + [self.notificationCenterWrapper removeObserver:self + name:UIApplicationSignificantTimeChangeNotification + object:nil]; } - (void)dealloc @@ -82,6 +85,7 @@ - (void)startWithDelegate:(id)delegate [self initKeyboardVisibilityObserver]; [self initScreenshotObserver]; [self initTimezoneObserver]; + [self initSignificantTimeChangeObserver]; } - (void)initBatteryObserver:(UIDevice *)currentDevice @@ -278,6 +282,27 @@ - (void)updateStoredTimezone storeTimezoneOffset:SentryDependencyContainer.sharedInstance.dateProvider.timezoneOffset]; } +- (void)initSignificantTimeChangeObserver +{ + + [self.notificationCenterWrapper addObserver:self + selector:@selector(significantTimeChangeTriggered:) + name:UIApplicationSignificantTimeChangeNotification + object:nil]; +} + +- (void)significantTimeChangeTriggered:(NSNotification *)notification +{ + SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc] initWithLevel:kSentryLevelInfo + category:@"device.event"]; + crumb.type = @"system"; + + // We don't add the timezone here, because we already add it in timezoneEventTriggered. + crumb.data = @{ @"action" : @"SIGNIFICANT_TIME_CHANGE" }; + + [_delegate addBreadcrumb:crumb]; +} + @end #endif // TARGET_OS_IOS && SENTRY_HAS_UIKIT diff --git a/Tests/SentryTests/Integrations/Breadcrumbs/SentrySystemEventBreadcrumbsTest.swift b/Tests/SentryTests/Integrations/Breadcrumbs/SentrySystemEventBreadcrumbsTest.swift index 5842b239d1f..179895b4781 100644 --- a/Tests/SentryTests/Integrations/Breadcrumbs/SentrySystemEventBreadcrumbsTest.swift +++ b/Tests/SentryTests/Integrations/Breadcrumbs/SentrySystemEventBreadcrumbsTest.swift @@ -324,10 +324,38 @@ class SentrySystemEventBreadcrumbsTest: XCTestCase { } } + func testSignificantTimeChangeNotificationBreadcrumb() throws { + sut = fixture.getSut(currentDevice: nil) + + fixture.notificationCenterWrapper.post(Notification(name: UIApplication.significantTimeChangeNotification, object: nil)) + + XCTAssertEqual(1, fixture.delegate.addCrumbInvocations.count) + + if let crumb = fixture.delegate.addCrumbInvocations.first { + + XCTAssertEqual("device.event", crumb.category) + XCTAssertEqual("system", crumb.type) + XCTAssertEqual(SentryLevel.info, crumb.level) + + let data = try XCTUnwrap(crumb.data, "no breadcrumb.data") + XCTAssertEqual("SIGNIFICANT_TIME_CHANGE", data["action"] as? String) + } + } + + func testSignificantTimeChangeNotificationBreadcrumb_UnsubscribeOnStop() { + sut = fixture.getSut(currentDevice: nil) + + sut.stop() + + let didCallRemoveObserver = fixture.notificationCenterWrapper.removeObserverWithNameAndObjectInvocations.invocations.filter { $0.name == UIApplication.significantTimeChangeNotification }.count == 1 + + XCTAssertTrue(didCallRemoveObserver, "Stop didn't call remove observer for UIApplicationSignificantTimeChangeNotification") + } + func testStopCallsSpecificRemoveObserverMethods() { sut = fixture.getSut(currentDevice: nil) sut.stop() - XCTAssertEqual(fixture.notificationCenterWrapper.removeObserverWithNameAndObjectInvocations.count, 6) + XCTAssertEqual(fixture.notificationCenterWrapper.removeObserverWithNameAndObjectInvocations.count, 7) } private func postBatteryLevelNotification(uiDevice: UIDevice?) { From 3bcba45bb1ac5fa693d9d44d1c50dcb76f9bbfe7 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Tue, 9 Sep 2025 16:10:02 +0200 Subject: [PATCH 2/6] Update Sources/Sentry/SentrySystemEventBreadcrumbs.m Co-authored-by: Philip Niedertscheider --- Sources/Sentry/SentrySystemEventBreadcrumbs.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/Sentry/SentrySystemEventBreadcrumbs.m b/Sources/Sentry/SentrySystemEventBreadcrumbs.m index 593e0982dd8..073c46c42d7 100644 --- a/Sources/Sentry/SentrySystemEventBreadcrumbs.m +++ b/Sources/Sentry/SentrySystemEventBreadcrumbs.m @@ -291,6 +291,12 @@ - (void)initSignificantTimeChangeObserver object:nil]; } +/** + * The system posts this notification when, for example, there’s a change to a new day (midnight), a carrier time update, or a change to, or from, daylight savings time. + * The notification doesn’t contain a user info dictionary. + * + * @see https://developer.apple.com/documentation/uikit/uiapplication/significanttimechangenotification#Discussion + */ - (void)significantTimeChangeTriggered:(NSNotification *)notification { SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc] initWithLevel:kSentryLevelInfo From b699185cf47c3e55d78212576ff5568737769444 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Tue, 9 Sep 2025 16:10:09 +0200 Subject: [PATCH 3/6] Update Tests/SentryTests/Integrations/Breadcrumbs/SentrySystemEventBreadcrumbsTest.swift Co-authored-by: Philip Niedertscheider --- .../SentrySystemEventBreadcrumbsTest.swift | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Tests/SentryTests/Integrations/Breadcrumbs/SentrySystemEventBreadcrumbsTest.swift b/Tests/SentryTests/Integrations/Breadcrumbs/SentrySystemEventBreadcrumbsTest.swift index 179895b4781..f372076fbc8 100644 --- a/Tests/SentryTests/Integrations/Breadcrumbs/SentrySystemEventBreadcrumbsTest.swift +++ b/Tests/SentryTests/Integrations/Breadcrumbs/SentrySystemEventBreadcrumbsTest.swift @@ -331,15 +331,14 @@ class SentrySystemEventBreadcrumbsTest: XCTestCase { XCTAssertEqual(1, fixture.delegate.addCrumbInvocations.count) - if let crumb = fixture.delegate.addCrumbInvocations.first { + let crumb = try XCTUnwrap(fixture.delegate.addCrumbInvocations.first) - XCTAssertEqual("device.event", crumb.category) - XCTAssertEqual("system", crumb.type) - XCTAssertEqual(SentryLevel.info, crumb.level) + XCTAssertEqual("device.event", crumb.category) + XCTAssertEqual("system", crumb.type) + XCTAssertEqual(SentryLevel.info, crumb.level) - let data = try XCTUnwrap(crumb.data, "no breadcrumb.data") - XCTAssertEqual("SIGNIFICANT_TIME_CHANGE", data["action"] as? String) - } + let data = try XCTUnwrap(crumb.data, "no breadcrumb.data") + XCTAssertEqual("SIGNIFICANT_TIME_CHANGE", data["action"] as? String) } func testSignificantTimeChangeNotificationBreadcrumb_UnsubscribeOnStop() { From ac9dab33a79d7f5f599d9644a70d1dcd08742fb0 Mon Sep 17 00:00:00 2001 From: Itay Brenner Date: Tue, 9 Sep 2025 10:08:47 -0300 Subject: [PATCH 4/6] chore(ci): Remove Package.swift checks (#6110) --- .github/file-filters.yml | 11 ----- .github/workflows/check-package-diff.yml | 56 ------------------------ .github/workflows/release.yml | 25 ++--------- Makefile | 15 ------- Utils/VersionBump/main.swift | 1 - Utils/expected_package_diff.patch | 21 --------- 6 files changed, 3 insertions(+), 126 deletions(-) delete mode 100644 .github/workflows/check-package-diff.yml delete mode 100644 Utils/expected_package_diff.patch diff --git a/.github/file-filters.yml b/.github/file-filters.yml index 83a2c44ed7f..fbd7279c604 100644 --- a/.github/file-filters.yml +++ b/.github/file-filters.yml @@ -371,17 +371,6 @@ run_testflight_for_pushes: &run_testflight_for_pushes - "fastlane/**" - "Makefile" -run_check_package_diff_for_prs: &run_check_package_diff_for_prs - - ".github/workflows/check-package-diff.yml" - - ".github/file-filters.yml" - - # Package files - - "Package*.swift" - - # Build configuration - - "Makefile" - - "Utils/expected_package_diff.patch" - run_codeql_analysis_for_prs: &run_codeql_analysis_for_prs - "Sources/**" diff --git a/.github/workflows/check-package-diff.yml b/.github/workflows/check-package-diff.yml deleted file mode 100644 index b2a144c7152..00000000000 --- a/.github/workflows/check-package-diff.yml +++ /dev/null @@ -1,56 +0,0 @@ -# We want to ensure that the Package.swift and Package@swift-5.9.swift are in sync. - -name: Check Package.swift Diff - -on: - push: - branches: - - main - - pull_request: - -jobs: - files-changed: - name: Detect File Changes - runs-on: ubuntu-latest - outputs: - run_check_package_diff_for_prs: ${{ steps.changes.outputs.run_check_package_diff_for_prs }} - steps: - - uses: actions/checkout@v5 - - name: Get changed files - id: changes - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 - with: - token: ${{ github.token }} - filters: .github/file-filters.yml - - check: - if: github.event_name != 'pull_request' || needs.files-changed.outputs.run_check_package_diff_for_prs == 'true' - needs: files-changed - name: Check Package.swift Diff - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - name: Check Package.swift Diff - run: make check-package-diff - - # This check validates that either check-package-diff passed or was skipped, which allows us - # to make check-package-diff a required check with only running the check-package-diff when required. - # So, we don't have to run check-package-diff, for example, for unrelated changes. - check_package_diff-required-check: - needs: - [ - files-changed, - check, - ] - name: Check Package.swift Diff - # This is necessary since a failed/skipped dependent job would cause this job to be skipped - if: always() - runs-on: ubuntu-latest - steps: - # If any jobs we depend on fails gets cancelled or times out, this job will fail. - # Skipped jobs are not considered failures. - - name: Check for failures - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') - run: | - echo "One of the check-package-diff jobs has failed." && exit 1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e9008a385ef..ac47169f9d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -129,7 +129,7 @@ jobs: # the github.sha is be the pre merge commit id for PRs. # See https://github.community/t/github-sha-isnt-the-value-expected/17903/17906. validate-spm: - name: Validate SPM Static ${{matrix.package-file.name}} + name: Validate SPM Static runs-on: macos-14 needs: [files-changed, assemble-xcframework-variant] # Run the job only for PRs with related changes or non-PR events. @@ -144,20 +144,14 @@ jobs: uses: ./.github/actions/prepare-package.swift with: is-pr: ${{ github.event_name == 'pull_request' }} - package-file: ${{matrix.package-file.name}} - run: swift build working-directory: Samples/macOS-SPM-CommandLine - name: Run CI Diagnostics if: failure() run: ./scripts/ci-diagnostics.sh - strategy: - matrix: - package-file: - - name: Package.swift - - name: Package@swift-5.9.swift validate-spm-dynamic: - name: Validate SPM Dynamic ${{matrix.package-file.name}} + name: Validate SPM Dynamic runs-on: macos-14 needs: [files-changed, assemble-xcframework-variant] # Run the job only for PRs with related changes or non-PR events. @@ -172,20 +166,14 @@ jobs: uses: ./.github/actions/prepare-package.swift with: is-pr: ${{ github.event_name == 'pull_request' }} - package-file: ${{matrix.package-file.name}} - run: swift build working-directory: Samples/SPM-Dynamic - name: Run CI Diagnostics if: failure() run: ./scripts/ci-diagnostics.sh - strategy: - matrix: - package-file: - - name: Package.swift - - name: Package@swift-5.9.swift swift-build: - name: Build Swift Static ${{matrix.package-file.name}} + name: Build Swift Static runs-on: macos-14 needs: [files-changed, assemble-xcframework-variant] # Run the job only for PRs with related changes or non-PR events. @@ -200,16 +188,10 @@ jobs: uses: ./.github/actions/prepare-package.swift with: is-pr: ${{ github.event_name == 'pull_request' }} - package-file: ${{matrix.package-file.name}} - run: swift build - name: Run CI Diagnostics if: failure() run: ./scripts/ci-diagnostics.sh - strategy: - matrix: - package-file: - - name: Package.swift - - name: Package@swift-5.9.swift validate-spm-visionos: name: Validate SPM Static visionOS @@ -227,7 +209,6 @@ jobs: uses: ./.github/actions/prepare-package.swift with: is-pr: ${{ github.event_name == 'pull_request' }} - package-file: Package@swift-5.9.swift - run: set -o pipefail &&xcodebuild build -scheme visionOS-SPM -sdk xros -destination 'generic/platform=xros' | tee raw-build-output-spm-visionOS.log | xcbeautify working-directory: Samples/visionOS-SPM - name: Run CI Diagnostics diff --git a/Makefile b/Makefile index 1d9f8d96843..004a24cae8a 100644 --- a/Makefile +++ b/Makefile @@ -189,18 +189,3 @@ xcode-ci: xcodegen --spec Samples/watchOS-Swift/watchOS-Swift.yml xcodegen --spec TestSamples/SwiftUITestSample/SwiftUITestSample.yml xcodegen --spec TestSamples/SwiftUICrashTest/SwiftUICrashTest.yml - -.PHONY: check-package-diff update-package-diff -# Diff will return an error if the files are different, so we need to ignore it with `|| true`. -check-package-diff: - diff Package.swift Package@swift-5.9.swift > current_package_diff.patch || true - @if diff ./Utils/expected_package_diff.patch current_package_diff.patch > /dev/null 2>&1; then \ - echo "--> Package diff check passed - no changes detected"; \ - else \ - echo "--> Package diff check failed - changes detected! Make sure to run \"make update-package-diff\" to update the expected diff."; \ - exit 1; \ - fi - -# Diff will return an error if the files are different, so we need to ignore it with `|| true`. -update-package-diff: - diff Package.swift Package@swift-5.9.swift > ./Utils/expected_package_diff.patch || true diff --git a/Utils/VersionBump/main.swift b/Utils/VersionBump/main.swift index ee32be2133e..5a1924f802d 100644 --- a/Utils/VersionBump/main.swift +++ b/Utils/VersionBump/main.swift @@ -39,7 +39,6 @@ let fromVersionFile = "./Sentry.podspec" let files = [ "./Sentry.podspec", "./Package.swift", - "./Package@swift-5.9.swift", "./SentryPrivate.podspec", "./SentrySwiftUI.podspec", "./Sources/Sentry/SentryMeta.m", diff --git a/Utils/expected_package_diff.patch b/Utils/expected_package_diff.patch deleted file mode 100644 index 91b792d78c1..00000000000 --- a/Utils/expected_package_diff.patch +++ /dev/null @@ -1,21 +0,0 @@ -1,2c1,5 -< // swift-tools-version:5.3 -< #if canImport(Darwin) ---- -> // swift-tools-version:5.9 -> // This Package.swift for Swift 5.9 (and newer) fixes an issue on Xcode 26 when linking `SentrySwiftUI`. -> // Don't remove this comment. -> -> #if canImport(Darwin) -15d17 -< .library(name: "Sentry-Dynamic-WithARM64e", targets: ["Sentry-Dynamic-WithARM64e"]), -61c63 -< if let env = env, String(cString: env, encoding: .utf8) == "1" { ---- -> if let env = env, String(cString: env) == "1" { -92a95 -> publicHeadersPath: "", -108c111 -< platforms: [.iOS(.v11), .macOS(.v10_13), .tvOS(.v11), .watchOS(.v4)], ---- -> platforms: [.iOS(.v11), .macOS(.v10_13), .tvOS(.v11), .watchOS(.v4), .visionOS(.v1)], From 895e7b0ab6dbb6738b5ec2fa57fb387c49db00e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 15:36:18 +0200 Subject: [PATCH 5/6] chore(deps): bump codecov/codecov-action from 5.5.0 to 5.5.1 (#6107) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.5.0 to 5.5.1. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/fdcc8476540edceab3de004e990f80d881c6cc00...5a1091511ad55cbe89839c7260b706298ca349f7) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: 5.5.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 382ce2624e3..ac89f9a7591 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -285,7 +285,7 @@ jobs: # We don't upload codecov for scheduled runs as CodeCov only accepts a limited amount of uploads per commit. - name: Push code coverage to codecov id: codecov_1 - uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # pin@v5.5.0 + uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # pin@v5.5.1 if: ${{ contains(matrix.platform, 'iOS') && !contains(github.ref, 'release') && github.event.schedule == '' }} with: # Although public repos should not have to specify a token there seems to be a bug with the Codecov GH action, which can @@ -297,7 +297,7 @@ jobs: # Sometimes codecov uploads etc can fail. Retry one time to rule out e.g. intermittent network failures. - name: Push code coverage to codecov id: codecov_2 - uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # pin@v5.5.0 + uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # pin@v5.5.1 if: ${{ steps.codecov_1.outcome == 'failure' && contains(matrix.platform, 'iOS') && !contains(github.ref, 'release') && github.event.schedule == '' }} with: token: ${{ secrets.CODECOV_TOKEN }} From b67dd20651f1b2f1a8322f77768a548b55b5ff42 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Tue, 9 Sep 2025 16:24:40 +0200 Subject: [PATCH 6/6] format code --- Sources/Sentry/SentrySystemEventBreadcrumbs.m | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Sources/Sentry/SentrySystemEventBreadcrumbs.m b/Sources/Sentry/SentrySystemEventBreadcrumbs.m index 073c46c42d7..6414fb7f615 100644 --- a/Sources/Sentry/SentrySystemEventBreadcrumbs.m +++ b/Sources/Sentry/SentrySystemEventBreadcrumbs.m @@ -292,10 +292,12 @@ - (void)initSignificantTimeChangeObserver } /** - * The system posts this notification when, for example, there’s a change to a new day (midnight), a carrier time update, or a change to, or from, daylight savings time. - * The notification doesn’t contain a user info dictionary. + * The system posts this notification when, for example, there’s a change to a new day (midnight), a + * carrier time update, or a change to, or from, daylight savings time. The notification doesn’t + * contain a user info dictionary. * - * @see https://developer.apple.com/documentation/uikit/uiapplication/significanttimechangenotification#Discussion + * @see + * https://developer.apple.com/documentation/uikit/uiapplication/significanttimechangenotification#Discussion */ - (void)significantTimeChangeTriggered:(NSNotification *)notification {