e-volv
Docs menu

iOS

e-volv-logs-swift (Swift package EvolveLogs) is the SDK for iOS 15+ and macOS 13+. Foundation + URLSession only — no third-party dependencies (system zlib for gzip, os.log for diagnostics). Mobile clients differ from the server SDKs in transport only: a disk-backed queue that survives process death, lifecycle-driven flush and long back-off.

Install

Add the e-volv-logs-swift Swift package (packages/logs-swift) in Xcode — File → Add Package Dependencies — and add the EvolveLogs library to your app target.

Initialise

swift
import EvolveLogs

EvolveLogs.initialize(EvolveLogsOptions(
    key: "evk_pub_…",   // public project key — minted on the project's page
    url: "https://api.e-volv.io/api/public/v1/logs",
    service: "acme-shop",
    environment: "production",
    release: "1.4.2"    // the value you later upload dSYMs for
))

Logs.info("order created", attrs: ["orderId": "o_1", "total": 42.5])
Logs.error("payment failed", attrs: ["orderId": "o_1"])

do {
    try charge()
} catch {
    Logs.exception(error, attrs: ["orderId": "o_1"]) // exception.type/message/stack
}

Logs.flush() // safe to call repeatedly; also automatic every 2 s and on background

Initialize from UIApplicationDelegate.application(_:didFinishLaunchingWithOptions:) (or your SwiftUI .init) before any Logs call. If the key or URL is missing the SDK is a no-op and warns once via os.log; it never throws into your code.

Key kinds

This SDK refuses a server key (evk_…) at init — a server key must never ship inside a binary. Use a public key (evk_pub_…, minted on the Observer project’s page). Every request carries x-evolve-app-id (your bundle identifier) and a generated, persisted x-evolve-install-id; the ingest checks the key’s app-id allowlist and caps per-install volume, which is what bounds the damage from an extracted key.

Traces and spans

Trace context lives in a @TaskLocal, so it flows across await and into Swift concurrency child tasks:

swift
let span = client.startSpan("db.query", attrs: ["table": "orders"])
do {
    try db.query("…")
    span.end()
} catch {
    span.end(error) // ends the span as failed
}

// rethrowing closure form — a thrown error ends the span failed and
// is rethrown, so control flow is unchanged:
let rows = try client.span("db.query") { try db.query("…") }

// queue hops: continue an inbound traceparent (consumer side)
Logs.runWithTraceparent(message.headers["traceparent"] ?? "") {
    Logs.info("job received", attrs: ["jobId": job.id])
    client.startSpan("queue.consume").end()
}

span.end records an event with span.name and durationMs — the ingest turns it into a span row, which is how span() reaches the trace graph. Logs.traceparent() returns the W3C 00-<traceId>-<spanId>-01 header of the current trace, or an empty string outside one. Spans nest by parent linkage; an absent or malformed inbound header starts a fresh trace.

URLSession propagation

Outbound, requests made inside a trace get a traceparent header (an existing header is never overwritten) via a URLProtocol you opt into per session — no swizzling:

swift
let config = URLSessionConfiguration.default
URLSessionIntegration.inject(into: config)
let session = URLSession(configuration: config)

(The documented URLProtocol limits: background sessions and requests handed straight to AVFoundation/WebKit bypass it.) Inbound, the SDK reads traceparent off responses with one isolated, guarded swizzle of URLSession.dataTask(_:completionHandler:) — installed once at initialize — so a crash reported from a completion handler joins the server trace that caused it.

Crash capture

NSSetUncaughtExceptionHandler for NSException crashes, and a BSD signal handler for SIGABRT/SIGBUS/SIGFPE/SIGILL/SIGSEGV/SIGTRAP. Both chain to the previously installed handler and re-raise, so the process still terminates (and is still reported) by the OS exactly as before.

The crash-time write path is a small C shim and async-signal-safe: the event JSON (current session id, service attrs, and the last 32 buffered events as breadcrumbs) is pre-serialized into a pre-allocated buffer; the handler formats only the exception fields with snprintf and lands the file with one write(2). The crash event is sent on next launch.

Limits, stated plainly: there are no Mach exception ports in this version. A pure signal handler cannot catch every crash class on iOS — stack overflow and some Swift runtime traps that go straight to Mach exception handling may bypass it. Mach ports (a dedicated handler thread that sees everything) are the known follow-up. The crash file is written to the app’s queue directory; if the device dies with it unsent it ships on the next launch.

Lifecycle, sessions, offline

The buffer is a disk queue (one atomic file per event, read at launch before new events) with a 1 MB byte cap and drop-oldest — a crash, a kill or a reboot loses nothing that was already logged. Flush is lifecycle-driven: every 2 seconds in foreground and on applicationDidEnterBackground. Failed batches stay on disk and back off rather than dropping. A fresh session.id per app foreground epoch, an app.start marker, and foreground/background transitions as events — session.id rides on every event (crash-free session rate). 429 honours Retry-After, else exponential backoff (500 ms doubling, capped at 10 s), 3 attempts; 413 halves the batch. Losses are visible on client.droppedCount.

Symbolication: upload your dSYMs

Release builds ship stripped binaries — without symbols, crash frames read as 0x1045a8f3c in MyApp. Upload the dSYM per release with the CLI (a server key, from CI, never from the app):

shell
npx @evolve/logs-cli upload-artifacts \
  --key evk_… --url https://api.e-volv.io \
  --release 1.4.2 --platform ios --type dsym \
  MyApp.app.dSYM

Uploading backfills already-received occurrences for the same release.

Out of scope (this version)

Mach exception ports (see Crash capture above); App Attest / Play Integrity attestation and the short-lived-token key exchange; session replay, performance vitals/RUM, network waterfall and metrics ingest; user-identity or device-fingerprint collection beyond the install id (which the user can clear).

Package reference: packages/logs-swift. Back to SDK overview.