https://github.com/launchdarkly/swift-launchdarkly-observability
LaunchDarkly Observability SDK for Swift
https://github.com/launchdarkly/swift-launchdarkly-observability
feature-flags feature-toggles ios launchdarkly launchdarkly-sdk macos managed-by-terraform observability session-replay swift tvos watchos
Last synced: about 2 months ago
JSON representation
LaunchDarkly Observability SDK for Swift
- Host: GitHub
- URL: https://github.com/launchdarkly/swift-launchdarkly-observability
- Owner: launchdarkly
- License: other
- Created: 2025-07-28T15:45:24.000Z (11 months ago)
- Default Branch: main
- Last Pushed: 2026-02-13T23:10:08.000Z (5 months ago)
- Last Synced: 2026-02-14T05:34:22.709Z (5 months ago)
- Topics: feature-flags, feature-toggles, ios, launchdarkly, launchdarkly-sdk, macos, managed-by-terraform, observability, session-replay, swift, tvos, watchos
- Language: Swift
- Homepage:
- Size: 47.6 MB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE.txt
- Codeowners: CODEOWNERS
- Notice: NOTICE
Awesome Lists containing this project
README
# swift-launchdarkly-observability
LaunchDarkly Observability SDK for Swift
## Early Access Preview️
**NB: APIs are subject to change until a 1.x version is released.**
## Features
### Automatic Instrumentation
The iOS observability plugin automatically instruments:
- **Activity Lifecycle**: App lifecycle events and transitions
- **HTTP Requests**: URLSession requests
- **Crash Reporting**: Automatic crash reporting
- **Feature Flag Evaluations**: Evaluation events added to your spans.
- **Session Management**: User session tracking and background timeout handling
## Example Application
A complete example application is available in the [swift-launchdarkly-observability/ExampleApp](/ExampleApp) directory.
## Install
> **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**
### Swift Package Manager
Add the Swift Package dependency in Xcode or add it to your `Package.swift`:
```swift
.package(url: "https://github.com/launchdarkly/swift-launchdarkly-observability", branch: "main"),
```
### CocoaPods
Add the pods to your `Podfile`:
```ruby
pod 'LaunchDarklyObservability'
pod 'LaunchDarklySessionReplay' # optional, only if using Session Replay
```
Some 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:
```ruby
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f < 13.0
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
end
end
end
end
```
## Usage
### Basic Setup
Add the observability plugin to your LaunchDarkly iOS Client SDK configuration:
```swift
import UIKit
import LaunchDarkly
import LaunchDarklyObservability
let mobileKey = "your-mobile-key"
let config = { () -> LDConfig in
var config = LDConfig(
mobileKey: mobileKey,
autoEnvAttributes: .enabled
)
config.plugins = [
Observability(options: .init(sessionBackgroundTimeout: 3))
]
return config
}()
let context = { () -> LDContext in
var contextBuilder = LDContextBuilder(
key: "12345"
)
contextBuilder.kind("user")
do {
return try contextBuilder.build().get()
} catch {
abort()
}
}()
final class AppDelegate: NSObject, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
) -> Bool {
LDClient.start(
config: config,
context: context,
startWaitSeconds: 5.0,
completion: { (timedOut: Bool) -> Void in
if timedOut {
// Client may not have the most recent flags for the configured context
} else {
// Client has received flags for the configured context
}
}
)
return true
}
}
```
### Configure Session Replay
Session 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:
```swift
import UIKit
import LaunchDarkly
import LaunchDarklyObservability
import LaunchDarklySessionReplay
let mobileKey = "your-mobile-key"
let config = { () -> LDConfig in
var config = LDConfig(
mobileKey: mobileKey,
autoEnvAttributes: .enabled
)
config.plugins = [
// Observability plugin must be added before SessionReplay
Observability(options: .init(
serviceName: "ios-app",
sessionBackgroundTimeout: 3)),
SessionReplay(options: .init(
isEnabled: true,
privacy: .init(
maskTextInputs: true,
maskWebViews: false,
maskImages: false,
maskAccessibilityIdentifiers: ["email-field", "password-field"]
)
))
]
return config
}()
let context = { () -> LDContext in
var contextBuilder = LDContextBuilder(key: "12345")
contextBuilder.kind("user")
do {
return try contextBuilder.build().get()
} catch {
abort()
}
}()
final class AppDelegate: NSObject, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
) -> Bool {
LDClient.start(
config: config,
context: context,
startWaitSeconds: 5.0,
completion: { (timedOut: Bool) -> Void in
if timedOut {
// Client may not have the most recent flags for the configured context
} else {
// Client has received flags for the configured context
}
}
)
return true
}
}
```
### Manual Start
By 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:
```swift
SessionReplay(options: .init(
isEnabled: false,
// ... other options
))
```
You can then activate recording later by setting `LDReplay.shared.isEnabled` to `true`.
```swift
// From a SwiftUI View or @MainActor isolated class
LDReplay.shared.isEnabled = true
```
#### Privacy Options
Configure privacy settings to control what data is captured:
- **maskTextInputs**: Mask all text input fields (default: `true`)
- **maskWebViews**: Mask contents of Web Views (default: `false`)
- **maskLabels**: Mask all text labels (default: `false`)
- **maskImages**: Mask all images (default: `false`)
- **maskAccessibilityIdentifiers**: Array of accessibility identifiers to mask
- **maskUIViews**: Array of UIView classes to mask
- **minimumAlpha**: Minimum alpha value for view visibility (default: `0.02`)
#### Fine-grained Masking Control
You can override the default privacy settings on individual views using modifiers:
**SwiftUI Views:**
```swift
import SwiftUI
import LaunchDarklySessionReplay
struct ContentView: View {
var body: some View {
VStack {
// Mask this specific view
Text("Sensitive information")
.ldMask()
// Unmask this view (even if it would be masked by default)
Image("profile-photo")
.ldUnmask()
// Conditionally mask based on a flag
TextField("Email", text: $email)
.ldPrivate(isEnabled: shouldMaskEmail)
}
}
}
```
**UIKit Views:**
```swift
import UIKit
import LaunchDarklySessionReplay
class CreditCardViewController: UIViewController {
let cvvField = UITextField()
let nameField = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
// Mask the CVV container
cvvField.ldMask()
// Unmask the name field (even if text inputs are masked by default)
nameField.ldUnmask()
// Conditionally mask based on a flag
cvvField.ldPrivate(isEnabled: true)
}
}
```
#### How the SDK Determines What to Mask
When 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:
1. **Explicit Masking (Highest Priority)**: Is the view, or *any* of its parent views, explicitly masked (e.g., using `.ldMask()` or matching `maskAccessibilityIdentifiers`)?
* **Yes**: The view is **masked**. This overrides all other rules.
2. **Explicit Unmasking**: Is the view itself explicitly unmasked (e.g., using `.ldUnmask()`)?
* **Yes**: The view is **unmasked**.
3. **Inherited Unmasking**: Does the nearest parent view with an explicit rule have an unmask rule?
* **Yes**: The view is **unmasked**.
4. **Global Configuration**: Does your global privacy configuration (like `maskTextInputs`, `maskImages`, etc.) apply to this view?
* **Yes**: The view follows the global configuration.
*Note: If multiple rules conflict at the same level, masking wins over unmasking.*
### Advanced Configuration
You can customize the observability plugin with various options:
```swift
import UIKit
import LaunchDarkly
import LaunchDarklyObservability
let config = { () -> LDConfig in
var config = LDConfig(
mobileKey: mobileKey,
autoEnvAttributes: .enabled
)
config.plugins = [
Observability(
options: .init(
serviceName: "ios-app",
serviceVersion: "0.1.0",
resourceAttributes: [
"environment": .string("production"),
"team": .string("mobile")
],
customHeaders: [
("X-Custom-Header", "custom-value")
],
sessionBackgroundTimeout: 60,
isDebug: true
)
)
]
return config
}()
```
### Recording Observability Data
After initialization of the LaunchDarkly iOS Client SDK, use `LDObserve` to record metrics, logs, errors, and traces:
```swift
import LaunchDarklyObservability
import OpenTelemetryApi
// Record metrics
LDObserve.shared.recordMetric(metric: .init(name: "user_actions", value: 1.0))
LDObserve.shared.recordCount(metric: .init(name: "api_calls", value: 1.0))
LDObserve.shared.recordIncr(metric: .init(name: "page_views", value: 1.0))
LDObserve.shared.recordHistogram(metric: .init(name: "response_time", value: 150.0))
LDObserve.shared.recordUpDownCounter(metric: .init(name: "active_connections", value: 1.0))
// Record logs
LDObserve.shared.recordLog(
message: "User performed action",
severity: .info,
attributes: [
"user_id": .string("12345"),
"action": .string("button_click")
]
)
// Record errors
LDObserve.shared.recordError(
error: paymentError,
attributes: [
"component": .string("payment"),
"error_code": .string("PAYMENT_FAILED")
]
)
// Create spans for tracing
let span = LDObserve.shared.startSpan(
name: "api_request",
attributes: [
"endpoint": .string("/api/users"),
"method": .string("GET")
]
)
span.end()
```
## Contributing
We 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.
## About LaunchDarkly
* 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:
* 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.
* 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?).
* 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.
* 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.
* 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.
* Explore LaunchDarkly
* [launchdarkly.com](https://www.launchdarkly.com/ "LaunchDarkly Main Website") for more information
* [docs.launchdarkly.com](https://docs.launchdarkly.com/ "LaunchDarkly Documentation") for our documentation and SDK reference guides
* [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ "LaunchDarkly API Documentation") for our API documentation
* [launchdarkly.com/blog](https://launchdarkly.com/blog/ "LaunchDarkly Blog Documentation") for the latest product updates