iOS App Monitoring
SkyWalking supports iOS/iPadOS app monitoring via the OpenTelemetry Swift SDK. Three data streams are captured:
- Outbound HTTP traffic — per-request client-side metrics for HTTP requests from the iOS app to remote servers (latency, error rate, per-domain breakdown)
- MetricKit daily stats — app launch time percentile, hang time percentile/sum, crash count, OOM kills, memory, network transfer (once per day per device)
- MetricKit diagnostic logs — crash and hang reports with native stack traces
All data is sent via standard OTLP/HTTP to SkyWalking OAP. No OTel Collector is required, though one can be used for buffering.
OAP Configuration
Enable the OTLP receiver with trace and log handlers, the iOS MAL rules, and the LAL rules
in application.yml:
receiver-otel:
selector: default
default:
enabledHandlers: otlp-traces,otlp-logs
enabledOtelMetricsRules: ios/ios-metrickit,ios/ios-metrickit-instance
log-analyzer:
selector: default
default:
lalFiles: ios-metrickit
OTLP/HTTP endpoints are available on the core REST port (default 12800)
at /v1/traces, /v1/logs, and /v1/metrics.
iOS App Setup
import OpenTelemetryApi
import OpenTelemetrySdk
import OpenTelemetryProtocolExporterHTTP
import ResourceExtension
import URLSessionInstrumentation
// Resource attributes (device, OS, app info — auto-collected)
let resources = DefaultResources().get()
// OTLP exporter — point to OAP's REST port (default 12800)
// In production, use an OTel Collector or API gateway in front of OAP
let oapEndpoint = "https://<oap-or-collector-host>/v1"
let traceExporter = OtlpHttpTraceExporter(
endpoint: URL(string: "\(oapEndpoint)/traces")!
)
let logExporter = OtlpHttpLogExporter(
endpoint: URL(string: "\(oapEndpoint)/logs")!
)
// TracerProvider
let tracerProvider = TracerProviderBuilder()
.add(spanProcessor: BatchSpanProcessor(spanExporter: traceExporter))
.with(resource: resources)
.build()
OpenTelemetry.registerTracerProvider(tracerProvider: tracerProvider)
// LoggerProvider (for MetricKit diagnostics)
let loggerProvider = LoggerProviderBuilder()
.with(resource: resources)
.with(processors: [SimpleLogRecordProcessor(logRecordExporter: logExporter)])
.build()
OpenTelemetry.registerLoggerProvider(loggerProvider: loggerProvider)
// Auto-instrument URLSession (exclude collector URL to avoid feedback loop)
let config = URLSessionInstrumentationConfiguration(
shouldInstrument: { request in
return request.url?.host != "<oap-or-collector-host>"
}
)
let _ = URLSessionInstrumentation(configuration: config)
Important: The
shouldInstrumentcallback is critical. Without it, the URLSession auto-instrumentation captures OTLP export calls themselves, creating an exponential feedback loop.
Security: iOS apps send telemetry from the public internet. See Security Notice for deployment guidelines.
Outbound HTTP Traffic
The IOSHTTPSpanListener detects iOS HTTP spans (resource attribute os.name is iOS or
iPadOS, span scope NSURLSession, kind CLIENT) and emits OAL sources (Service,
ServiceInstance, Endpoint) with Layer.IOS. This produces client-side outbound traffic
metrics — these measure HTTP requests from the iOS app to remote servers, not server-side
processing.
The listener reads HTTP attributes with stable-semconv fallback, so all three OpenTelemetry Swift URLSession instrumentation modes work:
| Attribute | Stable key (preferred) | Legacy key (fallback) |
|---|---|---|
| Host | server.address |
net.peer.name |
| Method | http.request.method |
http.method |
| Status code | http.response.status_code |
http.status_code |
- Service = the iOS app (from
service.name) - Instance = the app version (from
service.version) - Endpoint = the remote domain being called (e.g.,
api.example.com)
The existing core.oal rules automatically produce standard outbound traffic metrics under the IOS layer:
service_cpm, service_resp_time, service_sla, service_percentile,
service_instance_cpm, service_instance_resp_time,
endpoint_cpm, endpoint_resp_time, endpoint_sla, endpoint_percentile.
MetricKit Metrics
The IOSMetricKitSpanListener intercepts MetricKit payload spans (scope MetricKit,
span name MXMetricPayload) and extracts daily device statistics. It pushes SampleFamily
samples into the shared MAL pipeline via OpenTelemetryMetricRequestProcessor.toMeter().
These spans are not persisted as traces (24-hour duration is not meaningful in trace view).
Available metrics (prefixed with meter_ios_):
| Metric | Aggregation | Description |
|---|---|---|
app_launch_time_percentile |
histogram_percentile | App launch time (time to first draw) P50-P99. Bucket ceiling 30 s (finite overflow sentinel). |
hang_time_percentile |
histogram_percentile | UI hang (freeze) duration P50-P99. Bucket ceiling 30 s (finite overflow sentinel). |
hang_time_sum |
sum | Total hang time across devices |
foreground_abnormal_exit_count |
sum | Abnormal (crash) exits while app was in foreground |
background_abnormal_exit_count |
sum | Abnormal exits while app was in background (watchdog kills, background-task crashes) |
background_oom_kill_count |
sum | Out-of-memory kills (background memory-pressure exits) |
peak_memory |
max | Peak memory usage (worst-case device) |
cpu_time |
sum | Cumulative CPU time |
gpu_time |
sum | Cumulative GPU time |
disk_write |
sum | Disk write volume |
wifi_download |
avg | Average per-device WiFi download |
wifi_upload |
avg | Average per-device WiFi upload |
cellular_download |
avg | Average per-device cellular download |
cellular_upload |
avg | Average per-device cellular upload |
scroll_hitch_ratio |
avg | Average scroll jank ratio |
Normal-exit counts (metrickit.app_exit.foreground.normal_app_exit_count,
metrickit.app_exit.background.normal_app_exit_count) are intentionally not exposed — graceful
exits carry no diagnostic signal and would only crowd dashboards.
MetricKit Diagnostic Logs
Crash and hang diagnostics from MetricKit arrive as OTLP log records and are processed by
the ios-metrickit LAL rule. The rule uses layer: auto mode with sourceAttribute("os.name")
to detect iOS logs and set the IOS layer.
Diagnostic types:
metrickit.diagnostic.crash— crash reports with exception type, signal, and stack tracemetrickit.diagnostic.hang— hang reports with duration and stack tracemetrickit.diagnostic.cpu_exception— CPU usage exceptionsmetrickit.diagnostic.disk_write_exception— disk write exceptionsmetrickit.diagnostic.app_launch— slow app launch diagnostics (iOS 16+)
Limitations
- MetricKit data requires a real iOS device — not available in Simulator
- MetricKit stats are delivered approximately once per day — not real-time
- Screen/view transition tracking is not automatic — the OTel Swift SDK does not instrument UIViewController or SwiftUI lifecycle