{"id":35699251,"url":"https://github.com/launchdarkly/swift-launchdarkly-observability","last_synced_at":"2026-05-02T00:04:05.291Z","repository":{"id":306955949,"uuid":"1027832172","full_name":"launchdarkly/swift-launchdarkly-observability","owner":"launchdarkly","description":"LaunchDarkly Observability SDK for Swift","archived":false,"fork":false,"pushed_at":"2026-02-13T23:10:08.000Z","size":49909,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-14T05:34:22.709Z","etag":null,"topics":["feature-flags","feature-toggles","ios","launchdarkly","launchdarkly-sdk","macos","managed-by-terraform","observability","session-replay","swift","tvos","watchos"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/launchdarkly.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-07-28T15:45:24.000Z","updated_at":"2026-02-13T23:10:00.000Z","dependencies_parsed_at":"2025-07-28T17:42:42.029Z","dependency_job_id":"37bf4721-9222-49d3-90fb-106b9d248b6f","html_url":"https://github.com/launchdarkly/swift-launchdarkly-observability","commit_stats":null,"previous_names":["launchdarkly/swift-launchdarkly-observability"],"tags_count":32,"template":false,"template_full_name":null,"purl":"pkg:github/launchdarkly/swift-launchdarkly-observability","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/launchdarkly%2Fswift-launchdarkly-observability","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/launchdarkly%2Fswift-launchdarkly-observability/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/launchdarkly%2Fswift-launchdarkly-observability/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/launchdarkly%2Fswift-launchdarkly-observability/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/launchdarkly","download_url":"https://codeload.github.com/launchdarkly/swift-launchdarkly-observability/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/launchdarkly%2Fswift-launchdarkly-observability/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29639808,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-19T22:32:43.237Z","status":"online","status_checked_at":"2026-02-20T02:00:07.535Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["feature-flags","feature-toggles","ios","launchdarkly","launchdarkly-sdk","macos","managed-by-terraform","observability","session-replay","swift","tvos","watchos"],"created_at":"2026-01-06T01:11:07.609Z","updated_at":"2026-05-02T00:04:05.277Z","avatar_url":"https://github.com/launchdarkly.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# swift-launchdarkly-observability\nLaunchDarkly Observability SDK for Swift\n\n## Early Access Preview️\n**NB: APIs are subject to change until a 1.x version is released.**\n\n## Features\n\n### Automatic Instrumentation\n\nThe iOS observability plugin automatically instruments:\n- **Activity Lifecycle**: App lifecycle events and transitions\n- **HTTP Requests**: URLSession requests\n- **Crash Reporting**: Automatic crash reporting\n- **Feature Flag Evaluations**: Evaluation events added to your spans.\n- **Session Management**: User session tracking and background timeout handling\n\n## Example Application\n\nA complete example application is available in the [swift-launchdarkly-observability/ExampleApp](/ExampleApp) directory.\n\n## Install\n\n\u003e **NOTE: since APIs are subject to change until a 1.x version is released, pointing to main branch is a temporal workaround to test/use the package**\n\n### Swift Package Manager\n\nAdd the Swift Package dependency in Xcode or add it to your `Package.swift`:\n\n```swift\n.package(url: \"https://github.com/launchdarkly/swift-launchdarkly-observability\", branch: \"main\"),\n```\n\n### CocoaPods\n\nAdd the pods to your `Podfile`:\n\n```ruby\npod 'LaunchDarklyObservability'\npod 'LaunchDarklySessionReplay'   # optional, only if using Session Replay\n```\n\nSome transitive dependencies (e.g. LDSwiftEventSource) still declare an iOS 11.0 deployment target, which is below the minimum required by recent Xcode SDKs. Add the following `post_install` hook to your `Podfile` to raise their deployment target automatically:\n\n```ruby\npost_install do |installer|\n  installer.pods_project.targets.each do |target|\n    target.build_configurations.each do |config|\n      if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f \u003c 13.0\n        config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'\n      end\n    end\n  end\nend\n```\n\n## Usage\n\n### Basic Setup\n\nAdd the observability plugin to your LaunchDarkly iOS Client SDK configuration:\n\n```swift\nimport UIKit\nimport LaunchDarkly\nimport LaunchDarklyObservability\n\nlet mobileKey = \"your-mobile-key\"\nlet config = { () -\u003e LDConfig in\n    var config = LDConfig(\n        mobileKey: mobileKey,\n        autoEnvAttributes: .enabled\n    )\n    config.plugins = [\n        Observability(options: .init(sessionBackgroundTimeout: 3))\n    ]\n    return config\n}()\n\nlet context = { () -\u003e LDContext in\n    var contextBuilder = LDContextBuilder(\n        key: \"12345\"\n    )\n    contextBuilder.kind(\"user\")\n    do {\n        return try contextBuilder.build().get()\n    } catch {\n        abort()\n    }\n}()\n\nfinal class AppDelegate: NSObject, UIApplicationDelegate {\n    func application(\n        _ application: UIApplication,\n        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil\n    ) -\u003e Bool {\n        LDClient.start(\n            config: config,\n            context: context,\n            startWaitSeconds: 5.0,\n            completion: { (timedOut: Bool) -\u003e Void in\n                if timedOut {\n                    // Client may not have the most recent flags for the configured context\n                } else {\n                    // Client has received flags for the configured context\n                }\n            }\n        )\n        return true\n    }\n}\n```\n\n### Configure Session Replay\n\nSession Replay captures user interactions and screen recordings to help you understand how users interact with your application. To enable Session Replay, add the `SessionReplay` plugin alongside the `Observability` plugin:\n\n```swift\nimport UIKit\nimport LaunchDarkly\nimport LaunchDarklyObservability\nimport LaunchDarklySessionReplay\n\nlet mobileKey = \"your-mobile-key\"\nlet config = { () -\u003e LDConfig in\n    var config = LDConfig(\n        mobileKey: mobileKey,\n        autoEnvAttributes: .enabled\n    )\n    config.plugins = [\n        // Observability plugin must be added before SessionReplay\n        Observability(options: .init(\n            serviceName: \"ios-app\",\n            sessionBackgroundTimeout: 3)),\n        SessionReplay(options: .init(\n            isEnabled: true,\n            privacy: .init(\n                maskTextInputs: true,\n                maskWebViews: false,\n                maskImages: false,\n                maskAccessibilityIdentifiers: [\"email-field\", \"password-field\"]\n            )\n        ))\n    ]\n    return config\n}()\n\nlet context = { () -\u003e LDContext in\n    var contextBuilder = LDContextBuilder(key: \"12345\")\n    contextBuilder.kind(\"user\")\n    do {\n        return try contextBuilder.build().get()\n    } catch {\n        abort()\n    }\n}()\n\nfinal class AppDelegate: NSObject, UIApplicationDelegate {\n    func application(\n        _ application: UIApplication,\n        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil\n    ) -\u003e Bool {\n        LDClient.start(\n            config: config,\n            context: context,\n            startWaitSeconds: 5.0,\n            completion: { (timedOut: Bool) -\u003e Void in\n                if timedOut {\n                    // Client may not have the most recent flags for the configured context\n                } else {\n                    // Client has received flags for the configured context\n                }\n            }\n        )\n        return true\n    }\n}\n```\n\n### Manual Start\n\nBy default, Session Replay starts recording as soon as the SDK is initialized if `isEnabled` is set to `true`. If you want to initialize Session Replay without activating recording immediately (e.g., to wait for user consent or a specific event), set `isEnabled` to `false` in the options:\n\n```swift\nSessionReplay(options: .init(\n    isEnabled: false,\n    // ... other options\n))\n```\n\nYou can then activate recording later by setting `LDReplay.shared.isEnabled` to `true`.\n```swift\n// From a SwiftUI View or @MainActor isolated class\nLDReplay.shared.isEnabled = true\n```\n\n#### Privacy Options\n\nConfigure privacy settings to control what data is captured:\n\n- **maskTextInputs**: Mask all text input fields (default: `true`)\n- **maskWebViews**: Mask contents of Web Views (default: `false`)\n- **maskLabels**: Mask all text labels (default: `false`)\n- **maskImages**: Mask all images (default: `false`)\n- **maskAccessibilityIdentifiers**: Array of accessibility identifiers to mask\n- **maskUIViews**: Array of UIView classes to mask\n- **minimumAlpha**: Minimum alpha value for view visibility (default: `0.02`)\n\n#### Fine-grained Masking Control\n\nYou can override the default privacy settings on individual views using modifiers:\n\n**SwiftUI Views:**\n```swift\nimport SwiftUI\nimport LaunchDarklySessionReplay\n\nstruct ContentView: View {\n    var body: some View {\n        VStack {\n            // Mask this specific view\n            Text(\"Sensitive information\")\n                .ldMask()\n\n            // Unmask this view (even if it would be masked by default)\n            Image(\"profile-photo\")\n                .ldUnmask()\n\n            // Conditionally mask based on a flag\n            TextField(\"Email\", text: $email)\n                .ldPrivate(isEnabled: shouldMaskEmail)\n        }\n    }\n}\n```\n\n**UIKit Views:**\n```swift\nimport UIKit\nimport LaunchDarklySessionReplay\n\nclass CreditCardViewController: UIViewController {\n    let cvvField = UITextField()\n    let nameField = UITextField()\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n\n        // Mask the CVV container\n        cvvField.ldMask()\n\n        // Unmask the name field (even if text inputs are masked by default)\n        nameField.ldUnmask()\n\n        // Conditionally mask based on a flag\n        cvvField.ldPrivate(isEnabled: true)\n    }\n}\n```\n\n#### How the SDK Determines What to Mask\n\nWhen deciding whether a specific view should be masked in a Session Replay, the SDK evaluates rules in a strict order of precedence. It checks these conditions from top to bottom and stops at the first one that applies:\n\n1. **Explicit Masking (Highest Priority)**: Is the view, or *any* of its parent views, explicitly masked (e.g., using `.ldMask()` or matching `maskAccessibilityIdentifiers`)?\n   * **Yes**: The view is **masked**. This overrides all other rules.\n2. **Explicit Unmasking**: Is the view itself explicitly unmasked (e.g., using `.ldUnmask()`)?\n   * **Yes**: The view is **unmasked**.\n3. **Inherited Unmasking**: Does the nearest parent view with an explicit rule have an unmask rule?\n   * **Yes**: The view is **unmasked**.\n4. **Global Configuration**: Does your global privacy configuration (like `maskTextInputs`, `maskImages`, etc.) apply to this view?\n   * **Yes**: The view follows the global configuration.\n\n*Note: If multiple rules conflict at the same level, masking wins over unmasking.*\n\n### Advanced Configuration\n\nYou can customize the observability plugin with various options:\n\n```swift\nimport UIKit\nimport LaunchDarkly\nimport LaunchDarklyObservability\n\nlet config = { () -\u003e LDConfig in\n    var config = LDConfig(\n        mobileKey: mobileKey,\n        autoEnvAttributes: .enabled\n    )\n    config.plugins = [\n        Observability(\n            options: .init(\n                serviceName: \"ios-app\",\n                serviceVersion: \"0.1.0\",\n                resourceAttributes: [\n                    \"environment\": .string(\"production\"),\n                    \"team\": .string(\"mobile\")\n                ],\n                customHeaders: [\n                    (\"X-Custom-Header\", \"custom-value\")\n                ],\n                sessionBackgroundTimeout: 60,\n                isDebug: true\n            )\n        )\n    ]\n    return config\n}()\n```\n\n### Recording Observability Data\n\nAfter initialization of the LaunchDarkly iOS Client SDK, use `LDObserve` to record metrics, logs, errors, and traces:\n\n```swift\nimport LaunchDarklyObservability\nimport OpenTelemetryApi\n\n// Record metrics\nLDObserve.shared.recordMetric(metric: .init(name: \"user_actions\", value: 1.0))\nLDObserve.shared.recordCount(metric: .init(name: \"api_calls\", value: 1.0))\nLDObserve.shared.recordIncr(metric: .init(name: \"page_views\", value: 1.0))\nLDObserve.shared.recordHistogram(metric: .init(name: \"response_time\", value: 150.0))\nLDObserve.shared.recordUpDownCounter(metric: .init(name: \"active_connections\", value: 1.0))\n\n// Record logs\nLDObserve.shared.recordLog(\n    message: \"User performed action\",\n    severity: .info,\n    attributes: [\n        \"user_id\": .string(\"12345\"),\n        \"action\": .string(\"button_click\")\n    ]\n)\n\n// Record errors\nLDObserve.shared.recordError(\n    error: paymentError,\n    attributes: [\n        \"component\": .string(\"payment\"),\n        \"error_code\": .string(\"PAYMENT_FAILED\")\n    ]\n)\n\n// Create spans for tracing\nlet span = LDObserve.shared.startSpan(\n    name: \"api_request\",\n    attributes: [\n        \"endpoint\": .string(\"/api/users\"),\n        \"method\": .string(\"GET\")\n    ]\n)\n\nspan.end()\n```\n\n## Contributing\n\nWe encourage pull requests and other contributions from the community. Check out our [contributing guidelines](../../CONTRIBUTING.md) for instructions on how to contribute to this SDK.\n\n## About LaunchDarkly\n\n* LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard.  With LaunchDarkly, you can:\n    * Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases.\n    * Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?).\n    * Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file.\n    * Grant access to certain features based on user attributes, like payment plan (eg: users on the ‘gold’ plan get access to more features than users in the ‘silver’ plan). Disable parts of your application to facilitate maintenance, without taking everything offline.\n* LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Read [our documentation](https://docs.launchdarkly.com/sdk) for a complete list.\n* Explore LaunchDarkly\n    * [launchdarkly.com](https://www.launchdarkly.com/ \"LaunchDarkly Main Website\") for more information\n    * [docs.launchdarkly.com](https://docs.launchdarkly.com/  \"LaunchDarkly Documentation\") for our documentation and SDK reference guides\n    * [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/  \"LaunchDarkly API Documentation\") for our API documentation\n    * [launchdarkly.com/blog](https://launchdarkly.com/blog/  \"LaunchDarkly Blog Documentation\") for the latest product updates\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flaunchdarkly%2Fswift-launchdarkly-observability","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flaunchdarkly%2Fswift-launchdarkly-observability","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flaunchdarkly%2Fswift-launchdarkly-observability/lists"}