diff --git a/ObservabilityiOSTestApp/ObservabilityiOSTestApp.xcodeproj/project.pbxproj b/ExampleApp/ExampleApp.xcodeproj/project.pbxproj similarity index 62% rename from ObservabilityiOSTestApp/ObservabilityiOSTestApp.xcodeproj/project.pbxproj rename to ExampleApp/ExampleApp.xcodeproj/project.pbxproj index 8d9aaea..7201a90 100644 --- a/ObservabilityiOSTestApp/ObservabilityiOSTestApp.xcodeproj/project.pbxproj +++ b/ExampleApp/ExampleApp.xcodeproj/project.pbxproj @@ -7,47 +7,47 @@ objects = { /* Begin PBXBuildFile section */ - E7304B532E59332E00171341 /* LaunchDarklyObservability in Frameworks */ = {isa = PBXBuildFile; productRef = E7304B522E59332E00171341 /* LaunchDarklyObservability */; }; - E7DF64352E58CF430003BA29 /* LaunchDarklyObservability in Frameworks */ = {isa = PBXBuildFile; productRef = E7DF64342E58CF430003BA29 /* LaunchDarklyObservability */; }; + E7904AD42E6A528B00A15337 /* LaunchDarkly in Frameworks */ = {isa = PBXBuildFile; productRef = E7904AD32E6A528B00A15337 /* LaunchDarkly */; }; + E7904AD72E6A52CE00A15337 /* LaunchDarklyObservability in Frameworks */ = {isa = PBXBuildFile; productRef = E7904AD62E6A52CE00A15337 /* LaunchDarklyObservability */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - E7DF641D2E58CA090003BA29 /* ObservabilityiOSTestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ObservabilityiOSTestApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + E7904AC42E6A523D00A15337 /* ExampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ExampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ - E7DF641F2E58CA090003BA29 /* ObservabilityiOSTestApp */ = { + E7904AC62E6A523D00A15337 /* ExampleApp */ = { isa = PBXFileSystemSynchronizedRootGroup; - path = ObservabilityiOSTestApp; + path = ExampleApp; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ - E7DF641A2E58CA090003BA29 /* Frameworks */ = { + E7904AC12E6A523D00A15337 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E7304B532E59332E00171341 /* LaunchDarklyObservability in Frameworks */, - E7DF64352E58CF430003BA29 /* LaunchDarklyObservability in Frameworks */, + E7904AD72E6A52CE00A15337 /* LaunchDarklyObservability in Frameworks */, + E7904AD42E6A528B00A15337 /* LaunchDarkly in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - E7DF64142E58CA090003BA29 = { + E7904ABB2E6A523D00A15337 = { isa = PBXGroup; children = ( - E7DF641F2E58CA090003BA29 /* ObservabilityiOSTestApp */, - E7DF641E2E58CA090003BA29 /* Products */, + E7904AC62E6A523D00A15337 /* ExampleApp */, + E7904AC52E6A523D00A15337 /* Products */, ); sourceTree = ""; }; - E7DF641E2E58CA090003BA29 /* Products */ = { + E7904AC52E6A523D00A15337 /* Products */ = { isa = PBXGroup; children = ( - E7DF641D2E58CA090003BA29 /* ObservabilityiOSTestApp.app */, + E7904AC42E6A523D00A15337 /* ExampleApp.app */, ); name = Products; sourceTree = ""; @@ -55,69 +55,70 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - E7DF641C2E58CA090003BA29 /* ObservabilityiOSTestApp */ = { + E7904AC32E6A523D00A15337 /* ExampleApp */ = { isa = PBXNativeTarget; - buildConfigurationList = E7DF64292E58CA0B0003BA29 /* Build configuration list for PBXNativeTarget "ObservabilityiOSTestApp" */; + buildConfigurationList = E7904ACF2E6A523E00A15337 /* Build configuration list for PBXNativeTarget "ExampleApp" */; buildPhases = ( - E7DF64192E58CA090003BA29 /* Sources */, - E7DF641A2E58CA090003BA29 /* Frameworks */, - E7DF641B2E58CA090003BA29 /* Resources */, + E7904AC02E6A523D00A15337 /* Sources */, + E7904AC12E6A523D00A15337 /* Frameworks */, + E7904AC22E6A523D00A15337 /* Resources */, ); buildRules = ( ); dependencies = ( ); fileSystemSynchronizedGroups = ( - E7DF641F2E58CA090003BA29 /* ObservabilityiOSTestApp */, + E7904AC62E6A523D00A15337 /* ExampleApp */, ); - name = ObservabilityiOSTestApp; + name = ExampleApp; packageProductDependencies = ( - E7DF64342E58CF430003BA29 /* LaunchDarklyObservability */, - E7304B522E59332E00171341 /* LaunchDarklyObservability */, + E7904AD32E6A528B00A15337 /* LaunchDarkly */, + E7904AD62E6A52CE00A15337 /* LaunchDarklyObservability */, ); - productName = ObservabilityiOSTestApp; - productReference = E7DF641D2E58CA090003BA29 /* ObservabilityiOSTestApp.app */; + productName = ExampleApp; + productReference = E7904AC42E6A523D00A15337 /* ExampleApp.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - E7DF64152E58CA090003BA29 /* Project object */ = { + E7904ABC2E6A523D00A15337 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1640; LastUpgradeCheck = 1640; TargetAttributes = { - E7DF641C2E58CA090003BA29 = { + E7904AC32E6A523D00A15337 = { CreatedOnToolsVersion = 16.4; }; }; }; - buildConfigurationList = E7DF64182E58CA090003BA29 /* Build configuration list for PBXProject "ObservabilityiOSTestApp" */; + buildConfigurationList = E7904ABF2E6A523D00A15337 /* Build configuration list for PBXProject "ExampleApp" */; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); - mainGroup = E7DF64142E58CA090003BA29; + mainGroup = E7904ABB2E6A523D00A15337; minimizedProjectReferenceProxies = 1; packageReferences = ( - E7304B512E59332E00171341 /* XCLocalSwiftPackageReference "../../swift-launchdarkly-observability" */, + E7904AD22E6A528A00A15337 /* XCRemoteSwiftPackageReference "ios-client-sdk" */, + E7904AD52E6A52CE00A15337 /* XCLocalSwiftPackageReference "../../swift-launchdarkly-observability" */, ); preferredProjectObjectVersion = 77; - productRefGroup = E7DF641E2E58CA090003BA29 /* Products */; + productRefGroup = E7904AC52E6A523D00A15337 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - E7DF641C2E58CA090003BA29 /* ObservabilityiOSTestApp */, + E7904AC32E6A523D00A15337 /* ExampleApp */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - E7DF641B2E58CA090003BA29 /* Resources */ = { + E7904AC22E6A523D00A15337 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -127,7 +128,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - E7DF64192E58CA090003BA29 /* Sources */ = { + E7904AC02E6A523D00A15337 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -137,7 +138,7 @@ /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ - E7DF64272E58CA0B0003BA29 /* Debug */ = { + E7904ACD2E6A523E00A15337 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -189,16 +190,18 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; - E7DF64282E58CA0B0003BA29 /* Release */ = { + E7904ACE2E6A523E00A15337 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -244,104 +247,87 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; + SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; }; name = Release; }; - E7DF642A2E58CA0B0003BA29 /* Debug */ = { + E7904AD02E6A523E00A15337 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = ObservabilityiOSTestApp/ObservabilityiOSTestApp.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; - "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; - "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; - "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; - "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; - "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; - "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; - "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; - "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 18.5; - LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; - "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 15.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.launchdarkly.test.ObservabilityiOSTestApp; + PRODUCT_BUNDLE_IDENTIFIER = com.launchdarkly..ExampleApp; PRODUCT_NAME = "$(TARGET_NAME)"; - REGISTER_APP_GROUPS = YES; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,7"; - XROS_DEPLOYMENT_TARGET = 2.5; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; - E7DF642B2E58CA0B0003BA29 /* Release */ = { + E7904AD12E6A523E00A15337 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = ObservabilityiOSTestApp/ObservabilityiOSTestApp.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; - "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; - "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; - "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; - "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; - "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; - "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; - "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; - "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 18.5; - LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; - "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 15.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.launchdarkly.test.ObservabilityiOSTestApp; + PRODUCT_BUNDLE_IDENTIFIER = com.launchdarkly..ExampleApp; PRODUCT_NAME = "$(TARGET_NAME)"; - REGISTER_APP_GROUPS = YES; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,7"; - XROS_DEPLOYMENT_TARGET = 2.5; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - E7DF64182E58CA090003BA29 /* Build configuration list for PBXProject "ObservabilityiOSTestApp" */ = { + E7904ABF2E6A523D00A15337 /* Build configuration list for PBXProject "ExampleApp" */ = { isa = XCConfigurationList; buildConfigurations = ( - E7DF64272E58CA0B0003BA29 /* Debug */, - E7DF64282E58CA0B0003BA29 /* Release */, + E7904ACD2E6A523E00A15337 /* Debug */, + E7904ACE2E6A523E00A15337 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - E7DF64292E58CA0B0003BA29 /* Build configuration list for PBXNativeTarget "ObservabilityiOSTestApp" */ = { + E7904ACF2E6A523E00A15337 /* Build configuration list for PBXNativeTarget "ExampleApp" */ = { isa = XCConfigurationList; buildConfigurations = ( - E7DF642A2E58CA0B0003BA29 /* Debug */, - E7DF642B2E58CA0B0003BA29 /* Release */, + E7904AD02E6A523E00A15337 /* Debug */, + E7904AD12E6A523E00A15337 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -349,22 +335,34 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - E7304B512E59332E00171341 /* XCLocalSwiftPackageReference "../../swift-launchdarkly-observability" */ = { + E7904AD52E6A52CE00A15337 /* XCLocalSwiftPackageReference "../../swift-launchdarkly-observability" */ = { isa = XCLocalSwiftPackageReference; relativePath = "../../swift-launchdarkly-observability"; }; /* End XCLocalSwiftPackageReference section */ +/* Begin XCRemoteSwiftPackageReference section */ + E7904AD22E6A528A00A15337 /* XCRemoteSwiftPackageReference "ios-client-sdk" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/launchdarkly/ios-client-sdk.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 9.15.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + /* Begin XCSwiftPackageProductDependency section */ - E7304B522E59332E00171341 /* LaunchDarklyObservability */ = { + E7904AD32E6A528B00A15337 /* LaunchDarkly */ = { isa = XCSwiftPackageProductDependency; - productName = LaunchDarklyObservability; + package = E7904AD22E6A528A00A15337 /* XCRemoteSwiftPackageReference "ios-client-sdk" */; + productName = LaunchDarkly; }; - E7DF64342E58CF430003BA29 /* LaunchDarklyObservability */ = { + E7904AD62E6A52CE00A15337 /* LaunchDarklyObservability */ = { isa = XCSwiftPackageProductDependency; productName = LaunchDarklyObservability; }; /* End XCSwiftPackageProductDependency section */ }; - rootObject = E7DF64152E58CA090003BA29 /* Project object */; + rootObject = E7904ABC2E6A523D00A15337 /* Project object */; } diff --git a/ObservabilityiOSTestApp/ObservabilityiOSTestApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ExampleApp/ExampleApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from ObservabilityiOSTestApp/ObservabilityiOSTestApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to ExampleApp/ExampleApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/ObservabilityiOSTestApp/ObservabilityiOSTestApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ExampleApp/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved similarity index 94% rename from ObservabilityiOSTestApp/ObservabilityiOSTestApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved rename to ExampleApp/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5b327c8..c08a5ec 100644 --- a/ObservabilityiOSTestApp/ObservabilityiOSTestApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ExampleApp/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "4a5cccf0a865c6d66690b969da8d793114025347075398e0d7fe81080a7357cd", + "originHash" : "3d3d89bbf615611a1c3f621477012eafd151e17d3368be33ef104ea60601496c", "pins" : [ { "identity" : "datacompression", @@ -96,8 +96,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-certificates.git", "state" : { - "revision" : "c059d9c9d08d6654b9a92dda93d9049a278964c6", - "version" : "1.12.0" + "revision" : "20c451f1ad8e344e61ddbb34ef196653d4b73ea6", + "version" : "1.13.0" } }, { @@ -114,8 +114,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-crypto.git", "state" : { - "revision" : "334e682869394ee239a57dbe9262bff3cd9495bd", - "version" : "3.14.0" + "revision" : "d1c6b70f7c5f19fb0b8750cb8dcdf2ea6e2d8c34", + "version" : "3.15.0" } }, { @@ -195,8 +195,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-ssl.git", "state" : { - "revision" : "385f5bd783ffbfff46b246a7db7be8e4f04c53bd", - "version" : "2.33.0" + "revision" : "737e550e607d82bf15bdfddf158ec61652ce836f", + "version" : "2.34.0" } }, { @@ -213,8 +213,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-numerics.git", "state" : { - "revision" : "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8", - "version" : "1.0.3" + "revision" : "bbadd4b853a33fd78c4ae977d17bb2af15eb3f2a", + "version" : "1.1.0" } }, { diff --git a/ObservabilityiOSTestApp/ObservabilityiOSTestApp/ObservabilityiOSTestAppApp.swift b/ExampleApp/ExampleApp/AppDelegate.swift similarity index 77% rename from ObservabilityiOSTestApp/ObservabilityiOSTestApp/ObservabilityiOSTestAppApp.swift rename to ExampleApp/ExampleApp/AppDelegate.swift index 8a3225f..65d7f41 100644 --- a/ObservabilityiOSTestApp/ObservabilityiOSTestApp/ObservabilityiOSTestAppApp.swift +++ b/ExampleApp/ExampleApp/AppDelegate.swift @@ -1,8 +1,7 @@ -import SwiftUI +import UIKit import LaunchDarkly -import Plugin +import LaunchDarklyObservability -//let mobileKey = "mob-dbe6f0ac-80ce-4903-bf20-431c2e7aeae1" let mobileKey = "mob-48fd3788-eab7-4b72-b607-e41712049dbd" let config = { () -> LDConfig in var config = LDConfig( @@ -10,7 +9,7 @@ let config = { () -> LDConfig in autoEnvAttributes: .enabled ) config.plugins = [ - Observability() + Observability(options: .init(sessionBackgroundTimeout: 3)) ] return config }() @@ -51,15 +50,3 @@ final class AppDelegate: NSObject, UIApplicationDelegate { return true } } - -@main -struct ObservabilityiOSTestAppApp: App { - @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate - - var body: some Scene { - WindowGroup { -// ContentView() - HomeView() - } - } -} diff --git a/ObservabilityiOSTestApp/ObservabilityiOSTestApp/Assets.xcassets/AccentColor.colorset/Contents.json b/ExampleApp/ExampleApp/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from ObservabilityiOSTestApp/ObservabilityiOSTestApp/Assets.xcassets/AccentColor.colorset/Contents.json rename to ExampleApp/ExampleApp/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/ExampleApp/ExampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/ExampleApp/ExampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2305880 --- /dev/null +++ b/ExampleApp/ExampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ObservabilityiOSTestApp/ObservabilityiOSTestApp/Assets.xcassets/Contents.json b/ExampleApp/ExampleApp/Assets.xcassets/Contents.json similarity index 100% rename from ObservabilityiOSTestApp/ObservabilityiOSTestApp/Assets.xcassets/Contents.json rename to ExampleApp/ExampleApp/Assets.xcassets/Contents.json diff --git a/ExampleApp/ExampleApp/ContentView.swift b/ExampleApp/ExampleApp/ContentView.swift new file mode 100644 index 0000000..6255cf4 --- /dev/null +++ b/ExampleApp/ExampleApp/ContentView.swift @@ -0,0 +1,27 @@ +// +// ContentView.swift +// ExampleApp +// +// Created by Mario Canto on 04/09/25. +// + +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack(spacing: 32) { + Button { + fatalError() + } label: { + Text("Crash") + } + NetworkRequestView() + FeatureFlagView() + } + .padding() + } +} + +#Preview { + ContentView() +} diff --git a/ExampleApp/ExampleApp/ExampleAppApp.swift b/ExampleApp/ExampleApp/ExampleAppApp.swift new file mode 100644 index 0000000..f274671 --- /dev/null +++ b/ExampleApp/ExampleApp/ExampleAppApp.swift @@ -0,0 +1,12 @@ +import SwiftUI + +@main +struct ExampleAppApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/ObservabilityiOSTestApp/ObservabilityiOSTestApp/HomeView.swift b/ExampleApp/ExampleApp/FeatureFlagView.swift similarity index 52% rename from ObservabilityiOSTestApp/ObservabilityiOSTestApp/HomeView.swift rename to ExampleApp/ExampleApp/FeatureFlagView.swift index b08e13f..3264455 100644 --- a/ObservabilityiOSTestApp/ObservabilityiOSTestApp/HomeView.swift +++ b/ExampleApp/ExampleApp/FeatureFlagView.swift @@ -1,21 +1,20 @@ import SwiftUI import LaunchDarkly -import LaunchDarklyObservability -private let featureFlag = "new-home-experience" -struct HomeView: View { +struct FeatureFlagView: View { + private let featureFlag = "new-home-experience" @State private var isNewExperience = Optional.none var body: some View { - NavigationStack { - if isNewExperience == true { - HomeNewView() - } else if isNewExperience == false { - HomeOldView() + Group { + if let isNewExperience { + Text("Feature flag is: \(isNewExperience)") } else { - Text("Default Home View") + Text("Feature flag is nil...") } } + .font(.title) + .bold() .onAppear { isNewExperience = LDClient.get()?.boolVariation( forKey: featureFlag, @@ -23,4 +22,5 @@ struct HomeView: View { ) } } + } diff --git a/ExampleApp/ExampleApp/NetworkRequestView.swift b/ExampleApp/ExampleApp/NetworkRequestView.swift new file mode 100644 index 0000000..95186c0 --- /dev/null +++ b/ExampleApp/ExampleApp/NetworkRequestView.swift @@ -0,0 +1,24 @@ +import SwiftUI + +struct NetworkRequestView: View { + @State private var loading = false + var body: some View { + Group { + if loading { + ProgressView { + Text("Loading content that will be instrumented") + } + } else { + Text("Check the LaunchDarkly dashboard for instrumentation") + } + } + .task { + guard let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1") else { return } + do { + let (_, _) = try await URLSession.shared.data(from: url) + } catch { + + } + } + } +} diff --git a/ObservabilityiOSTestApp/ObservabilityiOSTestApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/ObservabilityiOSTestApp/ObservabilityiOSTestApp/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index ffdfe15..0000000 --- a/ObservabilityiOSTestApp/ObservabilityiOSTestApp/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "tinted" - } - ], - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "16x16" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "16x16" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "32x32" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "32x32" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "128x128" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "128x128" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "256x256" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "256x256" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "512x512" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "512x512" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ObservabilityiOSTestApp/ObservabilityiOSTestApp/ContentView.swift b/ObservabilityiOSTestApp/ObservabilityiOSTestApp/ContentView.swift deleted file mode 100644 index ab9627e..0000000 --- a/ObservabilityiOSTestApp/ObservabilityiOSTestApp/ContentView.swift +++ /dev/null @@ -1,147 +0,0 @@ -import SwiftUI -import LaunchDarklyObservability -import LaunchDarkly - -enum Failure: LocalizedError { - case test - case crash - - var errorDescription: String? { - switch self { - case .test: - return "this is a test error" - case .crash: - return "this is a crash error" - } - } - -} - -struct ContentView: View { - - @State private var isDarkModeEnabled: Bool = false - @State private var buttonPressed: Bool = false - @State private var errorPressed: Bool = false - @State private var counterMetricPressed: Bool = false - @State private var logsPressed: Bool = false - @State private var crashPressed: Bool = false - @State private var networkPressed: Bool = false - - var body: some View { - VStack(spacing: 32) { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") - Button { - buttonPressed.toggle() - } label: { - Text("span") - } - .buttonStyle(.borderedProminent) - Button { - logsPressed.toggle() - } label: { - Text("logs") - } - .buttonStyle(.borderedProminent) - Button { - counterMetricPressed.toggle() - } label: { - Text("metric: counter") - } - .buttonStyle(.borderedProminent) - Button { - networkPressed.toggle() - } label: { - if networkPressed { - ProgressView { - Text("get request to launchdarkly.com...") - } - } else { - Text("network request: span") - } - } - .buttonStyle(.borderedProminent) - .disabled(networkPressed) - Button { - errorPressed.toggle() - } label: { - Text("error") - } - .buttonStyle(.borderedProminent) - .tint(.red) - Button { - crashPressed.toggle() - } label: { - Text("Crash") - } - .buttonStyle(.borderedProminent) - .tint(.red) - - } - .padding() - .task(id: errorPressed) { - guard errorPressed else { return } - LDObserve.shared.recordError( - error: Failure.crash, - attributes: [:]) - errorPressed.toggle() - } - .task(id: buttonPressed) { - guard buttonPressed else { return } - let aSpan = LDObserve.shared.startSpan( - name: "button-pressed", - attributes: [:] - ) - LDClient.get()?.boolVariation( - forKey: "my-feature", - defaultValue: false - ) - - aSpan.end() - buttonPressed.toggle() - } - .task(id: counterMetricPressed) { - guard counterMetricPressed else { return } - LDObserve.shared.recordCount( - metric: .init( - name: "press-count", - value: 1, - timestamp: .now - ) - ) - counterMetricPressed.toggle() - } - .task(id: logsPressed) { - guard logsPressed else { return } - LDObserve.shared.recordLog( - message: "logs-button-pressed", - severity: .info - , attributes: [:]) - logsPressed.toggle() - } - .task(id: crashPressed) { - guard crashPressed else { return } - - fatalError() - - crashPressed.toggle() - } - .task(id: networkPressed) { - guard networkPressed else { return } - - let url = URL(string: "https://launchdarkly.com/")! - do { - let (data, urlResponse) = try await URLSession.shared.data(from: url) - networkPressed.toggle() - } catch { - networkPressed.toggle() - } - } - } -} - -#Preview { - ContentView() -} diff --git a/ObservabilityiOSTestApp/ObservabilityiOSTestApp/HomeNewView.swift b/ObservabilityiOSTestApp/ObservabilityiOSTestApp/HomeNewView.swift deleted file mode 100644 index 4444f48..0000000 --- a/ObservabilityiOSTestApp/ObservabilityiOSTestApp/HomeNewView.swift +++ /dev/null @@ -1,7 +0,0 @@ -import SwiftUI - -struct HomeNewView: View { - var body: some View { - Text("New Home Experience") - } -} diff --git a/ObservabilityiOSTestApp/ObservabilityiOSTestApp/HomeOldView.swift b/ObservabilityiOSTestApp/ObservabilityiOSTestApp/HomeOldView.swift deleted file mode 100644 index 95c9353..0000000 --- a/ObservabilityiOSTestApp/ObservabilityiOSTestApp/HomeOldView.swift +++ /dev/null @@ -1,7 +0,0 @@ -import SwiftUI - -struct HomeOldView: View { - var body: some View { - Text("Old Home Experience") - } -} diff --git a/ObservabilityiOSTestApp/ObservabilityiOSTestApp/ObservabilityiOSTestApp.entitlements b/ObservabilityiOSTestApp/ObservabilityiOSTestApp/ObservabilityiOSTestApp.entitlements deleted file mode 100644 index f2ef3ae..0000000 --- a/ObservabilityiOSTestApp/ObservabilityiOSTestApp/ObservabilityiOSTestApp.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-only - - - diff --git a/Package.swift b/Package.swift index 51cdf3c..1d0b466 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.1 +// swift-tools-version: 5.9 import PackageDescription let package = Package( @@ -7,82 +7,52 @@ let package = Package( products: [ .library( name: "LaunchDarklyObservability", - targets: ["LaunchDarklyObservability", "Client", "Plugin"]), + targets: ["LaunchDarklyObservability"]), ], dependencies: [ .package(url: "https://github.com/open-telemetry/opentelemetry-swift", from: "2.0.0"), .package(url: "https://github.com/launchdarkly/ios-client-sdk.git", from: "9.15.0"), - .package(url: "https://github.com/kstenerud/KSCrash.git", .upToNextMajor(from: "2.3.0")), + .package(url: "https://github.com/kstenerud/KSCrash.git", from: "2.3.0"), ], targets: [ .target(name: "Common"), .target( - name: "Interfaces", + name: "API", dependencies: [ - .product(name: "OpenTelemetryApi", package: "opentelemetry-swift"), .product(name: "OpenTelemetrySdk", package: "opentelemetry-swift"), + .product(name: "ResourceExtension", package: "opentelemetry-swift"), ] ), + .target(name: "CrashReporter"), .target( - name: "API", + name: "CrashReporterLive", dependencies: [ + "CrashReporter", + "Common", .product(name: "OpenTelemetryApi", package: "opentelemetry-swift"), .product(name: "OpenTelemetrySdk", package: "opentelemetry-swift"), - .product(name: "ResourceExtension", package: "opentelemetry-swift"), + .product(name: "Installations", package: "KSCrash") ] ), .target( - name: "Client", + name: "Observability", dependencies: [ - "API", - "Interfaces", "Common", + "API", "CrashReporter", "CrashReporterLive", - .product(name: "OpenTelemetryApi", package: "opentelemetry-swift"), .product(name: "OpenTelemetrySdk", package: "opentelemetry-swift"), .product(name: "OpenTelemetryProtocolExporterHTTP", package: "opentelemetry-swift"), - .product(name: "StdoutExporter", package: "opentelemetry-swift"), .product(name: "URLSessionInstrumentation", package: "opentelemetry-swift"), ] ), - .target( - name: "Plugin", - dependencies: [ - "API", - "Interfaces", - "Client", - "LaunchDarklyObservability", - .product(name: "OpenTelemetryApi", package: "opentelemetry-swift"), - .product(name: "OpenTelemetrySdk", package: "opentelemetry-swift"), - ] - ), .target( name: "LaunchDarklyObservability", dependencies: [ + "Observability", "API", - "Interfaces", - "Client", - .product(name: "OpenTelemetryApi", package: "opentelemetry-swift"), - .product(name: "OpenTelemetrySdk", package: "opentelemetry-swift"), - .product(name: "LaunchDarkly", package: "ios-client-sdk"), - ] - ), - .testTarget( - name: "LaunchDarklyObservabilityTests", - dependencies: ["LaunchDarklyObservability"] - ), - .target( - name: "CrashReporter" - ), - .target( - name: "CrashReporterLive", - dependencies: [ - "CrashReporter", "Common", - .product(name: "OpenTelemetryApi", package: "opentelemetry-swift"), - .product(name: "OpenTelemetrySdk", package: "opentelemetry-swift"), - .product(name: "Installations", package: "KSCrash") + .product(name: "LaunchDarkly", package: "ios-client-sdk"), ] ) ] diff --git a/Sources/Interfaces/Metric.swift b/Sources/API/Metric.swift similarity index 100% rename from Sources/Interfaces/Metric.swift rename to Sources/API/Metric.swift diff --git a/Sources/Interfaces/Observe.swift b/Sources/API/Observe.swift similarity index 100% rename from Sources/Interfaces/Observe.swift rename to Sources/API/Observe.swift diff --git a/Sources/Client/SessionManager.swift b/Sources/Client/SessionManager.swift deleted file mode 100644 index 3421a27..0000000 --- a/Sources/Client/SessionManager.swift +++ /dev/null @@ -1,146 +0,0 @@ -import UIKit.UIApplication -import Combine - -public final class SessionManager { - private var id: String - private var startTime: Date - private var backgroundTime: Date? - private var options: SessionOptions - private var cancellables = Set() - var sessionAttributes: [String: String] { - [ - "session.id": id, - "session.start_time": String(format: "%.0f", startTime.timeIntervalSince1970) - ] - } - private var appState = AppLifecycleState.unattached - - var sessionInfo: SessionInfo { - .init( - id: id, - startTime: startTime - ) - } - - init( - id: String = UUID().uuidString, - startTime: Date = Date.now, - options: SessionOptions - ) { - self.id = id - self.startTime = startTime - self.options = options - } - - func onDidFinishLaunching( - _ handler: (@Sendable () -> Void)? - ) { - NotificationCenter.default.publisher(for: UIApplication.didFinishLaunchingNotification) - .subscribe(on: RunLoop.main) - .receive(on: RunLoop.main) - .sink { _ in - handler?() - } - .store(in: &cancellables) - } - - func onWillTerminate( - _ handler: (() -> Void)?, - ) { - NotificationCenter.default.publisher(for: UIApplication.willTerminateNotification) - .subscribe(on: RunLoop.main) - .receive(on: RunLoop.main) - .sink { _ in - handler?() - } - .store(in: &cancellables) - } - - func start( - onWillEndSession: ((_ sessionId: String) -> Void)?, - onDidStartSession: ((_ sessionId: String) -> Void)? - ) { - guard let onWillEndSession, let onDidStartSession else { return } - - Publishers.MergeMany( - NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification), - NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification) - ) - .subscribe(on: RunLoop.main) - .receive(on: RunLoop.main) - .sink { [weak self] notification in - guard let self else { return } - let oldAppState = self.appState - let newState = update(state: oldAppState, message: notification.name) - self.appState = newState - switch newState { - case .foregroundActive: - guard AppLifecycleState.notInForeground.contains(oldAppState), let backgroundTime = self.backgroundTime else { return } - let timeInBackground = Date.now.timeIntervalSince1970 - backgroundTime.timeIntervalSince1970 - if timeInBackground >= self.options.timeout { - onWillEndSession(self.sessionInfo.id) - self.resetSession() - onDidStartSession(self.sessionInfo.id) - } - self.backgroundTime = nil - case .foregroundInactive: - break - case .background, .unattached, .suspended: - self.backgroundTime = Date.now - } - } - .store(in: &cancellables) - } - - private func resetSession() { - let oldSessionId = sessionInfo.id - let newSessionId = UUID().uuidString - let oldStartTime = startTime - - id = newSessionId - let newStartTime = Date.now - startTime = newStartTime - - if options.isDebug { - print("🔄 Session reset: \(oldSessionId) -> \(sessionInfo.id)") - let dateInterval = DateInterval(start: oldStartTime, end: newStartTime) - print("Session duration: \(dateInterval.duration) seconds") - } - } -} - -enum AppLifecycleState { - case unattached - case foregroundInactive - case foregroundActive - case suspended - case background - - static let notInForeground: [Self] = [ - .unattached, .suspended, .background - ] -} -private func update( - state: AppLifecycleState, - message: Notification.Name -) -> AppLifecycleState { - switch message { - case UIApplication.didFinishLaunchingNotification: - return .foregroundInactive - - case UIApplication.willResignActiveNotification: - return .background - case UIApplication.didEnterBackgroundNotification: - return .background - - case UIApplication.willEnterForegroundNotification: - return .foregroundInactive - case UIApplication.didBecomeActiveNotification: - return .foregroundActive - - case UIApplication.willTerminateNotification: - return .suspended - default: - return state - } -} diff --git a/Sources/CrashReporter/Models/Crash/AppMemory.swift b/Sources/CrashReporter/Models/Crash/AppMemory.swift deleted file mode 100644 index 6db215c..0000000 --- a/Sources/CrashReporter/Models/Crash/AppMemory.swift +++ /dev/null @@ -1,29 +0,0 @@ -import Foundation - -// MARK: - AppMemory -public struct AppMemory: Codable, Hashable, Sendable { - public let memoryFootprint: Int - public let memoryRemaining: Int - public let memoryPressure: String - public let memoryLevel: String - public let memoryLimit: Int - public let appTransitionState: String - - public enum CodingKeys: String, CodingKey { - case memoryFootprint = "memory_footprint" - case memoryRemaining = "memory_remaining" - case memoryPressure = "memory_pressure" - case memoryLevel = "memory_level" - case memoryLimit = "memory_limit" - case appTransitionState = "app_transition_state" - } - - public init(memoryFootprint: Int, memoryRemaining: Int, memoryPressure: String, memoryLevel: String, memoryLimit: Int, appTransitionState: String) { - self.memoryFootprint = memoryFootprint - self.memoryRemaining = memoryRemaining - self.memoryPressure = memoryPressure - self.memoryLevel = memoryLevel - self.memoryLimit = memoryLimit - self.appTransitionState = appTransitionState - } -} diff --git a/Sources/CrashReporter/Models/Crash/ApplicationStats.swift b/Sources/CrashReporter/Models/Crash/ApplicationStats.swift deleted file mode 100644 index 564cebb..0000000 --- a/Sources/CrashReporter/Models/Crash/ApplicationStats.swift +++ /dev/null @@ -1,38 +0,0 @@ -import Foundation - -// MARK: - ApplicationStats -public struct ApplicationStats: Codable, Hashable, Sendable { - public let applicationActive: Bool - public let applicationInForeground: Bool - public let launchesSinceLastCrash: Int - public let sessionsSinceLastCrash: Int - public let activeTimeSinceLastCrash: Double - public let backgroundTimeSinceLastCrash: Double - public let sessionsSinceLaunch: Int - public let activeTimeSinceLaunch: Double - public let backgroundTimeSinceLaunch: Double - - public enum CodingKeys: String, CodingKey { - case applicationActive = "application_active" - case applicationInForeground = "application_in_foreground" - case launchesSinceLastCrash = "launches_since_last_crash" - case sessionsSinceLastCrash = "sessions_since_last_crash" - case activeTimeSinceLastCrash = "active_time_since_last_crash" - case backgroundTimeSinceLastCrash = "background_time_since_last_crash" - case sessionsSinceLaunch = "sessions_since_launch" - case activeTimeSinceLaunch = "active_time_since_launch" - case backgroundTimeSinceLaunch = "background_time_since_launch" - } - - public init(applicationActive: Bool, applicationInForeground: Bool, launchesSinceLastCrash: Int, sessionsSinceLastCrash: Int, activeTimeSinceLastCrash: Double, backgroundTimeSinceLastCrash: Double, sessionsSinceLaunch: Int, activeTimeSinceLaunch: Double, backgroundTimeSinceLaunch: Double) { - self.applicationActive = applicationActive - self.applicationInForeground = applicationInForeground - self.launchesSinceLastCrash = launchesSinceLastCrash - self.sessionsSinceLastCrash = sessionsSinceLastCrash - self.activeTimeSinceLastCrash = activeTimeSinceLastCrash - self.backgroundTimeSinceLastCrash = backgroundTimeSinceLastCrash - self.sessionsSinceLaunch = sessionsSinceLaunch - self.activeTimeSinceLaunch = activeTimeSinceLaunch - self.backgroundTimeSinceLaunch = backgroundTimeSinceLaunch - } -} diff --git a/Sources/CrashReporter/Models/Crash/Backtrace.swift b/Sources/CrashReporter/Models/Crash/Backtrace.swift deleted file mode 100644 index 4996a46..0000000 --- a/Sources/CrashReporter/Models/Crash/Backtrace.swift +++ /dev/null @@ -1,17 +0,0 @@ -import Foundation - -// MARK: - Backtrace -public struct Backtrace: Codable, Hashable, Sendable { - public let contents: [BacktraceContent] - public let skipped: Int - - public enum CodingKeys: String, CodingKey { - case contents = "contents" - case skipped = "skipped" - } - - public init(contents: [BacktraceContent], skipped: Int) { - self.contents = contents - self.skipped = skipped - } -} diff --git a/Sources/CrashReporter/Models/Crash/BacktraceContent.swift b/Sources/CrashReporter/Models/Crash/BacktraceContent.swift deleted file mode 100644 index 7bcb41f..0000000 --- a/Sources/CrashReporter/Models/Crash/BacktraceContent.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Foundation - -// MARK: - Content -public struct BacktraceContent: Codable, Hashable, Sendable { - public let objectName: String? - public let objectAddr: Int? - public let symbolName: String? - public let symbolAddr: Int? - public let instructionAddr: Int - - public enum CodingKeys: String, CodingKey { - case objectName = "object_name" - case objectAddr = "object_addr" - case symbolName = "symbol_name" - case symbolAddr = "symbol_addr" - case instructionAddr = "instruction_addr" - } - - public init(objectName: String?, objectAddr: Int?, symbolName: String?, symbolAddr: Int?, instructionAddr: Int) { - self.objectName = objectName - self.objectAddr = objectAddr - self.symbolName = symbolName - self.symbolAddr = symbolAddr - self.instructionAddr = instructionAddr - } -} diff --git a/Sources/CrashReporter/Models/Crash/Crash.swift b/Sources/CrashReporter/Models/Crash/Crash.swift deleted file mode 100644 index da53a48..0000000 --- a/Sources/CrashReporter/Models/Crash/Crash.swift +++ /dev/null @@ -1,17 +0,0 @@ -import Foundation - -// MARK: - Crash -public struct Crash: Codable, Hashable, Sendable { - public let error: CrashError - public let threads: [Thread] - - public enum CodingKeys: String, CodingKey { - case error = "error" - case threads = "threads" - } - - public init(error: CrashError, threads: [Thread]) { - self.error = error - self.threads = threads - } -} diff --git a/Sources/CrashReporter/Models/Crash/CrashError.swift b/Sources/CrashReporter/Models/Crash/CrashError.swift deleted file mode 100644 index efadd04..0000000 --- a/Sources/CrashReporter/Models/Crash/CrashError.swift +++ /dev/null @@ -1,23 +0,0 @@ -import Foundation - -// MARK: - Error -public struct CrashError: Codable, Hashable, Sendable { - public let mach: Mach - public let signal: Signal - public let address: Int - public let type: String - - public enum CodingKeys: String, CodingKey { - case mach = "mach" - case signal = "signal" - case address = "address" - case type = "type" - } - - public init(mach: Mach, signal: Signal, address: Int, type: String) { - self.mach = mach - self.signal = signal - self.address = address - self.type = type - } -} diff --git a/Sources/CrashReporter/Models/Crash/CrashReportInfo.swift b/Sources/CrashReporter/Models/Crash/CrashReportInfo.swift deleted file mode 100644 index 14547f5..0000000 --- a/Sources/CrashReporter/Models/Crash/CrashReportInfo.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation - -// MARK: - CrashReport -public struct CrashReportInfo: Codable, Hashable, Sendable { - public let report: Report - public let system: System - public let crash: Crash - - public enum CodingKeys: String, CodingKey { - case report = "report" - case system = "system" - case crash = "crash" - } - - public init(report: Report, system: System, crash: Crash) { - self.report = report - self.system = system - self.crash = crash - } -} diff --git a/Sources/CrashReporter/Models/Crash/Exception.swift b/Sources/CrashReporter/Models/Crash/Exception.swift deleted file mode 100644 index 376ea58..0000000 --- a/Sources/CrashReporter/Models/Crash/Exception.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation - -// MARK: - Exception -public struct Exception: Codable, Hashable, Sendable { - public let exception: Int - public let esr: Int - public let far: Int - - public enum CodingKeys: String, CodingKey { - case exception = "exception" - case esr = "esr" - case far = "far" - } - - public init(exception: Int, esr: Int, far: Int) { - self.exception = exception - self.esr = esr - self.far = far - } -} diff --git a/Sources/CrashReporter/Models/Crash/Mach.swift b/Sources/CrashReporter/Models/Crash/Mach.swift deleted file mode 100644 index 7cace65..0000000 --- a/Sources/CrashReporter/Models/Crash/Mach.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Foundation - -// MARK: - Mach -public struct Mach: Codable, Hashable, Sendable { - public let exception: Int - public let exceptionName: String - public let code: Int - public let codeName: String - public let subcode: Int - - public enum CodingKeys: String, CodingKey { - case exception = "exception" - case exceptionName = "exception_name" - case code = "code" - case codeName = "code_name" - case subcode = "subcode" - } - - public init(exception: Int, exceptionName: String, code: Int, codeName: String, subcode: Int) { - self.exception = exception - self.exceptionName = exceptionName - self.code = code - self.codeName = codeName - self.subcode = subcode - } -} diff --git a/Sources/CrashReporter/Models/Crash/Memory.swift b/Sources/CrashReporter/Models/Crash/Memory.swift deleted file mode 100644 index a8d2b86..0000000 --- a/Sources/CrashReporter/Models/Crash/Memory.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation - -// MARK: - Memory -public struct Memory: Codable, Hashable, Sendable { - public let size: Int - public let usable: Int - public let free: Int - - public enum CodingKeys: String, CodingKey { - case size = "size" - case usable = "usable" - case free = "free" - } - - public init(size: Int, usable: Int, free: Int) { - self.size = size - self.usable = usable - self.free = free - } -} diff --git a/Sources/CrashReporter/Models/Crash/Registers.swift b/Sources/CrashReporter/Models/Crash/Registers.swift deleted file mode 100644 index 31f7f1d..0000000 --- a/Sources/CrashReporter/Models/Crash/Registers.swift +++ /dev/null @@ -1,17 +0,0 @@ -import Foundation - -// MARK: - Registers -public struct Registers: Codable, Hashable, Sendable { - public let basic: [String: Double] - public let exception: Exception? - - public enum CodingKeys: String, CodingKey { - case basic = "basic" - case exception = "exception" - } - - public init(basic: [String: Double], exception: Exception?) { - self.basic = basic - self.exception = exception - } -} diff --git a/Sources/CrashReporter/Models/Crash/Report.swift b/Sources/CrashReporter/Models/Crash/Report.swift deleted file mode 100644 index 41c2705..0000000 --- a/Sources/CrashReporter/Models/Crash/Report.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Foundation - -// MARK: - Report -public struct Report: Codable, Hashable, Sendable { - public let version: String - public let id: String - public let processName: String - public let timestamp: String - public let type: String - - public enum CodingKeys: String, CodingKey { - case version = "version" - case id = "id" - case processName = "process_name" - case timestamp = "timestamp" - case type = "type" - } - - public init(version: String, id: String, processName: String, timestamp: String, type: String) { - self.version = version - self.id = id - self.processName = processName - self.timestamp = timestamp - self.type = type - } -} diff --git a/Sources/CrashReporter/Models/Crash/Signal.swift b/Sources/CrashReporter/Models/Crash/Signal.swift deleted file mode 100644 index 645eb76..0000000 --- a/Sources/CrashReporter/Models/Crash/Signal.swift +++ /dev/null @@ -1,23 +0,0 @@ -import Foundation - -// MARK: - Signal -public struct Signal: Codable, Hashable, Sendable { - public let signal: Int - public let name: String - public let code: Int - public let codeName: String - - public enum CodingKeys: String, CodingKey { - case signal = "signal" - case name = "name" - case code = "code" - case codeName = "code_name" - } - - public init(signal: Int, name: String, code: Int, codeName: String) { - self.signal = signal - self.name = name - self.code = code - self.codeName = codeName - } -} diff --git a/Sources/CrashReporter/Models/Crash/Stack.swift b/Sources/CrashReporter/Models/Crash/Stack.swift deleted file mode 100644 index 0167a1a..0000000 --- a/Sources/CrashReporter/Models/Crash/Stack.swift +++ /dev/null @@ -1,29 +0,0 @@ -import Foundation - -// MARK: - Stack -public struct Stack: Codable, Hashable, Sendable { - public let growDirection: String - public let dumpStart: Int - public let dumpEnd: Int - public let stackPointer: Int - public let overflow: Bool - public let contents: String - - public enum CodingKeys: String, CodingKey { - case growDirection = "grow_direction" - case dumpStart = "dump_start" - case dumpEnd = "dump_end" - case stackPointer = "stack_pointer" - case overflow = "overflow" - case contents = "contents" - } - - public init(growDirection: String, dumpStart: Int, dumpEnd: Int, stackPointer: Int, overflow: Bool, contents: String) { - self.growDirection = growDirection - self.dumpStart = dumpStart - self.dumpEnd = dumpEnd - self.stackPointer = stackPointer - self.overflow = overflow - self.contents = contents - } -} diff --git a/Sources/CrashReporter/Models/Crash/System.swift b/Sources/CrashReporter/Models/Crash/System.swift deleted file mode 100644 index 7e40e90..0000000 --- a/Sources/CrashReporter/Models/Crash/System.swift +++ /dev/null @@ -1,104 +0,0 @@ -import Foundation - -// MARK: - System -public struct System: Codable, Hashable, Sendable { - public let systemName: String - public let systemVersion: String - public let machine: String - public let model: String - public let kernelVersion: String - public let osVersion: String - public let jailbroken: Bool - public let bootTime: String? - public let appStartTime: Date - public let cfBundleExecutablePath: String - public let cfBundleExecutable: String - public let cfBundleIdentifier: String - public let cfBundleName: String - public let cfBundleVersion: String - public let cfBundleShortVersionString: String - public let appUUID: String - public let cpuArch: String - public let cpuType: Int - public let cpuSubtype: Int - public let binaryCPUType: Int - public let binaryCPUSubtype: Int - public let timeZone: String - public let processName: String - public let processID: Int - public let parentProcessID: Int - public let deviceAppHash: String - public let buildType: String - public let storage: Int - public let memory: Memory - public let applicationStats: ApplicationStats - public let appMemory: AppMemory - - public enum CodingKeys: String, CodingKey { - case systemName = "system_name" - case systemVersion = "system_version" - case machine = "machine" - case model = "model" - case kernelVersion = "kernel_version" - case osVersion = "os_version" - case jailbroken = "jailbroken" - case bootTime = "boot_time" - case appStartTime = "app_start_time" - case cfBundleExecutablePath = "CFBundleExecutablePath" - case cfBundleExecutable = "CFBundleExecutable" - case cfBundleIdentifier = "CFBundleIdentifier" - case cfBundleName = "CFBundleName" - case cfBundleVersion = "CFBundleVersion" - case cfBundleShortVersionString = "CFBundleShortVersionString" - case appUUID = "app_uuid" - case cpuArch = "cpu_arch" - case cpuType = "cpu_type" - case cpuSubtype = "cpu_subtype" - case binaryCPUType = "binary_cpu_type" - case binaryCPUSubtype = "binary_cpu_subtype" - case timeZone = "time_zone" - case processName = "process_name" - case processID = "process_id" - case parentProcessID = "parent_process_id" - case deviceAppHash = "device_app_hash" - case buildType = "build_type" - case storage = "storage" - case memory = "memory" - case applicationStats = "application_stats" - case appMemory = "app_memory" - } - - public init(systemName: String, systemVersion: String, machine: String, model: String, kernelVersion: String, osVersion: String, jailbroken: Bool, bootTime: String?, appStartTime: Date, cfBundleExecutablePath: String, cfBundleExecutable: String, cfBundleIdentifier: String, cfBundleName: String, cfBundleVersion: String, cfBundleShortVersionString: String, appUUID: String, cpuArch: String, cpuType: Int, cpuSubtype: Int, binaryCPUType: Int, binaryCPUSubtype: Int, timeZone: String, processName: String, processID: Int, parentProcessID: Int, deviceAppHash: String, buildType: String, storage: Int, memory: Memory, applicationStats: ApplicationStats, appMemory: AppMemory) { - self.systemName = systemName - self.systemVersion = systemVersion - self.machine = machine - self.model = model - self.kernelVersion = kernelVersion - self.osVersion = osVersion - self.jailbroken = jailbroken - self.bootTime = bootTime - self.appStartTime = appStartTime - self.cfBundleExecutablePath = cfBundleExecutablePath - self.cfBundleExecutable = cfBundleExecutable - self.cfBundleIdentifier = cfBundleIdentifier - self.cfBundleName = cfBundleName - self.cfBundleVersion = cfBundleVersion - self.cfBundleShortVersionString = cfBundleShortVersionString - self.appUUID = appUUID - self.cpuArch = cpuArch - self.cpuType = cpuType - self.cpuSubtype = cpuSubtype - self.binaryCPUType = binaryCPUType - self.binaryCPUSubtype = binaryCPUSubtype - self.timeZone = timeZone - self.processName = processName - self.processID = processID - self.parentProcessID = parentProcessID - self.deviceAppHash = deviceAppHash - self.buildType = buildType - self.storage = storage - self.memory = memory - self.applicationStats = applicationStats - self.appMemory = appMemory - } -} diff --git a/Sources/CrashReporter/Models/Crash/Thread.swift b/Sources/CrashReporter/Models/Crash/Thread.swift deleted file mode 100644 index 5bb367d..0000000 --- a/Sources/CrashReporter/Models/Crash/Thread.swift +++ /dev/null @@ -1,35 +0,0 @@ -import Foundation - -// MARK: - Thread -public struct Thread: Codable, Hashable, Sendable { - public let backtrace: Backtrace - public let registers: Registers? - public let index: Int - public let state: String - public let crashed: Bool - public let currentThread: Bool - public let stack: Stack? - public let name: String? - - public enum CodingKeys: String, CodingKey { - case backtrace = "backtrace" - case registers = "registers" - case index = "index" - case state = "state" - case crashed = "crashed" - case currentThread = "current_thread" - case stack = "stack" - case name = "name" - } - - public init(backtrace: Backtrace, registers: Registers?, index: Int, state: String, crashed: Bool, currentThread: Bool, stack: Stack?, name: String?) { - self.backtrace = backtrace - self.registers = registers - self.index = index - self.state = state - self.crashed = crashed - self.currentThread = currentThread - self.stack = stack - self.name = name - } -} diff --git a/Sources/CrashReporterLive/LaunchDarklyCrashFilter.swift b/Sources/CrashReporterLive/LaunchDarklyCrashFilter.swift index c5d981e..064dca1 100644 --- a/Sources/CrashReporterLive/LaunchDarklyCrashFilter.swift +++ b/Sources/CrashReporterLive/LaunchDarklyCrashFilter.swift @@ -5,7 +5,6 @@ import KSCrashDemangleFilter import KSCrashFilters @preconcurrency import OpenTelemetryApi import OpenTelemetrySdk -import ResourceExtension import CrashReporter import Common diff --git a/Sources/CrashReporterLive/LiveValue.swift b/Sources/CrashReporterLive/LiveValue.swift index 93b404d..fbadf4e 100644 --- a/Sources/CrashReporterLive/LiveValue.swift +++ b/Sources/CrashReporterLive/LiveValue.swift @@ -5,7 +5,6 @@ import KSCrashDemangleFilter import KSCrashFilters @preconcurrency import OpenTelemetryApi import OpenTelemetrySdk -import ResourceExtension import CrashReporter import Common diff --git a/Sources/LaunchDarklyObservability/LDObserve.swift b/Sources/LaunchDarklyObservability/LDObserve.swift index e841113..0392191 100644 --- a/Sources/LaunchDarklyObservability/LDObserve.swift +++ b/Sources/LaunchDarklyObservability/LDObserve.swift @@ -1,7 +1,7 @@ import Foundation @preconcurrency import OpenTelemetryApi -import Client -import Interfaces +import API +import Observability private final class NoOpClient: Observe { func recordMetric(metric: Metric) {} diff --git a/Sources/Plugin/EvalTracingHook.swift b/Sources/LaunchDarklyObservability/Plugin/EvalTracingHook.swift similarity index 99% rename from Sources/Plugin/EvalTracingHook.swift rename to Sources/LaunchDarklyObservability/Plugin/EvalTracingHook.swift index 58aaada..43fb303 100644 --- a/Sources/Plugin/EvalTracingHook.swift +++ b/Sources/LaunchDarklyObservability/Plugin/EvalTracingHook.swift @@ -2,7 +2,7 @@ import Foundation import LaunchDarkly @preconcurrency import OpenTelemetryApi import OpenTelemetrySdk -import Client +import Observability import Common public final class EvalTracingHook: @unchecked Sendable, Hook { diff --git a/Sources/LaunchDarklyObservability/Plugin/Observability.swift b/Sources/LaunchDarklyObservability/Plugin/Observability.swift new file mode 100644 index 0000000..5c9848a --- /dev/null +++ b/Sources/LaunchDarklyObservability/Plugin/Observability.swift @@ -0,0 +1,59 @@ +import Foundation +import LaunchDarkly +import OpenTelemetryApi +import OpenTelemetrySdk +import ResourceExtension + +import API +import Observability + +public final class Observability: Plugin { + private let options: Options + public init(options: Options = Options(resourceAttributes: DefaultResources().get().attributes)) { + self.options = options + } + public func getMetadata() -> PluginMetadata { + guard !options.serviceName.isEmpty else { + return .init(name: "'@launchdarkly/observability-ios'") + } + return .init(name: options.serviceName) + } + + public func register(client: LDClient, metadata: EnvironmentMetadata) { + let sdkKey = metadata.credential + + + var resourceAttributes = DefaultResources().get().attributes + resourceAttributes["launchdarkly.sdk.version"] = .string(String(format: "%@/%@", metadata.sdkMetadata.name, metadata.sdkMetadata.version)) + resourceAttributes["highlight.project_id"] = .string(sdkKey) + + let options = Options( + serviceName: options.serviceName, + serviceVersion: options.serviceVersion, + otlpEndpoint: options.otlpEndpoint, + backendUrl: options.backendUrl, + resourceAttributes: options.resourceAttributes.merging(resourceAttributes) { (old, _) in old }, + customHeaders: options.customHeaders, + sessionBackgroundTimeout: options.sessionBackgroundTimeout, + isDebug: options.isDebug, + disableErrorTracking: options.disableErrorTracking, + disableLogs: options.disableLogs, + disableTraces: options.disableTraces, + disableMetrics: options.disableMetrics, + loggerName: options.loggerName + ) + let client = ObservabilityClient( + sdkKey: sdkKey, + resource: .init(attributes: resourceAttributes), + options: options + ) + + LDObserve.shared.set(client: client) + } + + public func getHooks(metadata: EnvironmentMetadata) -> [any Hook] { + [ + EvalTracingHook(withSpans: true, withValue: true, version: metadata.sdkMetadata.version) + ] + } +} diff --git a/Sources/Client/ExceptionError.swift b/Sources/Observability/ExceptionError.swift similarity index 100% rename from Sources/Client/ExceptionError.swift rename to Sources/Observability/ExceptionError.swift diff --git a/Sources/Client/InstrumentationManager.swift b/Sources/Observability/InstrumentationManager.swift similarity index 94% rename from Sources/Client/InstrumentationManager.swift rename to Sources/Observability/InstrumentationManager.swift index fbbcf50..b32783e 100644 --- a/Sources/Client/InstrumentationManager.swift +++ b/Sources/Observability/InstrumentationManager.swift @@ -1,26 +1,24 @@ import Foundation -@preconcurrency import OpenTelemetryApi + import OpenTelemetrySdk -import StdoutExporter +import OpenTelemetryApi import OpenTelemetryProtocolExporterHttp -import API -import Interfaces import Common +import API private let tracesPath = "/v1/traces" private let logsPath = "/v1/logs" private let metricsPath = "/v1/metrics" -public final class InstrumentationManager { +final class InstrumentationManager { private let sdkKey: String private let options: Options let otelLogger: Logger? let otelTracer: Tracer? - let otelBatchLogRecordProcessor: BatchLogRecordProcessor? let otelMeter: (any Meter)? + public let otelBatchLogRecordProcessor: BatchLogRecordProcessor? private let sessionManager: SessionManager - private var cachedGauges = AtomicDictionary() private var cachedCounters = AtomicDictionary() private var cachedLongCounters = AtomicDictionary() @@ -56,8 +54,8 @@ public final class InstrumentationManager { processor ] ) - .with(resource: Resource(attributes: options.resourceAttributes)) - .build() + .with(resource: Resource(attributes: options.resourceAttributes)) + .build() return (processor, provider) } .map { (arg0) in @@ -67,7 +65,7 @@ public final class InstrumentationManager { ) return (processor, loggerProvider) } - + URL(string: options.otlpEndpoint) .flatMap { $0.appending(path: tracesPath) } .map { url in @@ -129,7 +127,7 @@ public final class InstrumentationManager { meterProvider: meterProvider ) } - + self.otelBatchLogRecordProcessor = processorAndProvider?.0 self.otelLogger = OpenTelemetry.instance.loggerProvider.get( instrumentationScopeName: options.serviceName @@ -199,11 +197,11 @@ public final class InstrumentationManager { attributes[SemanticConvention.highlightSessionId.rawValue] = .string(sessionId) } otelLogger?.logRecordBuilder() - .setBody(.string(message)) - .setTimestamp(.now) - .setSeverity(severity) - .setAttributes(attributes) - .emit() + .setBody(.string(message)) + .setTimestamp(.now) + .setSeverity(severity) + .setAttributes(attributes) + .emit() } func recordError(error: Error, attributes: [String: AttributeValue]) { diff --git a/Sources/Client/ObservabilityClient.swift b/Sources/Observability/ObservabilityClient.swift similarity index 75% rename from Sources/Client/ObservabilityClient.swift rename to Sources/Observability/ObservabilityClient.swift index 8d5d67e..7b6d9c1 100644 --- a/Sources/Client/ObservabilityClient.swift +++ b/Sources/Observability/ObservabilityClient.swift @@ -1,10 +1,8 @@ @preconcurrency import OpenTelemetryApi import OpenTelemetrySdk -import StdoutExporter import URLSessionInstrumentation import API -import Interfaces import Common import CrashReporter import CrashReporterLive @@ -16,21 +14,9 @@ public final class ObservabilityClient: Observe { private let options: Options private var cachedSpans = AtomicDictionary() - private var task: Task? private let crashReporter: CrashReporter private let urlSessionInstrumentation: URLSessionInstrumentation - private var onWillEndSession: (_ sessionId: String) -> Void { - { [weak self] sessionId in - self?.willEndSession(sessionId) - } - } - private var onDidStartSession: (_ sessionId: String) -> Void { - { [weak self] sessionId in - self?.didStartSession(sessionId) - } - } - public init(sdkKey: String, resource: Resource, options: Options) { let sessionManager = SessionManager(options: .init(timeout: options.sessionBackgroundTimeout)) self.instrumentationManager = .init(sdkKey: sdkKey, options: options, sessionManager: sessionManager) @@ -60,10 +46,13 @@ public final class ObservabilityClient: Observe { ) ) - self.sessionManager.start( - onWillEndSession: onWillEndSession, - onDidStartSession: onDidStartSession - ) + sessionManager.onSessionDidChange = { _ in + // TODO: create a span + } + sessionManager.onStateDidChange = { _, _ in + // TODO: create a span + + } } public func recordMetric(metric: Metric) { @@ -102,24 +91,3 @@ public final class ObservabilityClient: Observe { // TODO: Implement flush } } - -extension ObservabilityClient { - // NOTE: we are not creating spans for app life cycle - private func didStartSession(_ id: String) { - /* - let span = instrumentationManager.startSpan( - name: "app.session.started", - attributes: [:] - ) - cachedSpans[id] = span - */ - } - - private func willEndSession(_ id: String) { - /* - guard let span = cachedSpans[id] else { return } - span.end() - */ - } - -} diff --git a/Sources/Client/SessionInfo.swift b/Sources/Observability/SessionInfo.swift similarity index 100% rename from Sources/Client/SessionInfo.swift rename to Sources/Observability/SessionInfo.swift diff --git a/Sources/Observability/SessionManager.swift b/Sources/Observability/SessionManager.swift new file mode 100644 index 0000000..2fdd670 --- /dev/null +++ b/Sources/Observability/SessionManager.swift @@ -0,0 +1,126 @@ +import Foundation +import UIKit.UIApplication + +final class SessionManager { + enum State: String { + case notRunning + case inactive + case active + case background + case suspended + } + private var id: String + private var startTime: Date + private var backgroundTime: Date? + private var options: SessionOptions + var sessionAttributes: [String: String] { + [ + "session.id": id, + "session.start_time": String(format: "%.0f", startTime.timeIntervalSince1970) + ] + } + private let stateQueue = DispatchQueue( + label: "com.launchdarkly.observability.state-queue", + attributes: .concurrent) + + private var currentState: State = .notRunning + var onSessionDidChange: ((SessionInfo) -> Void)? + var onStateDidChange: ((State, SessionInfo) -> Void)? + + var sessionInfo: SessionInfo { + .init( + id: id, + startTime: startTime + ) + } + + init( + id: String = UUID().uuidString, + startTime: Date = Date.now, + options: SessionOptions + ) { + self.id = id + self.startTime = startTime + self.options = options + observeLifecycleNotifications() + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + private func observeLifecycleNotifications() { + NotificationCenter.default.addObserver(self, selector: #selector(handleDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(handleWillResignActive), name: UIApplication.willResignActiveNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(handleDidEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(handleWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(handleWillTerminate), name: UIApplication.willTerminateNotification, object: nil) + } + + @objc private func handleDidBecomeActive() { + transition(to: .active) + } + + @objc private func handleWillResignActive() { + transition(to: .inactive) + } + + @objc private func handleDidEnterBackground() { + transition(to: .background) + } + + @objc private func handleWillEnterForeground() { + transition(to: .inactive) + } + + @objc private func handleWillTerminate() { + transition(to: .notRunning) + } + + private func transition(to newState: State) { + stateQueue.sync(flags: .barrier) { [weak self] in + guard let self else { return } + guard self.currentState != newState else { return } + self.currentState = newState + switch newState { + case .background: + self.handleBackgroundState() + case .active: + self.handleActiveState() + default: + break + } + self.onStateDidChange?(newState, self.sessionInfo) + } + } + + private func handleActiveState() { + guard let backgroundTime = self.backgroundTime else { return } + let timeInBackground = Date.now.timeIntervalSince1970 - backgroundTime.timeIntervalSince1970 + if timeInBackground >= self.options.timeout { + self.resetSession() + } + self.backgroundTime = nil + } + + private func handleBackgroundState() { + self.backgroundTime = Date.now + } + + private func resetSession() { + let oldSessionId = sessionInfo.id + let newSessionId = UUID().uuidString + let oldStartTime = startTime + + id = newSessionId + let newStartTime = Date.now + startTime = newStartTime + + if options.isDebug { +// print("🔄 Session reset: \(oldSessionId) -> \(sessionInfo.id)") + let dateInterval = DateInterval(start: oldStartTime, end: newStartTime) +// print("Session duration: \(dateInterval.duration) seconds") + } + onSessionDidChange?(sessionInfo) + } +} diff --git a/Sources/Client/SessionOptions.swift b/Sources/Observability/SessionOptions.swift similarity index 100% rename from Sources/Client/SessionOptions.swift rename to Sources/Observability/SessionOptions.swift diff --git a/Sources/Plugin/Observability.swift b/Sources/Plugin/Observability.swift deleted file mode 100644 index cf2b170..0000000 --- a/Sources/Plugin/Observability.swift +++ /dev/null @@ -1,39 +0,0 @@ -import LaunchDarkly -import OpenTelemetryApi -import OpenTelemetrySdk -import ResourceExtension - -import API -import Client -import LaunchDarklyObservability - -public final class Observability: Plugin { - public init() {} - - public func getMetadata() -> PluginMetadata { - guard case let .string(serviceName) = DefaultResources().get().attributes["service.name"], !serviceName.isEmpty else { - return .init(name: "'@launchdarkly/observability-ios'") - } - return .init(name: serviceName) - } - - public func register(client: LDClient, metadata: EnvironmentMetadata) { - let sdkKey = metadata.credential - - - var resourceAttributes = DefaultResources().get().attributes - resourceAttributes["launchdarkly.sdk.version"] = .string(String(format: "%@/%@", metadata.sdkMetadata.name, metadata.sdkMetadata.version)) - resourceAttributes["highlight.project_id"] = .string(sdkKey) - - let options = Options(resourceAttributes: resourceAttributes) - let client = ObservabilityClient(sdkKey: sdkKey, resource: .init(attributes: resourceAttributes), options: options) - - LDObserve.shared.set(client: client) - } - - public func getHooks(metadata: EnvironmentMetadata) -> [any Hook] { - [ - EvalTracingHook(withSpans: true, withValue: true, version: metadata.sdkMetadata.version) - ] - } -}