iOS SDK Integration
This guide covers every module of the Kubesense iOS SDK (v1.0.0): adding the SDK, initializing the core, and enabling RUM, Logs, distributed tracing (including OpenTelemetry), Session Replay, crash reporting, WebView tracking, Feature Flags and profiling — plus automatic URLSession instrumentation.
| Field | Value |
|---|---|
| Platforms | iOS 15.0+, tvOS 15.0+ (Session Replay and WebView tracking are iOS only) |
| Languages | Swift and Objective-C |
| Distribution | Swift Package Manager (https://github.com/kubesense-ai/kubesense-ios-sdk) and CocoaPods trunk |
| Modules | KubesenseCore (required), KubesenseRUM, KubesenseLogs, KubesenseTrace, KubesenseSessionReplay, KubesenseCrashReporting, KubesenseWebViewTracking, KubesenseFlags, KubesenseProfiling |
Prerequisites
Before integrating, obtain the following from your Kubesense workspace:
| Value | Where to find it |
|---|---|
applicationID | Settings → RUM Application Management → your application → View Details |
clientToken | Settings → RUM Application Management → your application → View Details |
kubesenseRumEndpoint | Your Kubesense collector host, for example us2.kubesense.ai |
The client token is a public token meant for client-side SDKs, so it is safe to ship in the app. Never put a server API key in a mobile app.
Core Setup
1. Add the SDK
Swift Package Manager. In Xcode, choose File → Add Package Dependencies… and enter https://github.com/kubesense-ai/kubesense-ios-sdk, or add it to Package.swift:
dependencies: [
.package(url: "https://github.com/kubesense-ai/kubesense-ios-sdk.git", from: "1.0.0")
],
targets: [
.target(
name: "MyApp",
dependencies: [
.product(name: "KubesenseCore", package: "kubesense-ios-sdk"),
.product(name: "KubesenseRUM", package: "kubesense-ios-sdk"),
// Add only the modules you use:
.product(name: "KubesenseLogs", package: "kubesense-ios-sdk"),
.product(name: "KubesenseCrashReporting", package: "kubesense-ios-sdk"),
]
)
]CocoaPods. Add one pod per module to your Podfile, then run pod install:
platform :ios, '15.0'
use_frameworks!
target 'MyApp' do
pod 'KubesenseCore', '~> 1.0'
pod 'KubesenseRUM', '~> 1.0'
# Add only the modules you use:
pod 'KubesenseLogs', '~> 1.0'
pod 'KubesenseCrashReporting', '~> 1.0'
end2. Initialize the SDK
Initialize the core as early as possible — in application(_:didFinishLaunchingWithOptions:) or your SwiftUI App initializer — before enabling any feature:
import KubesenseCore
import KubesenseRUM
Kubesense.initialize(
with: Kubesense.Configuration(
clientToken: "<CLIENT_TOKEN>",
env: "production",
service: "my-ios-app",
// Single intake host for every feature. Host only — no scheme, no path.
kubesenseRumEndpoint: "<your-collector-host>"
),
trackingConsent: .granted
)
RUM.enable(with: RUM.Configuration(applicationID: "<RUM_APPLICATION_ID>"))warning: One endpoint for every feature: RUM, Logs, Traces, Session Replay, Profiling and Flags all upload to kubesenseRumEndpoint. The value is a host, with an optional port — intake.example.com or intake.example.com:8443. Any scheme or path you include is ignored; uploads always go over HTTPS to https://<host>/rum/api/v1/.... Leave it out to upload to the site selected with site: — .prod (us2.kubesense.ai) by default.
| Setting | Meaning |
|---|---|
clientToken | Required. The RUM application's client token. |
env | Required. The environment name you filter by in Kubesense, for example production or staging. |
kubesenseRumEndpoint | Your collector host. |
site | Used when kubesenseRumEndpoint is not set: .prod (us2.kubesense.ai, default) or .staging. |
service, version | Identify the app. version defaults to the app's bundle version. |
batchSize, uploadFrequency, batchProcessingLevel | Tune how much data is uploaded, and how often. |
backgroundTasksEnabled | Use background tasks to finish uploads when the app goes to the background. |
remoteConfigurationEnabled, remoteConfigurationRefreshPeriod | See Remote Configuration. |
Objective-C uses the same API with the Kubesense prefix: [KubesenseSDK initializeWithConfiguration:…], KubesenseConfiguration, KubesenseRUMConfiguration and so on.
To see SDK messages in the Xcode console while integrating, set Kubesense.verbosityLevel = .debug before Kubesense.initialize.
Remote Configuration
info: Enabled by default. Applications write no remote-configuration code — Kubesense.initialize() does all of it.
Sampling rates, feature switches and privacy masking can be managed centrally in Kubesense and delivered to the SDK, so you can retune or switch off a feature without shipping an app release. The iOS SDK uses the same configuration document as the Android SDK.
Kubesense.initialize()reads the document cached by the previous launch — a local read, never a network call — and applies its settings before uploads start.- Each
X.enable()skips the feature if it is switched off remotely, then applies its own settings over what your code passed. - A fresh document is fetched in the background for the next launch, and fetched again periodically.
warning: Configuration is applied at startup, so remote changes take effect at the next launch, not mid-session.
Kubesense.Configuration(
clientToken: "<CLIENT_TOKEN>",
env: "production",
kubesenseRumEndpoint: "<your-collector-host>",
remoteConfigurationEnabled: false, // stay on in-code settings only
remoteConfigurationRefreshPeriod: 6 * 60 * 60 // seconds; the default
)Values below one minute are raised to one minute; zero or less fetches once after initialization (and when the app returns to the foreground), never on a timer. Missing, malformed or out-of-range values never override your code — see the Android SDK's Remote Configuration section for how the document is applied and which settings it controls.
Features
Real User Monitoring (RUM)
Module
KubesenseRUM (SPM product or pod).
Enable RUM
Automatic tracking of UIKit and SwiftUI views and actions is off until you pass a predicate:
import KubesenseRUM
RUM.enable(
with: RUM.Configuration(
applicationID: "<RUM_APPLICATION_ID>",
sessionSampleRate: 100,
uiKitViewsPredicate: DefaultUIKitRUMViewsPredicate(),
uiKitActionsPredicate: DefaultUIKitRUMActionsPredicate(),
swiftUIViewsPredicate: DefaultSwiftUIRUMViewsPredicate(),
swiftUIActionsPredicate: DefaultSwiftUIRUMActionsPredicate(isLegacyDetectionEnabled: true),
urlSessionTracking: .init(
firstPartyHostsTracing: .trace(hosts: ["api.example.com"], sampleRate: 20)
),
trackBackgroundEvents: false,
appHangThreshold: 0.25,
trackWatchdogTerminations: true
)
)| Option | Default | Meaning |
|---|---|---|
applicationID | — | Required. The RUM application ID. |
sessionSampleRate | 100 | Percentage of sessions recorded (0–100). |
uiKitViewsPredicate, uiKitActionsPredicate | nil | Track UIViewControllers as views and UIKit taps as actions. |
swiftUIViewsPredicate, swiftUIActionsPredicate | nil | Track SwiftUI views and taps automatically. |
urlSessionTracking | nil | Track URLSession requests as resources; see Network Requests. |
trackFrustrations | true | Detect error taps and other frustration signals. |
trackBackgroundEvents | false | Keep recording while the app is in the background. |
longTaskThreshold | 0.1 s | Report main-thread tasks longer than this. |
appHangThreshold | nil (off) | Report app hangs longer than this many seconds. |
trackWatchdogTerminations | false | Report watchdog terminations. |
vitalsUpdateFrequency | .average | How often CPU, memory and frame-rate vitals are sampled. |
trackAnonymousUser | true | Keep a stable anonymous ID across sessions. |
collectAccessibility | false | Collect accessibility settings on views. |
viewEventMapper, actionEventMapper, resourceEventMapper, errorEventMapper, longTaskEventMapper | nil | Edit or drop events before they are sent. |
SwiftUI views and actions
struct CheckoutView: View {
var body: some View {
Form { /* ... */ }
.trackRUMView(name: "checkout")
}
}
Button("Place order") { placeOrder() }
.trackRUMTapAction(name: "Place order")Manual view tracking
let rum = RUMMonitor.shared()
rum.startView(key: "checkout", name: "Checkout")
// ... when the screen goes away:
rum.stopView(key: "checkout")
// Or for a view controller:
rum.startView(viewController: self, name: "Checkout")Manual action tracking
RUMMonitor.shared().addAction(type: .tap, name: "Place order")
// Continuous actions, such as a scroll:
RUMMonitor.shared().startAction(type: .scroll, name: "Product list")
RUMMonitor.shared().stopAction(type: .scroll, name: "Product list")Manual error reporting
do {
try placeOrder()
} catch {
RUMMonitor.shared().addError(error: error, source: .source)
}
RUMMonitor.shared().addError(message: "Cart is empty", source: .source)Custom timings, attributes and feature operations
let rum = RUMMonitor.shared()
// Time from the start of the current view:
rum.addTiming(name: "first_item_visible")
// On all following events, or on the current view only:
rum.addAttribute(forKey: "plan", value: "pro")
rum.addViewAttribute(forKey: "cart_items", value: 3)
// Multi-step flows and whether they succeed:
rum.startFeatureOperation(name: "checkout", operationKey: nil, attributes: [:])
rum.succeedFeatureOperation(name: "checkout", operationKey: nil, attributes: [:])rum.currentSessionID { id in ... } returns the current session ID, and rum.stopSession() ends the session; the next view or action starts a new one.
Network Requests (URLSession)
With urlSessionTracking set on RUM.Configuration (or Trace.Configuration), requests made with URLSession are tracked automatically as RUM resources, and hosts listed in firstPartyHostsTracing (and their subdomains) receive distributed tracing headers. A host may contain one * wildcard, such as api-*.example.com.
To also capture the detailed timing breakdown (DNS, connection, TLS, first byte, download), enable it for your session delegate class:
class APISessionDelegate: NSObject, URLSessionDataDelegate {}
URLSessionInstrumentation.enableDurationBreakdown(
with: .init(delegateClass: APISessionDelegate.self)
)
let session = URLSession(
configuration: .default,
delegate: APISessionDelegate(),
delegateQueue: nil
)note: List only your own hosts in firstPartyHostsTracing: third-party APIs may reject unknown tracing headers. Use .traceWithHeaders(hostsWithHeaders:sampleRate:) to choose the header formats per host.
Logs
Module
KubesenseLogs.
Enable Logs and create a logger
import KubesenseLogs
Logs.enable()
let logger = Logger.create(
with: Logger.Configuration(
name: "checkout",
networkInfoEnabled: true,
remoteLogThreshold: .info, // lower levels stay on the device
consoleLogFormat: .short // also print to the Xcode console
)
)
logger.info("Checkout started")
logger.warn("Slow payment provider")
logger.error("Payment failed", attributes: ["reason": "card_declined"])
logger.addAttribute(forKey: "cart_id", value: "c-123") // on every log of this logger
logger.addTag(withKey: "team", value: "payments")Levels are debug, info, notice, warn, error and critical. bundleWithRumEnabled and bundleWithTraceEnabled (both true by default) link each log to the current RUM session and active span.
Distributed Tracing
Module
KubesenseTrace.
Enable Trace
import KubesenseTrace
Trace.enable(
with: Trace.Configuration(
urlSessionTracking: .init(
firstPartyHostsTracing: .trace(hosts: ["api.example.com"])
),
networkInfoEnabled: true
)
)Create spans
let span = Tracer.shared().startSpan(operationName: "checkout")
span.setTag(key: "cart.items", value: 3)
// ... work ...
span.finish()OpenTelemetry support
KubesenseTrace provides an OpenTelemetry tracer provider, so code instrumented with the OpenTelemetry API reports to Kubesense:
import OpenTelemetryApi
OpenTelemetry.registerTracerProvider(tracerProvider: OTelTracerProvider())
let tracer = OpenTelemetry.instance.tracerProvider.get(
instrumentationName: "checkout",
instrumentationVersion: nil
)
let span = tracer.spanBuilder(spanName: "place-order").startSpan()
// ... work ...
span.end()Session Replay
Session Replay records what the user saw, as a video-like replay attached to the RUM session. It requires RUM and is available on iOS.
Module
KubesenseSessionReplay.
Enable Session Replay
Enable it after RUM:
import KubesenseSessionReplay
SessionReplay.enable(
with: SessionReplay.Configuration(
replaySampleRate: 20, // % of RUM sessions that get a replay
textAndInputPrivacyLevel: .maskSensitiveInputs,
imagePrivacyLevel: .maskNonBundledOnly,
touchPrivacyLevel: .hide,
startRecordingImmediately: true,
featureFlags: [.swiftui: true] // record SwiftUI views; off by default
)
)Privacy levels
| Setting | Values (default first) |
|---|---|
textAndInputPrivacyLevel | maskAll, maskAllInputs, maskSensitiveInputs |
imagePrivacyLevel | maskAll, maskNonBundledOnly, maskNone |
touchPrivacyLevel | hide, show |
Override the levels for a single view and its subviews, or hide it entirely:
myView.kubesense.sessionReplayPrivacyOverrides.textAndInputPrivacy = .maskAll
myView.kubesense.sessionReplayPrivacyOverrides.hide = truewarning: SwiftUI views are only recorded with featureFlags: [.swiftui: true]. Without it, SwiftUI screens appear as placeholders in the replay.
Crash Reporting
Module
KubesenseCrashReporting (built on KSCrash).
Enable crash reporting
Enable it together with RUM (and Logs, if you use them), which receive the crash reports:
import KubesenseCrashReporting
CrashReporting.enable()Crash reports are sent at the next launch. For app hangs and watchdog terminations, set appHangThreshold and trackWatchdogTerminations on RUM.Configuration.
WebView Tracking
If a page shown in a WKWebView already runs the Kubesense browser SDK, its events can join the app's RUM session.
Module
KubesenseWebViewTracking.
Setup
import KubesenseWebViewTracking
WebViewTracking.enable(webView: webView, hosts: ["shop.example.com"])
// When the web view is no longer used:
WebViewTracking.disable(webView: webView)List only the hosts you trust.
Feature Flags
Module
KubesenseFlags.
Enable Flags and evaluate flags
import KubesenseFlags
Flags.enable() // after Kubesense.initialize and RUM.enable
let flags = FlagsClient.create()
flags.setEvaluationContext(
FlagsEvaluationContext(
targetingKey: "user-123",
attributes: ["plan": .string("pro")]
)
) { result in
// Flags are ready (or kept their defaults if loading failed).
}
let enabled = flags.getBooleanValue(key: "checkout.enabled", defaultValue: false)getStringValue, getIntegerValue, getDoubleValue and getObjectValue work the same way, and each has a …Details variant that also returns the variant, the reason and any error.
Flags are read from a cache filled by one request when the evaluation context is set, so evaluating a flag never waits on the network. Flags.Configuration controls trackExposures, trackEvaluations and rumIntegrationEnabled (all true by default); with the RUM integration, each evaluation is added to the current RUM view.
note: Feature Flags need flag support on your Kubesense collector. Until it is available there, every flag returns its defaultValue.
Profiling
info: Experimental. Requires RUM.
Module
KubesenseProfiling.
Enable Profiling
import KubesenseProfiling
Profiling.enable(
with: Profiling.Configuration(
applicationLaunchSampleRate: 5, // % of app launches profiled (default 5)
continuousSampleRate: 5 // % of RUM sessions profiled continuously (default 5)
)
)Launch profiles cover the app's startup; continuous profiling records CPU and wall-time samples during sampled sessions and links them to long tasks and app hangs.
Identifying Users
Set the user after sign-in and clear it on sign-out. Use internal IDs rather than emails unless you're allowed to send them:
Kubesense.setUserInfo(id: user.id, name: user.displayName)
Kubesense.setAccountInfo(id: user.accountID) // optional: the user's company or account
// On sign-out:
Kubesense.clearUserInfo()
Kubesense.clearAccountInfo()Tracking Consent
The trackingConsent passed to Kubesense.initialize says whether the user has agreed to be tracked:
| Value | Effect |
|---|---|
.granted | Data is collected and sent. |
.pending | Data is collected and kept on the device until you decide. |
.notGranted | Nothing is sent, and data waiting on the device is deleted. |
If you ask for consent after startup, start with .pending, then call:
Kubesense.set(trackingConsent: .granted) // or .notGrantedVerify the Integration
- Run the app, open a few screens, tap a button, make a network request, log a message and trigger a handled error.
- In Kubesense RUM, find the session under your application, environment and service, with its views, action, resource, log and error. It can take a minute to appear.
- If enabled, check for a replay with masked content, a crash report after relaunching a crashed app, and a WebView session linked to the app session.
| Problem | What to check |
|---|---|
| No data in Kubesense | kubesenseRumEndpoint is the host only (no https://, no path); the client token and application ID belong to that collector; consent isn't .pending or .notGranted; the device can reach the collector over HTTPS. Set Kubesense.verbosityLevel = .debug. |
| No views or actions | Pass uiKitViewsPredicate / swiftUIViewsPredicate (and the actions predicates), or track them manually. |
CocoaPods: Unable to find a specification for KubesenseCore | Your local specs are out of date: run pod repo update, then pod install. |
| SwiftUI screens are blank in replays | Enable featureFlags: [.swiftui: true] on SessionReplay.Configuration. |
Best Practices
- Initialize early — call
Kubesense.initialize()and theenable()calls at app launch, to capture the first screen and early errors. - Enable RUM first — then Logs, Trace, Session Replay, Flags and crash reporting, which all correlate with the RUM session.
- Pass the view and action predicates — without them, nothing is tracked automatically.
- List only your own hosts as first-party — tracing headers go to every listed host and its subdomains.
- Keep Session Replay masked by default — relax privacy only on screens you've checked, with
sessionReplayPrivacyOverrides. - Provide safe flag defaults — the
defaultValueis what users get when flags can't be loaded. - Start with
.pendingconsent when you need consent first; data is uploaded only after.granted.