diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9b01eef2914..cb76f1203b7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -251,6 +251,33 @@ jobs: device: "iPhone 15 Pro" scheme: "Sentry" + - name: iOS 17 SentrySwiftUI + runs-on: macos-14 + xcode: "15.4" + test-destination-os: "17.5" + platform: "iOS" + create_device: true + device: "iPhone 15 Pro" + scheme: "SentrySwiftUI" + + - name: iOS 17 SentrySwiftLog + runs-on: macos-14 + xcode: "15.4" + test-destination-os: "17.5" + platform: "iOS" + create_device: true + device: "iPhone 15 Pro" + scheme: "SentrySwiftLog" + + - name: iOS 17 SentryPulse + runs-on: macos-14 + xcode: "15.4" + test-destination-os: "17.5" + platform: "iOS" + create_device: true + device: "iPhone 15 Pro" + scheme: "SentryPulse" + # iOS 18 - Use pre-installed iOS 18.4 runtime on macOS-15 - name: iOS 18 Sentry runs-on: macos-15 @@ -332,26 +359,6 @@ jobs: platform: "tvOS" scheme: "Sentry" - # iOS 17 - Use pre-installed iOS 17.5 runtime on macOS-14 with Xcode 15.4 - - name: iOS 17 SentrySwiftUI - runs-on: macos-14 - xcode: "15.4" - test-destination-os: "17.5" - platform: "iOS" - create_device: true - device: "iPhone 15 Pro" - scheme: "SentrySwiftUI" - - # iOS 17 - Use pre-installed iOS 17.5 runtime on macOS-14 with Xcode 15.4 - - name: iOS 17 SentrySwiftLog - runs-on: macos-14 - xcode: "15.4" - test-destination-os: "17.5" - platform: "iOS" - create_device: true - device: "iPhone 15 Pro" - scheme: "SentrySwiftLog" - # tvOS 18 - name: tvOS 18 Sentry runs-on: macos-15 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f52b037c1e..4686ab7c2c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ - Move `enableDataSwizzling` from experimental options to top-level options (#6592). This option remains enabled by default. - Add `sentry.replay_id` attribute to logs ([#6515](https://github.com/getsentry/sentry-cocoa/pull/6515)) - Structured Logs: Add `SentrySwiftLog` Integration (#6286) +- Structured Logs: Add `SentryPulse` Integration (#6588) ### Fixes diff --git a/Package.swift b/Package.swift index 471534c8953..730472bd56c 100644 --- a/Package.swift +++ b/Package.swift @@ -17,6 +17,7 @@ var products: [Product] = [ .library(name: "Sentry-WithoutUIKitOrAppKit-WithARM64e", targets: ["Sentry-WithoutUIKitOrAppKit-WithARM64e", "SentryCppHelper"]), .library(name: "SentrySwiftUI", targets: ["Sentry", "SentrySwiftUI", "SentryCppHelper"]), .library(name: "SentryDistribution", targets: ["SentryDistribution"]), + .library(name: "SentryPulse", targets: ["Sentry", "SentryPulse"]), .library(name: "SentrySwiftLog", targets: ["Sentry", "SentrySwiftLog"]) ] @@ -55,6 +56,14 @@ var targets: [Target] = [ .linkedFramework("Sentry") ] ), + .target( + name: "SentryPulse", + dependencies: ["Sentry", .product(name: "Pulse", package: "Pulse")], + path: "Sources/SentryPulse", + linkerSettings: [ + .linkedFramework("Sentry") + ] + ), .target( name: "SentrySwiftLog", dependencies: ["Sentry", .product(name: "Logging", package: "swift-log")], @@ -63,6 +72,7 @@ var targets: [Target] = [ .linkedFramework("Sentry") ] ), + .target( name: "SentryInternal", path: "Sources/SentrySwiftUI", @@ -112,7 +122,7 @@ if let env = env, String(cString: env, encoding: .utf8) == "1" { name: "SentryObjc", dependencies: ["SentrySwift"], path: "Sources", - exclude: ["Sentry/SentryDummyPublicEmptyClass.m", "Sentry/SentryDummyPrivateEmptyClass.m", "Swift", "SentrySwiftUI", "SentrySwiftLog", "Resources", "Configuration", "SentryCppHelper", "SentryDistribution", "SentryDistributionTests"], + exclude: ["Sentry/SentryDummyPublicEmptyClass.m", "Sentry/SentryDummyPrivateEmptyClass.m", "Swift", "SentrySwiftUI", "SentryPulse", "SentrySwiftLog", "Resources", "Configuration", "SentryCppHelper", "SentryDistribution", "SentryDistributionTests"], cSettings: [ .headerSearchPath("Sentry/include/HybridPublic"), .headerSearchPath("Sentry"), @@ -133,7 +143,8 @@ let package = Package( // SPM doesn't support peer-dependencies, so users are locked into our declared version. // Using `from: "1.6.0"` covers 1.6.0 < 2.0.0, resolving minor versions automatically. // See develop-docs/DECISIONS.md for discussion. - .package(url: "https://github.com/apple/swift-log", from: "1.6.0") + .package(url: "https://github.com/apple/swift-log", from: "1.6.0"), + .package(url: "https://github.com/kean/Pulse", from: "5.0.0") ], targets: targets, cxxLanguageStandard: .cxx14 diff --git a/Plans/SentryPulse_Base.xctestplan b/Plans/SentryPulse_Base.xctestplan new file mode 100644 index 00000000000..154dbb27311 --- /dev/null +++ b/Plans/SentryPulse_Base.xctestplan @@ -0,0 +1,24 @@ +{ + "configurations" : [ + { + "id" : "EBB4F92C-3CA9-47E0-8589-7934881F1E4B", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + "codeCoverage" : true + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:Sentry.xcodeproj", + "identifier" : "92B71B682EBB7F3C00A64658", + "name" : "SentryPulseTests" + } + } + ], + "version" : 1 +} diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 8d48bfba522..05b4725e561 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -717,7 +717,6 @@ 92235CAC2E15369900865983 /* SentryLogBatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92235CAB2E15369900865983 /* SentryLogBatcher.swift */; }; 92235CAE2E15549C00865983 /* SentryLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92235CAD2E15549C00865983 /* SentryLogger.swift */; }; 92235CB02E155B2600865983 /* SentryLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92235CAF2E155B2600865983 /* SentryLoggerTests.swift */; }; - 92275AAA2EBA402D00F31AB9 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 92275AA92EBA402D00F31AB9 /* Logging */; }; 92275AAB2EBA417700F31AB9 /* SentryLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9241AC4A2EBA39EE00E611ED /* SentryLogHandler.swift */; }; 9241AC1C2EBA38CC00E611ED /* SentrySwiftLog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9241AC132EBA38CB00E611ED /* SentrySwiftLog.framework */; }; 9241AC3C2EBA399100E611ED /* libSentryTestUtils.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8431F00A29B284F200D8DC56 /* libSentryTestUtils.a */; }; @@ -725,8 +724,13 @@ 9241AC442EBA39A500E611ED /* SentrySwiftLog.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9241AC132EBA38CB00E611ED /* SentrySwiftLog.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9241AC4F2EBA3A0C00E611ED /* SentryLogHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9241AC4D2EBA3A0C00E611ED /* SentryLogHandlerTests.swift */; }; 925824C22CB5897700C9B20B /* SentrySessionReplayIntegration-Hybrid.h in Headers */ = {isa = PBXBuildFile; fileRef = D80382BE2C09C6FD0090E048 /* SentrySessionReplayIntegration-Hybrid.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 9259B0C02EBCB3620088C4A4 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 9259B0BF2EBCB3620088C4A4 /* Logging */; }; + 9259B0C32EBCB3730088C4A4 /* Pulse in Frameworks */ = {isa = PBXBuildFile; productRef = 9259B0C22EBCB3730088C4A4 /* Pulse */; }; 9264E1EB2E2E385E00B077CF /* SentryLogMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9264E1EA2E2E385B00B077CF /* SentryLogMessage.swift */; }; 9264E1ED2E2E397C00B077CF /* SentryLogMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9264E1EC2E2E397400B077CF /* SentryLogMessageTests.swift */; }; + 9266FC202EBCB4A20043C083 /* libSentryTestUtils.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8431F00A29B284F200D8DC56 /* libSentryTestUtils.a */; }; + 9266FC232EBCB4A50043C083 /* SentryTestUtilsDynamic.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D84DAD4D2B17428D003CF120 /* SentryTestUtilsDynamic.framework */; }; + 9266FC282EBCB4B60043C083 /* SentryPulse.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 92B71B612EBB7F3C00A64658 /* SentryPulse.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 92672BB629C9A2A9006B021C /* SentryBreadcrumb+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 92672BB529C9A2A9006B021C /* SentryBreadcrumb+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 927A5CC42DD7626B00B82404 /* SentryEnvelopeItemHeaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927A5CC32DD7626400B82404 /* SentryEnvelopeItemHeaderTests.swift */; }; 928207C42E251B8F009285A4 /* SentryScope+PrivateSwift.h in Headers */ = {isa = PBXBuildFile; fileRef = 928207C32E251B8F009285A4 /* SentryScope+PrivateSwift.h */; }; @@ -736,6 +740,9 @@ 928BED2B2E16977A00B4D398 /* SentryLogBatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 928BED2A2E16977A00B4D398 /* SentryLogBatcherTests.swift */; }; 92B6BDA92E05B8F600D538B3 /* SentryLogLevelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92B6BDA82E05B8F000D538B3 /* SentryLogLevelTests.swift */; }; 92B6BDAD2E05B9FB00D538B3 /* SentryLogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92B6BDAC2E05B9F700D538B3 /* SentryLogTests.swift */; }; + 92B71B6A2EBB7F3C00A64658 /* SentryPulse.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92B71B612EBB7F3C00A64658 /* SentryPulse.framework */; }; + 92B71B852EBB7F5500A64658 /* SentryPulse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92B71B832EBB7F5500A64658 /* SentryPulse.swift */; }; + 92B71B882EBB7F7300A64658 /* SentryPulseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92B71B862EBB7F7300A64658 /* SentryPulseTests.swift */; }; 92D957732E05A44600E20E66 /* SentryAsyncLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 92D957722E05A44600E20E66 /* SentryAsyncLog.m */; }; 92D957772E05A4F300E20E66 /* SentryAsyncLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 92D957762E05A4F300E20E66 /* SentryAsyncLog.h */; }; 92E5F3D62CDBB3BF00B7AD98 /* SentrySampling.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E8C57A525EEFC42001CEEFA /* SentrySampling.h */; }; @@ -1215,6 +1222,13 @@ remoteGlobalIDString = 63AA759A1EB8AEF500D153DE; remoteInfo = Sentry; }; + 9236F38F2EBCB5E7004E9C80 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6327C5CA1EB8A783004E799B /* Project object */; + proxyType = 1; + remoteGlobalIDString = 63AA759A1EB8AEF500D153DE; + remoteInfo = Sentry; + }; 9241AC1D2EBA38CC00E611ED /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 6327C5CA1EB8A783004E799B /* Project object */; @@ -1243,6 +1257,27 @@ remoteGlobalIDString = 63AA759A1EB8AEF500D153DE; remoteInfo = Sentry; }; + 9266FC212EBCB4A20043C083 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6327C5CA1EB8A783004E799B /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8431F00929B284F200D8DC56; + remoteInfo = SentryTestUtils; + }; + 9266FC252EBCB4A50043C083 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6327C5CA1EB8A783004E799B /* Project object */; + proxyType = 1; + remoteGlobalIDString = D84DAD4C2B17428D003CF120; + remoteInfo = SentryTestUtilsDynamic; + }; + 92B71B6B2EBB7F3C00A64658 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6327C5CA1EB8A783004E799B /* Project object */; + proxyType = 1; + remoteGlobalIDString = 92B71B602EBB7F3C00A64658; + remoteInfo = SentryPulse; + }; D4B339FB2EA7823000359F3A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 6327C5CA1EB8A783004E799B /* Project object */; @@ -1292,6 +1327,17 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + 9266FC272EBCB4A50043C083 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 9266FC282EBCB4B60043C083 /* SentryPulse.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; D4B339FD2EA7823000359F3A /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -2143,6 +2189,11 @@ 928BED2A2E16977A00B4D398 /* SentryLogBatcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogBatcherTests.swift; sourceTree = ""; }; 92B6BDA82E05B8F000D538B3 /* SentryLogLevelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogLevelTests.swift; sourceTree = ""; }; 92B6BDAC2E05B9F700D538B3 /* SentryLogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogTests.swift; sourceTree = ""; }; + 92B71B612EBB7F3C00A64658 /* SentryPulse.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SentryPulse.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 92B71B692EBB7F3C00A64658 /* SentryPulseTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SentryPulseTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 92B71B832EBB7F5500A64658 /* SentryPulse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryPulse.swift; sourceTree = ""; }; + 92B71B862EBB7F7300A64658 /* SentryPulseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryPulseTests.swift; sourceTree = ""; }; + 92B71B8A2EBB7F3C00A64658 /* SentryPulse.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SentryPulse.xcconfig; sourceTree = ""; }; 92D957722E05A44600E20E66 /* SentryAsyncLog.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryAsyncLog.m; sourceTree = ""; }; 92D957762E05A4F300E20E66 /* SentryAsyncLog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryAsyncLog.h; path = include/SentryAsyncLog.h; sourceTree = ""; }; 92EC54CD2E1EB54B00A10AC2 /* SentryClient+Logs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryClient+Logs.h"; path = "include/SentryClient+Logs.h"; sourceTree = ""; }; @@ -2632,7 +2683,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 92275AAA2EBA402D00F31AB9 /* Logging in Frameworks */, + 9259B0C02EBCB3620088C4A4 /* Logging in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2646,6 +2697,24 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 92B71B5E2EBB7F3C00A64658 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9259B0C32EBCB3730088C4A4 /* Pulse in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 92B71B662EBB7F3C00A64658 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9266FC202EBCB4A20043C083 /* libSentryTestUtils.a in Frameworks */, + 9266FC232EBCB4A50043C083 /* SentryTestUtilsDynamic.framework in Frameworks */, + 92B71B6A2EBB7F3C00A64658 /* SentryPulse.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D4CBA2402DE06D0200581618 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2945,6 +3014,8 @@ D4CBA2432DE06D0200581618 /* SentryTestUtilsTests.xctest */, 9241AC132EBA38CB00E611ED /* SentrySwiftLog.framework */, 9241AC1B2EBA38CC00E611ED /* SentrySwiftLogTests.xctest */, + 92B71B612EBB7F3C00A64658 /* SentryPulse.framework */, + 92B71B692EBB7F3C00A64658 /* SentryPulseTests.xctest */, ); name = Products; sourceTree = ""; @@ -3075,6 +3146,7 @@ D800942328F82E8D005D3943 /* Swift */, 63FE6FB920DA4C1000CDBAE8 /* SentryCrash */, D8199DB329376ECC0074249E /* SentrySwiftUI */, + 92B71B842EBB7F5500A64658 /* SentryPulse */, 9241AC4B2EBA39EE00E611ED /* SentrySwiftLog */, 63AA75A31EB8AFDF00D153DE /* Configuration */, D8B0542F2A7D35F10056BAF6 /* Resources */, @@ -3091,6 +3163,7 @@ 846D3F122E286ECF00D4E7E3 /* DuplicatedSDKTest */, 63AA75931EB8AEDB00D153DE /* SentryTests */, 8431EFDB29B27B3D00D8DC56 /* SentryProfilerTests */, + 92B71B872EBB7F7300A64658 /* SentryPulseTests */, 9241AC4E2EBA3A0C00E611ED /* SentrySwiftLogTests */, D8F01DE32A125D7B008F4996 /* HybridSDKTest */, D833D60F2D1320DF00961E7A /* SentrySwiftUITests */, @@ -3164,6 +3237,7 @@ 63AA75C51EB8B00100D153DE /* Sentry.xcconfig */, D8199DCF29376FF40074249E /* SentrySwiftUI.xcconfig */, D92A1BCF2EBA3A0000E611ED /* SentrySwiftLog.xcconfig */, + 92B71B8A2EBB7F3C00A64658 /* SentryPulse.xcconfig */, ); path = Configuration; sourceTree = ""; @@ -4293,6 +4367,22 @@ path = SentrySwiftLogTests; sourceTree = ""; }; + 92B71B842EBB7F5500A64658 /* SentryPulse */ = { + isa = PBXGroup; + children = ( + 92B71B832EBB7F5500A64658 /* SentryPulse.swift */, + ); + path = SentryPulse; + sourceTree = ""; + }; + 92B71B872EBB7F7300A64658 /* SentryPulseTests */ = { + isa = PBXGroup; + children = ( + 92B71B862EBB7F7300A64658 /* SentryPulseTests.swift */, + ); + path = SentryPulseTests; + sourceTree = ""; + }; D4009EA02D77196F0007AF30 /* ViewCapture */ = { isa = PBXGroup; children = ( @@ -5494,6 +5584,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 92B71B5C2EBB7F3C00A64658 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; D8199DA529376E9B0074249E /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -5610,7 +5707,7 @@ ); name = SentrySwiftLog; packageProductDependencies = ( - 92275AA92EBA402D00F31AB9 /* Logging */, + 9259B0BF2EBCB3620088C4A4 /* Logging */, ); productName = SentrySwiftLog; productReference = 9241AC132EBA38CB00E611ED /* SentrySwiftLog.framework */; @@ -5639,6 +5736,51 @@ productReference = 9241AC1B2EBA38CC00E611ED /* SentrySwiftLogTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 92B71B602EBB7F3C00A64658 /* SentryPulse */ = { + isa = PBXNativeTarget; + buildConfigurationList = 92B71B812EBB7F3C00A64658 /* Build configuration list for PBXNativeTarget "SentryPulse" */; + buildPhases = ( + 92B71B5C2EBB7F3C00A64658 /* Headers */, + 92B71B5D2EBB7F3C00A64658 /* Sources */, + 92B71B5E2EBB7F3C00A64658 /* Frameworks */, + 92B71B5F2EBB7F3C00A64658 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 9236F3902EBCB5E7004E9C80 /* PBXTargetDependency */, + ); + name = SentryPulse; + packageProductDependencies = ( + 9259B0C22EBCB3730088C4A4 /* Pulse */, + ); + productName = SentryPulse; + productReference = 92B71B612EBB7F3C00A64658 /* SentryPulse.framework */; + productType = "com.apple.product-type.framework"; + }; + 92B71B682EBB7F3C00A64658 /* SentryPulseTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 92B71B822EBB7F3C00A64658 /* Build configuration list for PBXNativeTarget "SentryPulseTests" */; + buildPhases = ( + 92B71B652EBB7F3C00A64658 /* Sources */, + 92B71B662EBB7F3C00A64658 /* Frameworks */, + 92B71B672EBB7F3C00A64658 /* Resources */, + 9266FC272EBCB4A50043C083 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 92B71B6C2EBB7F3C00A64658 /* PBXTargetDependency */, + 9266FC222EBCB4A20043C083 /* PBXTargetDependency */, + 9266FC262EBCB4A50043C083 /* PBXTargetDependency */, + ); + name = SentryPulseTests; + packageProductDependencies = ( + ); + productName = SentryPulseTests; + productReference = 92B71B692EBB7F3C00A64658 /* SentryPulseTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; D4CBA2422DE06D0200581618 /* SentryTestUtilsTests */ = { isa = PBXNativeTarget; buildConfigurationList = D4CBA2502DE06D0200581618 /* Build configuration list for PBXNativeTarget "SentryTestUtilsTests" */; @@ -5751,6 +5893,12 @@ 9241AC1A2EBA38CC00E611ED = { CreatedOnToolsVersion = 26.1; }; + 92B71B602EBB7F3C00A64658 = { + CreatedOnToolsVersion = 26.1; + }; + 92B71B682EBB7F3C00A64658 = { + CreatedOnToolsVersion = 26.1; + }; D4CBA2422DE06D0200581618 = { CreatedOnToolsVersion = 16.3; }; @@ -5772,7 +5920,8 @@ ); mainGroup = 6327C5C91EB8A783004E799B; packageReferences = ( - 9241AC452EBA39C700E611ED /* XCRemoteSwiftPackageReference "swift-log" */, + 9259B0BE2EBCB3620088C4A4 /* XCRemoteSwiftPackageReference "swift-log" */, + 9259B0C12EBCB3730088C4A4 /* XCRemoteSwiftPackageReference "Pulse" */, ); productRefGroup = 6327C5D41EB8A783004E799B /* Products */; projectDirPath = ""; @@ -5794,6 +5943,8 @@ D4CBA2422DE06D0200581618 /* SentryTestUtilsTests */, 9241AC122EBA38CB00E611ED /* SentrySwiftLog */, 9241AC1A2EBA38CC00E611ED /* SentrySwiftLogTests */, + 92B71B602EBB7F3C00A64658 /* SentryPulse */, + 92B71B682EBB7F3C00A64658 /* SentryPulseTests */, ); }; /* End PBXProject section */ @@ -5876,6 +6027,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 92B71B5F2EBB7F3C00A64658 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 92B71B672EBB7F3C00A64658 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; D4CBA2412DE06D0200581618 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -6760,6 +6925,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 92B71B5D2EBB7F3C00A64658 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 92B71B852EBB7F5500A64658 /* SentryPulse.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 92B71B652EBB7F3C00A64658 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 92B71B882EBB7F7300A64658 /* SentryPulseTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D4CBA23F2DE06D0200581618 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -6828,6 +7009,11 @@ target = 63AA759A1EB8AEF500D153DE /* Sentry */; targetProxy = 84B7FA3729B2860500AD93B1 /* PBXContainerItemProxy */; }; + 9236F3902EBCB5E7004E9C80 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 63AA759A1EB8AEF500D153DE /* Sentry */; + targetProxy = 9236F38F2EBCB5E7004E9C80 /* PBXContainerItemProxy */; + }; 9241AC1E2EBA38CC00E611ED /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 9241AC122EBA38CB00E611ED /* SentrySwiftLog */; @@ -6848,6 +7034,21 @@ target = 63AA759A1EB8AEF500D153DE /* Sentry */; targetProxy = 9241AC482EBA39D600E611ED /* PBXContainerItemProxy */; }; + 9266FC222EBCB4A20043C083 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8431F00929B284F200D8DC56 /* SentryTestUtils */; + targetProxy = 9266FC212EBCB4A20043C083 /* PBXContainerItemProxy */; + }; + 9266FC262EBCB4A50043C083 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D84DAD4C2B17428D003CF120 /* SentryTestUtilsDynamic */; + targetProxy = 9266FC252EBCB4A50043C083 /* PBXContainerItemProxy */; + }; + 92B71B6C2EBB7F3C00A64658 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 92B71B602EBB7F3C00A64658 /* SentryPulse */; + targetProxy = 92B71B6B2EBB7F3C00A64658 /* PBXContainerItemProxy */; + }; D4B339FC2EA7823000359F3A /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = D84DAD4C2B17428D003CF120 /* SentryTestUtilsDynamic */; @@ -7810,6 +8011,7 @@ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_SWIFT_FLAGS = "-enable-experimental-feature AccessLevelOnImport"; PRODUCT_BUNDLE_IDENTIFIER = io.sentry.SentrySwiftLogTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -7818,7 +8020,6 @@ SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator xros xrsimulator"; SUPPORTS_MACCATALYST = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG SENTRY_TEST $(inherited)"; - OTHER_SWIFT_FLAGS = "-enable-experimental-feature AccessLevelOnImport"; SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -7896,6 +8097,7 @@ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_SWIFT_FLAGS = "-enable-experimental-feature AccessLevelOnImport"; PRODUCT_BUNDLE_IDENTIFIER = io.sentry.SentrySwiftLogTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -7904,7 +8106,6 @@ SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator xros xrsimulator"; SUPPORTS_MACCATALYST = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG SENTRY_TEST $(inherited)"; - OTHER_SWIFT_FLAGS = "-enable-experimental-feature AccessLevelOnImport"; SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -7975,6 +8176,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "-enable-experimental-feature AccessLevelOnImport"; PRODUCT_BUNDLE_IDENTIFIER = io.sentry.SentrySwiftLogTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -7983,7 +8185,6 @@ SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator xros xrsimulator"; SUPPORTS_MACCATALYST = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SENTRY_TEST"; - OTHER_SWIFT_FLAGS = "-enable-experimental-feature AccessLevelOnImport"; SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = NO; @@ -8054,6 +8255,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "-enable-experimental-feature AccessLevelOnImport"; PRODUCT_BUNDLE_IDENTIFIER = io.sentry.SentrySwiftLogTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -8062,7 +8264,6 @@ SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator xros xrsimulator"; SUPPORTS_MACCATALYST = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SENTRY_TEST_CI"; - OTHER_SWIFT_FLAGS = "-enable-experimental-feature AccessLevelOnImport"; SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = NO; @@ -8133,6 +8334,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "-enable-experimental-feature AccessLevelOnImport"; PRODUCT_BUNDLE_IDENTIFIER = io.sentry.SentrySwiftLogTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -8140,7 +8342,6 @@ STRING_CATALOG_GENERATE_SYMBOLS = NO; SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator xros xrsimulator"; SUPPORTS_MACCATALYST = YES; - OTHER_SWIFT_FLAGS = "-enable-experimental-feature AccessLevelOnImport"; SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = NO; @@ -8211,6 +8412,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "-enable-experimental-feature AccessLevelOnImport"; PRODUCT_BUNDLE_IDENTIFIER = io.sentry.SentrySwiftLogTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -8218,7 +8420,6 @@ STRING_CATALOG_GENERATE_SYMBOLS = NO; SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator xros xrsimulator"; SUPPORTS_MACCATALYST = YES; - OTHER_SWIFT_FLAGS = "-enable-experimental-feature AccessLevelOnImport"; SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = NO; @@ -8229,6 +8430,272 @@ }; name = ReleaseWithoutUIKit; }; + 92B71B702EBB7F3C00A64658 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 92B71B8A2EBB7F3C00A64658 /* SentryPulse.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + GCC_C_LANGUAGE_STANDARD = gnu17; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Sentry. All rights reserved."; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 92B71B712EBB7F3C00A64658 /* DebugWithoutUIKit */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 92B71B8A2EBB7F3C00A64658 /* SentryPulse.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + GCC_C_LANGUAGE_STANDARD = gnu17; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Sentry. All rights reserved."; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = DebugWithoutUIKit; + }; + 92B71B722EBB7F3C00A64658 /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 92B71B8A2EBB7F3C00A64658 /* SentryPulse.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + GCC_C_LANGUAGE_STANDARD = gnu17; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Sentry. All rights reserved."; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Test; + }; + 92B71B732EBB7F3C00A64658 /* TestCI */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 92B71B8A2EBB7F3C00A64658 /* SentryPulse.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + GCC_C_LANGUAGE_STANDARD = gnu17; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Sentry. All rights reserved."; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = TestCI; + }; + 92B71B742EBB7F3C00A64658 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 92B71B8A2EBB7F3C00A64658 /* SentryPulse.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + GCC_C_LANGUAGE_STANDARD = gnu17; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Sentry. All rights reserved."; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 92B71B752EBB7F3C00A64658 /* ReleaseWithoutUIKit */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 92B71B8A2EBB7F3C00A64658 /* SentryPulse.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + GCC_C_LANGUAGE_STANDARD = gnu17; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Sentry. All rights reserved."; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = ReleaseWithoutUIKit; + }; + 92B71B762EBB7F3C00A64658 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGNING_REQUIRED = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + OTHER_SWIFT_FLAGS = "-enable-experimental-feature AccessLevelOnImport"; + PRODUCT_BUNDLE_IDENTIFIER = io.sentry.SentryPulseTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 92B71B772EBB7F3C00A64658 /* DebugWithoutUIKit */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGNING_REQUIRED = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + OTHER_SWIFT_FLAGS = "-enable-experimental-feature AccessLevelOnImport"; + PRODUCT_BUNDLE_IDENTIFIER = io.sentry.SentryPulseTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = DebugWithoutUIKit; + }; + 92B71B782EBB7F3C00A64658 /* Test */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGNING_REQUIRED = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + OTHER_SWIFT_FLAGS = "-enable-experimental-feature AccessLevelOnImport"; + PRODUCT_BUNDLE_IDENTIFIER = io.sentry.SentryPulseTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Test; + }; + 92B71B792EBB7F3C00A64658 /* TestCI */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGNING_REQUIRED = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + OTHER_SWIFT_FLAGS = "-enable-experimental-feature AccessLevelOnImport"; + PRODUCT_BUNDLE_IDENTIFIER = io.sentry.SentryPulseTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = TestCI; + }; + 92B71B7A2EBB7F3C00A64658 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGNING_REQUIRED = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + OTHER_SWIFT_FLAGS = "-enable-experimental-feature AccessLevelOnImport"; + PRODUCT_BUNDLE_IDENTIFIER = io.sentry.SentryPulseTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 92B71B7B2EBB7F3C00A64658 /* ReleaseWithoutUIKit */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGNING_REQUIRED = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + OTHER_SWIFT_FLAGS = "-enable-experimental-feature AccessLevelOnImport"; + PRODUCT_BUNDLE_IDENTIFIER = io.sentry.SentryPulseTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = ReleaseWithoutUIKit; + }; D4CBA24A2DE06D0200581618 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -9218,6 +9685,32 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 92B71B812EBB7F3C00A64658 /* Build configuration list for PBXNativeTarget "SentryPulse" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 92B71B702EBB7F3C00A64658 /* Debug */, + 92B71B712EBB7F3C00A64658 /* DebugWithoutUIKit */, + 92B71B722EBB7F3C00A64658 /* Test */, + 92B71B732EBB7F3C00A64658 /* TestCI */, + 92B71B742EBB7F3C00A64658 /* Release */, + 92B71B752EBB7F3C00A64658 /* ReleaseWithoutUIKit */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 92B71B822EBB7F3C00A64658 /* Build configuration list for PBXNativeTarget "SentryPulseTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 92B71B762EBB7F3C00A64658 /* Debug */, + 92B71B772EBB7F3C00A64658 /* DebugWithoutUIKit */, + 92B71B782EBB7F3C00A64658 /* Test */, + 92B71B792EBB7F3C00A64658 /* TestCI */, + 92B71B7A2EBB7F3C00A64658 /* Release */, + 92B71B7B2EBB7F3C00A64658 /* ReleaseWithoutUIKit */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; D4CBA2502DE06D0200581618 /* Build configuration list for PBXNativeTarget "SentryTestUtilsTests" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -9273,7 +9766,7 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 9241AC452EBA39C700E611ED /* XCRemoteSwiftPackageReference "swift-log" */ = { + 9259B0BE2EBCB3620088C4A4 /* XCRemoteSwiftPackageReference "swift-log" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/apple/swift-log"; requirement = { @@ -9281,14 +9774,27 @@ minimumVersion = 1.6.0; }; }; + 9259B0C12EBCB3730088C4A4 /* XCRemoteSwiftPackageReference "Pulse" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/kean/Pulse"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.0.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 92275AA92EBA402D00F31AB9 /* Logging */ = { + 9259B0BF2EBCB3620088C4A4 /* Logging */ = { isa = XCSwiftPackageProductDependency; - package = 9241AC452EBA39C700E611ED /* XCRemoteSwiftPackageReference "swift-log" */; + package = 9259B0BE2EBCB3620088C4A4 /* XCRemoteSwiftPackageReference "swift-log" */; productName = Logging; }; + 9259B0C22EBCB3730088C4A4 /* Pulse */ = { + isa = XCSwiftPackageProductDependency; + package = 9259B0C12EBCB3730088C4A4 /* XCRemoteSwiftPackageReference "Pulse" */; + productName = Pulse; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 6327C5CA1EB8A783004E799B /* Project object */; diff --git a/Sentry.xcodeproj/xcshareddata/xcschemes/SentryPulse.xcscheme b/Sentry.xcodeproj/xcshareddata/xcschemes/SentryPulse.xcscheme new file mode 100644 index 00000000000..babca27f304 --- /dev/null +++ b/Sentry.xcodeproj/xcshareddata/xcschemes/SentryPulse.xcscheme @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/Configuration/SentryPulse.xcconfig b/Sources/Configuration/SentryPulse.xcconfig new file mode 100644 index 00000000000..cc3ff940211 --- /dev/null +++ b/Sources/Configuration/SentryPulse.xcconfig @@ -0,0 +1,26 @@ +PRODUCT_NAME = SentryPulse +CURRENT_PROJECT_VERSION = 8.57.2 + +GENERATE_INFOPLIST_FILE = YES +PRODUCT_BUNDLE_IDENTIFIER = io.sentry.$(PRODUCT_NAME) + +MACOSX_DEPLOYMENT_TARGET = 12 +IPHONEOS_DEPLOYMENT_TARGET = 15.0 +WATCHOS_DEPLOYMENT_TARGET = 8.0 +TVOS_DEPLOYMENT_TARGET = 15.0 +MACH_O_TYPE = mh_dylib + +OTHER_LDFLAGS_DebugWithoutUIKit = -framework SentryWithoutUIKit +OTHER_LDFLAGS_ReleaseWithoutUIKit = -framework SentryWithoutUIKit +OTHER_LDFLAGS_Debug = -framework Sentry +OTHER_LDFLAGS_Test = -framework Sentry +OTHER_LDFLAGS_TestCI = -framework Sentry +OTHER_LDFLAGS_Release = -framework Sentry +OTHER_LDFLAGS = $(OTHER_LDFLAGS_$(CONFIGURATION)) +ARCHS = $(ARCHS_STANDARD) +ARCHS[sdk=*simulator] = $(ARCHS_STANDARD) + +CODE_SIGN_STYLE = Manual +DEVELOPMENT_TEAM = +CODE_SIGN_IDENTITY = +PROVISIONING_PROFILE_SPECIFIER = diff --git a/Sources/SentryPulse/SentryPulse.swift b/Sources/SentryPulse/SentryPulse.swift new file mode 100644 index 00000000000..01aea439d8e --- /dev/null +++ b/Sources/SentryPulse/SentryPulse.swift @@ -0,0 +1,173 @@ +import Combine +import Pulse +import Sentry + +/// Automatically forwards Pulse log messages to Sentry's structured logging system. +/// +/// `SentryPulse` observes Pulse's `LoggerStore.events` publisher and automatically +/// forwards all `.messageCreated` events to Sentry. This provides seamless integration between +/// Pulse and Sentry without requiring any changes to existing logging code. +/// +/// ## How It Works +/// - Subscribes to Pulse's `LoggerStore.events` publisher +/// - Listens for `.messageCreated` events +/// - Automatically forwards each log message to Sentry with appropriate level mapping +/// - Preserves all metadata and source information +/// +/// ## Level Mapping +/// Pulse log levels are mapped to Sentry log levels: +/// - `.trace` → `.trace` +/// - `.debug` → `.debug` +/// - `.info` → `.info` +/// - `.notice` → `.info` (notice maps to info as SentryLog doesn't have notice) +/// - `.warning` → `.warn` +/// - `.error` → `.error` +/// - `.critical` → `.fatal` +/// +/// ## Usage +/// +/// ```swift +/// import Pulse +/// import Sentry +/// import SentryPulse +/// +/// SentrySDK.start { options in +/// options.dsn = "YOUR_DSN" +/// } +/// +/// // Setup Pulse... +/// +/// SentryPulse.start() +/// ``` +/// +/// ## Lifecycle Management +/// - Call `SentryPulse.start()` once during app initialization to enable integration +/// - Call `SentryPulse.stop()` to disable log forwarding +/// - Integration persists for the lifetime of the app (unless explicitly stopped) +/// +/// - Note: Sentry Logs is currently in Beta. See the [Sentry Logs Documentation](https://docs.sentry.io/platforms/apple/logs/). +public final class SentryPulse { + + private static var shared: SentryPulse? + private static let lock = NSLock() + + private var cancellable: AnyCancellable? + private let sentryLogger: SentryLogger + + /// Starts forwarding Pulse logs to Sentry. + /// + /// Call this method once during app initialization, after initializing the Sentry SDK. + /// The integration will remain active for the lifetime of the app (or until `stop()` is called). + /// + /// ```swift + /// import Pulse + /// import Sentry + /// import SentryPulse + /// + /// SentrySDK.start { options in + /// options.dsn = "YOUR_DSN" + /// } + /// + /// // Setup Pulse... + /// + /// SentryPulse.start() + /// ``` + /// + /// - Parameters: + /// - loggerStore: The Pulse `LoggerStore` to observe. Defaults to `.shared`. + /// + /// - Note: Calling this method multiple times has no effect. The integration is started only once. + public static func start(loggerStore: LoggerStore = .shared) { + startInternal(loggerStore: loggerStore, sentryLogger: SentrySDK.logger) + } + + // Internal method for testing + static func startInternal(loggerStore: LoggerStore, sentryLogger: SentryLogger) { + lock.lock() + defer { lock.unlock() } + + guard shared == nil else { return } + shared = SentryPulse(loggerStore: loggerStore, sentryLogger: sentryLogger) + } + + /// Stops forwarding Pulse logs to Sentry. + /// + /// After calling this method, Pulse logs will no longer be sent to Sentry. + /// You can call `start()` again to re-enable the integration. + public static func stop() { + lock.lock() + defer { lock.unlock() } + + shared?.stop() + shared = nil + } + + // Internal initializer for testing + init(loggerStore: LoggerStore, sentryLogger: SentryLogger) { + self.sentryLogger = sentryLogger + cancellable = loggerStore.events + .sink { [weak self] event in + guard let self = self else { return } + + if case .messageStored(let message) = event { + self.forwardMessageToSentry(message) + } + } + } + + func stop() { + cancellable?.cancel() + cancellable = nil + } + + deinit { + stop() + } + + // MARK: - Private Implementation + + private func forwardMessageToSentry(_ message: LoggerStore.Event.MessageCreated) { + // Build attributes with Pulse-specific metadata + var attributes: [String: Any] = [:] + attributes["sentry.origin"] = "auto.logging.pulse" + attributes["pulse.level"] = message.level.name + attributes["pulse.label"] = message.label + if !message.file.isEmpty { + attributes["pulse.file"] = message.file + } + if !message.function.isEmpty { + attributes["pulse.function"] = message.function + } + attributes["pulse.line"] = message.line + + // Add metadata from Pulse message (already converted to [String: String]) + if let metadata = message.metadata { + for (key, value) in metadata { + attributes["pulse.\(key)"] = value + } + } + + // Forward to Sentry logger with appropriate level + logToSentry(message.level, message: message.message, attributes: attributes) + } + + private func logToSentry(_ level: LoggerStore.Level, message: String, attributes: [String: Any]) { + switch level { + case .trace: + sentryLogger.trace(message, attributes: attributes) + case .debug: + sentryLogger.debug(message, attributes: attributes) + case .info: + sentryLogger.info(message, attributes: attributes) + case .notice: + // Map notice to info as SentryLog doesn't have notice + sentryLogger.info(message, attributes: attributes) + case .warning: + sentryLogger.warn(message, attributes: attributes) + case .error: + sentryLogger.error(message, attributes: attributes) + case .critical: + sentryLogger.fatal(message, attributes: attributes) + } + } +} diff --git a/Sources/Swift/Tools/SentryLogger.swift b/Sources/Swift/Tools/SentryLogger.swift index 5cae9883917..80458f07d1e 100644 --- a/Sources/Swift/Tools/SentryLogger.swift +++ b/Sources/Swift/Tools/SentryLogger.swift @@ -13,9 +13,6 @@ import Foundation /// - `Float` (converted to `Double`) /// - Other types (converted to string) /// -/// - Note: Sentry Logs is currently in Beta. See the [Sentry Logs Documentation](https://docs.sentry.io/product/explore/logs/). -/// - Warning: This API is experimental and subject to change without notice. -/// /// ## Usage /// ```swift /// let logger = SentrySDK.logger diff --git a/Tests/SentryPulseTests/SentryPulseTests.swift b/Tests/SentryPulseTests/SentryPulseTests.swift new file mode 100644 index 00000000000..44f172276ec --- /dev/null +++ b/Tests/SentryPulseTests/SentryPulseTests.swift @@ -0,0 +1,451 @@ +internal import _SentryPrivate +import Pulse +@_spi(Private) @testable import Sentry +@_spi(Private) @testable import SentryPulse +@_spi(Private) import SentryTestUtils +import XCTest + +// swiftlint:disable cyclomatic_complexity + +final class SentryPulseTests: XCTestCase { + + private class Fixture { + let hub: TestHub + let client: TestClient + let dateProvider: TestCurrentDateProvider + let options: Options + let scope: Scope + let batcher: TestLogBatcher + let sentryLogger: SentryLogger + let loggerStore: LoggerStore + + init() { + options = Options() + options.dsn = TestConstants.dsnAsString(username: "SentryPulseTests") + + client = TestClient(options: options)! + scope = Scope() + hub = TestHub(client: client, andScope: scope) + dateProvider = TestCurrentDateProvider() + batcher = TestLogBatcher(client: client, dispatchQueue: TestSentryDispatchQueueWrapper()) + + dateProvider.setDate(date: Date(timeIntervalSince1970: 1_627_846_800.123456)) + sentryLogger = SentryLogger(hub: hub, dateProvider: dateProvider, batcher: batcher) + + // Create in-memory LoggerStore for testing + let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + loggerStore = try! LoggerStore(storeURL: tempURL, options: [.inMemory]) + } + + func getSut() -> SentryPulse { + return SentryPulse(loggerStore: loggerStore, sentryLogger: sentryLogger) + } + } + + private class TestLogBatcher: SentryLogBatcher { + var addInvocations = Invocations() + + override func add(_ log: SentryLog) { + addInvocations.record(log) + } + } + + private var fixture: Fixture! + private var sut: SentryPulse! + + override func setUp() { + super.setUp() + fixture = Fixture() + sut = fixture.getSut() + } + + override func tearDown() { + super.tearDown() + clearTestState() + } + + // MARK: - Basic Logging Tests + + func testLog_WithTraceLevel() { + fixture.loggerStore.storeMessage( + label: "test", + level: .trace, + message: "Test trace message", + metadata: [:], + file: "TestFile.swift", + function: "testFunction", + line: 1 + ) + + assertLogCaptured( + .trace, + "Test trace message", + [ + "sentry.origin": SentryLog.Attribute(string: "auto.logging.pulse"), + "pulse.level": SentryLog.Attribute(string: "trace"), + "pulse.label": SentryLog.Attribute(string: "test"), + "pulse.file": SentryLog.Attribute(string: "TestFile.swift"), + "pulse.function": SentryLog.Attribute(string: "testFunction"), + "pulse.line": SentryLog.Attribute(string: "1") + ] + ) + } + + func testLog_WithDebugLevel() { + fixture.loggerStore.storeMessage( + label: "test", + level: .debug, + message: "Test debug message", + metadata: [:], + file: "TestFile.swift", + function: "testFunction", + line: 1 + ) + + assertLogCaptured( + .debug, + "Test debug message", + [ + "sentry.origin": SentryLog.Attribute(string: "auto.logging.pulse"), + "pulse.level": SentryLog.Attribute(string: "debug"), + "pulse.label": SentryLog.Attribute(string: "test"), + "pulse.file": SentryLog.Attribute(string: "TestFile.swift"), + "pulse.function": SentryLog.Attribute(string: "testFunction"), + "pulse.line": SentryLog.Attribute(string: "1") + ] + ) + } + + func testLog_WithInfoLevel() { + fixture.loggerStore.storeMessage( + label: "test", + level: .info, + message: "Test info message", + metadata: [:], + file: "TestFile.swift", + function: "testFunction", + line: 1 + ) + + assertLogCaptured( + .info, + "Test info message", + [ + "sentry.origin": SentryLog.Attribute(string: "auto.logging.pulse"), + "pulse.level": SentryLog.Attribute(string: "info"), + "pulse.label": SentryLog.Attribute(string: "test"), + "pulse.file": SentryLog.Attribute(string: "TestFile.swift"), + "pulse.function": SentryLog.Attribute(string: "testFunction"), + "pulse.line": SentryLog.Attribute(string: "1") + ] + ) + } + + func testLog_WithNoticeLevel() { + fixture.loggerStore.storeMessage( + label: "test", + level: .notice, + message: "Test notice message", + metadata: [:], + file: "TestFile.swift", + function: "testFunction", + line: 1 + ) + + assertLogCaptured( + .info, + "Test notice message", + [ + "sentry.origin": SentryLog.Attribute(string: "auto.logging.pulse"), + "pulse.level": SentryLog.Attribute(string: "notice"), + "pulse.label": SentryLog.Attribute(string: "test"), + "pulse.file": SentryLog.Attribute(string: "TestFile.swift"), + "pulse.function": SentryLog.Attribute(string: "testFunction"), + "pulse.line": SentryLog.Attribute(string: "1") + ] + ) + } + + func testLog_WithWarningLevel() { + fixture.loggerStore.storeMessage( + label: "test", + level: .warning, + message: "Test warning message", + metadata: [:], + file: "TestFile.swift", + function: "testFunction", + line: 1 + ) + + assertLogCaptured( + .warn, + "Test warning message", + [ + "sentry.origin": SentryLog.Attribute(string: "auto.logging.pulse"), + "pulse.level": SentryLog.Attribute(string: "warning"), + "pulse.label": SentryLog.Attribute(string: "test"), + "pulse.file": SentryLog.Attribute(string: "TestFile.swift"), + "pulse.function": SentryLog.Attribute(string: "testFunction"), + "pulse.line": SentryLog.Attribute(string: "1") + ] + ) + } + + func testLog_WithErrorLevel() { + fixture.loggerStore.storeMessage( + label: "test", + level: .error, + message: "Test error message", + metadata: [:], + file: "TestFile.swift", + function: "testFunction", + line: 1 + ) + + assertLogCaptured( + .error, + "Test error message", + [ + "sentry.origin": SentryLog.Attribute(string: "auto.logging.pulse"), + "pulse.level": SentryLog.Attribute(string: "error"), + "pulse.label": SentryLog.Attribute(string: "test"), + "pulse.file": SentryLog.Attribute(string: "TestFile.swift"), + "pulse.function": SentryLog.Attribute(string: "testFunction"), + "pulse.line": SentryLog.Attribute(string: "1") + ] + ) + } + + func testLog_WithCriticalLevel() { + fixture.loggerStore.storeMessage( + label: "test", + level: .critical, + message: "Test critical message", + metadata: [:], + file: "TestFile.swift", + function: "testFunction", + line: 1 + ) + + assertLogCaptured( + .fatal, + "Test critical message", + [ + "sentry.origin": SentryLog.Attribute(string: "auto.logging.pulse"), + "pulse.level": SentryLog.Attribute(string: "critical"), + "pulse.label": SentryLog.Attribute(string: "test"), + "pulse.file": SentryLog.Attribute(string: "TestFile.swift"), + "pulse.function": SentryLog.Attribute(string: "testFunction"), + "pulse.line": SentryLog.Attribute(string: "1") + ] + ) + } + + // MARK: - Metadata Tests + + func testLog_WithMetadata() { + let metadata: [String: LoggerStore.MetadataValue] = [ + "user_id": .string("12345"), + "session_id": .string("abc-def-ghi") + ] + + fixture.loggerStore.storeMessage( + label: "test", + level: .info, + message: "Test with metadata", + metadata: metadata, + file: "TestFile.swift", + function: "testFunction", + line: 1 + ) + + assertLogCaptured( + .info, + "Test with metadata", + [ + "sentry.origin": SentryLog.Attribute(string: "auto.logging.pulse"), + "pulse.level": SentryLog.Attribute(string: "info"), + "pulse.label": SentryLog.Attribute(string: "test"), + "pulse.file": SentryLog.Attribute(string: "TestFile.swift"), + "pulse.function": SentryLog.Attribute(string: "testFunction"), + "pulse.line": SentryLog.Attribute(string: "1"), + "pulse.user_id": SentryLog.Attribute(string: "12345"), + "pulse.session_id": SentryLog.Attribute(string: "abc-def-ghi") + ] + ) + } + + func testLog_WithComplexMetadata() { + let metadata: [String: LoggerStore.MetadataValue] = [ + "count": .string("42"), + "enabled": .stringConvertible(true), + "score": .stringConvertible(3.14159) + ] + + fixture.loggerStore.storeMessage( + label: "test", + level: .info, + message: "Test with complex metadata", + metadata: metadata, + file: "TestFile.swift", + function: "testFunction", + line: 1 + ) + + assertLogCaptured( + .info, + "Test with complex metadata", + [ + "sentry.origin": SentryLog.Attribute(string: "auto.logging.pulse"), + "pulse.level": SentryLog.Attribute(string: "info"), + "pulse.label": SentryLog.Attribute(string: "test"), + "pulse.file": SentryLog.Attribute(string: "TestFile.swift"), + "pulse.function": SentryLog.Attribute(string: "testFunction"), + "pulse.line": SentryLog.Attribute(string: "1"), + "pulse.count": SentryLog.Attribute(string: "42"), + "pulse.enabled": SentryLog.Attribute(string: "true"), + "pulse.score": SentryLog.Attribute(string: "3.14159") + ] + ) + } + + // MARK: - Integration Lifecycle Tests + + func testStaticAPI_CanStartAndStop() { + // Use a fresh fixture to avoid contamination from previous tests + let staticFixture = Fixture() + + // Clean up any existing integration + SentryPulse.stop() + + // Start integration with test logger + SentryPulse.startInternal(loggerStore: staticFixture.loggerStore, sentryLogger: staticFixture.sentryLogger) + + // Log should be captured + staticFixture.loggerStore.storeMessage( + label: "test", + level: .info, + message: "With static API", + metadata: [:], + file: "TestFile.swift", + function: "testFunction", + line: 1 + ) + + XCTAssertEqual(staticFixture.batcher.addInvocations.invocations.count, 1, "Should capture log after start") + + // Stop integration + SentryPulse.stop() + + // Log should NOT be captured + staticFixture.loggerStore.storeMessage( + label: "test", + level: .info, + message: "After static stop", + metadata: [:], + file: "TestFile.swift", + function: "testFunction", + line: 1 + ) + + XCTAssertEqual(staticFixture.batcher.addInvocations.invocations.count, 1, "Should not capture log after stop") + + // Restart integration + SentryPulse.startInternal(loggerStore: staticFixture.loggerStore, sentryLogger: staticFixture.sentryLogger) + + // Log should be captured again + staticFixture.loggerStore.storeMessage( + label: "test", + level: .info, + message: "After restart", + metadata: [:], + file: "TestFile.swift", + function: "testFunction", + line: 1 + ) + + XCTAssertEqual(staticFixture.batcher.addInvocations.invocations.count, 2, "Should capture log after restart") + + // Cleanup + SentryPulse.stop() + } + + // MARK: - Helper Methods + + private func assertLogCaptured( + _ expectedLevel: SentryLog.Level, + _ expectedBody: String, + _ expectedAttributes: [String: SentryLog.Attribute], + file: StaticString = #file, + line: UInt = #line + ) { + let logs = fixture.batcher.addInvocations.invocations + XCTAssertEqual(logs.count, 1, "Expected exactly one log to be captured", file: file, line: line) + + guard let capturedLog = logs.first else { + XCTFail("No log captured", file: file, line: line) + return + } + + XCTAssertEqual(capturedLog.level, expectedLevel, "Log level mismatch", file: file, line: line) + XCTAssertEqual(capturedLog.body, expectedBody, "Log body mismatch", file: file, line: line) + XCTAssertEqual(capturedLog.timestamp, fixture.dateProvider.date(), "Log timestamp mismatch", file: file, line: line) + + // Count expected default attributes dynamically + var expectedDefaultAttributeCount = 3 // sdk.name, sdk.version, environment are always present + if fixture.options.releaseName != nil { + expectedDefaultAttributeCount += 1 // sentry.release + } + if fixture.hub.scope.span != nil { + expectedDefaultAttributeCount += 1 // sentry.trace.parent_span_id + } + // OS and device attributes (up to 5 more if context is available) + if let contextDictionary = fixture.hub.scope.serialize()["context"] as? [String: [String: Any]] { + if let osContext = contextDictionary["os"] { + if osContext["name"] != nil { expectedDefaultAttributeCount += 1 } + if osContext["version"] != nil { expectedDefaultAttributeCount += 1 } + } + if contextDictionary["device"] != nil { + expectedDefaultAttributeCount += 1 // device.brand (always "Apple") + if let deviceContext = contextDictionary["device"] { + if deviceContext["model"] != nil { expectedDefaultAttributeCount += 1 } + if deviceContext["family"] != nil { expectedDefaultAttributeCount += 1 } + } + } + } + + // Compare attributes + XCTAssertEqual(capturedLog.attributes.count, expectedAttributes.count + expectedDefaultAttributeCount, "Attribute count mismatch", file: file, line: line) + + for (key, expectedAttribute) in expectedAttributes { + guard let actualAttribute = capturedLog.attributes[key] else { + XCTFail("Missing attribute key: \(key)", file: file, line: line) + continue + } + + XCTAssertEqual(actualAttribute.type, expectedAttribute.type, "Attribute type mismatch for key: \(key)", file: file, line: line) + + // Compare values based on type + switch expectedAttribute.type { + case "string": + let expectedValue = expectedAttribute.value as! String + let actualValue = actualAttribute.value as! String + XCTAssertEqual(actualValue, expectedValue, "String attribute value mismatch for key: \(key)", file: file, line: line) + case "boolean": + let expectedValue = expectedAttribute.value as! Bool + let actualValue = actualAttribute.value as! Bool + XCTAssertEqual(actualValue, expectedValue, "Boolean attribute value mismatch for key: \(key)", file: file, line: line) + case "integer": + let expectedValue = expectedAttribute.value as! Int + let actualValue = actualAttribute.value as! Int + XCTAssertEqual(actualValue, expectedValue, "Integer attribute value mismatch for key: \(key)", file: file, line: line) + case "double": + let expectedValue = expectedAttribute.value as! Double + let actualValue = actualAttribute.value as! Double + XCTAssertEqual(actualValue, expectedValue, accuracy: 0.000001, "Double attribute value mismatch for key: \(key)", file: file, line: line) + default: + XCTFail("Unknown attribute type for key: \(key). Type: \(expectedAttribute.type)", file: file, line: line) + } + } + } +} diff --git a/Utils/VersionBump/main.swift b/Utils/VersionBump/main.swift index 928c7f3e4f4..a06d680bc29 100644 --- a/Utils/VersionBump/main.swift +++ b/Utils/VersionBump/main.swift @@ -51,7 +51,8 @@ let restrictFiles = [ "./Sources/Configuration/SDK.xcconfig", "./Sources/Configuration/Versioning.xcconfig", "./Sources/Configuration/SentrySwiftUI.xcconfig", - "./Sources/Configuration/SentrySwiftLog.xcconfig" + "./Sources/Configuration/SentrySwiftLog.xcconfig", + "./Sources/Configuration/SentryPulse.xcconfig" ] let args = CommandLine.arguments