Skip to content
Telemetry

iOS SDK

Log product telemetry

Attach structured product events to the active customer without blocking the app on delivery.

Record a connection failure

BTX.log(...) accepts an event immediately and sends it later. The static facade returns an enqueue disposition, not a network-delivery result.

Record the connection stage, firmware, and failure reason from your companion app’s connection manager. The example uses sample Mithril Wearables state.

Bluetooth connection outcome
BTX.log(
    "device_connection_failed",
    properties: [
        "deviceId": "ring_123",
        "deviceModel": "Mithril One",
        "firmwareVersion": "2.3.7",
        "transport": "bluetooth",
        "connectionStage": "service_discovery",
        "errorCode": "connection_timeout",
        "retryCount": 2
    ]
)

The SDK buffers logs until configuration, identity, and runtime transport are ready.

01

Use structured properties

Properties can contain strings, integers, doubles, booleans, nil, arrays, dictionaries keyed by String, or explicit BTXJSONValue values.

  • Use a stable event name such as device_connection_failed.
  • Prefer product identifiers and bounded values over free-form dumps.
  • Do not include passwords, access tokens, or server credentials.
  • Keep property shapes consistent so events remain comparable.
02

Use automatic runtime context

When logs are enabled, BTX records one canonical app.snapshot event after the runtime starts. It includes privacy-safe app, operating system, device family, architecture, simulator, and SDK version context.

The SDK intentionally excludes advertising identifiers, identifierForVendor, serial numbers, the user-assigned device name, and other stable personal device identifiers.

03

Report device health

Report battery level and health, charging state, firmware, and product-specific attributes from your device integration. Enable .logs and identify the customer before reporting. Call this helper on the main actor when hardware state changes. The manufacturer, model, and namespace below are sample Mithril Wearables values.

Device health snapshot
import Foundation
import BTXClientKit

@MainActor
func reportDeviceHealth(
    peripheralID: UUID,
    hardwareID: String,
    firmwareVersion: String,
    batteryPercent: Double?,
    batteryHealthPercent: Double?,
    isCharging: Bool?,
    hardwareVersion: String? = nil,
    attributes: [String: BTXJSONValue] = [:]
) {
    BTX.deviceSnapshots.refresh(connectionKey: peripheralID.uuidString) {
        BTXDeviceSnapshot(
            identifier: .vendorHardwareID(
                hardwareID,
                namespace: "mithril.hardware_id"
            ),
            additionalIdentifiers: [
                .coreBluetoothPeripheralUUID(peripheralID)
            ],
            manufacturer: "Mithril Wearables",
            category: "wearable",
            model: "Mithril One",
            firmwareVersion: firmwareVersion,
            hardwareVersion: hardwareVersion,
            batteryPercent: batteryPercent,
            batteryHealthPercent: batteryHealthPercent,
            isCharging: isCharging,
            attributes: attributes
        )
    }
}

Battery fields use a 0–100 scale. Use nil for unavailable readings and omit unknown custom attributes. In workspaces with Devices enabled, firmware, hardware revision, battery, and battery health appear in device details. Charging and custom readings are available in the expanded snapshot event JSON.

The vendor hardware ID is the physical identity; the Core Bluetooth peripheral UUID is a scoped alias.

The SDK coalesces refreshes, skips unchanged snapshots, and reports active devices again after customer or project changes. On disconnect, call BTX.deviceSnapshots.disconnect(connectionKey:) to cancel pending work. This stops snapshot reporting; it does not emit a disconnected event.

04

Diagnose connection failures

Create one attempt UUID for a connection attempt, including automatic retries. Report the final failure from your connection manager, with the stage, total elapsed time, retry count, and stable error code. BTX does not automatically observe Bluetooth callbacks.

Failed connection attempt
import Foundation
import BTXClientKit

func recordConnectionFailure(
    deviceID: String,
    attemptID: UUID,
    stage: String,
    durationMs: Int,
    retryCount: Int,
    errorCode: String
) {
    BTX.log(
        "device_connection_failed",
        level: .warning,
        properties: [
            "deviceId": deviceID,
            "attemptID": attemptID.uuidString,
            "transport": "bluetooth",
            "connectionStage": stage,
            "durationMs": durationMs,
            "retryCount": retryCount,
            "errorCode": errorCode
        ]
    )
}

Use the same deviceId as the health snapshot and keep attemptID consistent through scanning, connecting, discovery, and notification setup. Measure duration with a monotonic timer. Report device_connection_ready only after your app's required services and subscriptions are available. Log transitions and outcomes, not every radio callback.

Search Telemetry by device or attempt ID and expand events for their properties. These are host-defined events reviewed through search and JSON details, not an automatically assembled connection trace.

05

Diagnose firmware updates

Keep one update UUID through transfer, reboot, reconnect, verification, and automatic retries. Record firmware_update_started after preflight checks pass. Report a final failure only after your updater stops or exhausts its retry policy.

Failed firmware update
import Foundation
import BTXClientKit

func recordFirmwareUpdateFailure(
    deviceID: String,
    updateID: UUID,
    targetVersion: String,
    stage: String,
    durationMs: Int,
    retryCount: Int,
    errorCode: String
) {
    BTX.log(
        "firmware_update_failed",
        level: .error,
        properties: [
            "deviceId": deviceID,
            "updateID": updateID.uuidString,
            "targetVersion": targetVersion,
            "updateStage": stage,
            "durationMs": durationMs,
            "retryCount": retryCount,
            "errorCode": errorCode
        ]
    )
}

Use stages such as transfer, reboot, reconnect, and verification. Keep failure codes bounded and use the same physical deviceId across logs and snapshots. Your app owns update safety checks and recovery.

After reconnecting, read the version from the device. Emit firmware_update_completed only when it matches the target; otherwise report failed verification. A finished transfer alone is not a successful update. Refresh the health snapshot with the actual installed version and emit only one terminal outcome for each update. If the version cannot be read, report that failure rather than inventing a value.

Search Telemetry by update ID to inspect the update's events. These events do not automatically create firmware rollout groups or success-rate charts. Refresh snapshots separately to update the device's displayed firmware version.