Mob.PostMortem.IOS (mob v0.9.7)

Copy Markdown View Source

MetricKit-backed post-mortem ingest for iOS.

The framework attaches an MXMetricManagerSubscriber in the NIF on the first sweep/0 call, receives MetricKit payloads on a background queue thereafter, and drains the bounded in-memory queue into Mob.Defect.Capsules on Mob.Defect.Bus each time sweep/0 is called.

Scope of a payload

MetricKit hands the OS-delivered payload to the delegate once per incident class per day (approximately — the delivery cadence is under Apple's control). Each delivery may contain one or more diagnostics of different kinds:

MetricKit diagnostickind
MXCrashDiagnostic:native_crash
MXHangDiagnostic:anr (iOS's word is "hang"; the defect taxonomy uses ANR)
MXCPUExceptionDiagnostic:perf_regression
MXDiskWriteExceptionDiagnostic:perf_regression

The Payload suffix belongs on the container MXDiagnosticPayload the OS hands to didReceiveDiagnosticPayloads:, which then exposes the individual diagnostics above.

What the delivery contract looks like from Elixir

sweep/0 is safe to call at any point; MetricKit's own delivery is asynchronous and the queue accumulates until a caller drains it. A typical shape:

# In your app's on_start
def on_start do
  # ...
  Mob.PostMortem.sweep()
end

The top-level Mob.PostMortem.sweep/0 calls into this module. Nothing auto-runs — that is the framework-wide discipline: mob owns the format and the bus but never becomes the collector.

Until observed, not once

The native queue is cleared as it is drained, and MetricKit does not deliver a payload twice, so the capsule on this boot's bus is the only copy; a boot that dies before anyone looks would take the diagnostic with it. So each drained payload is written to Mob.PostMortem.Journal before it is emitted, and every sweep, in this boot or a later one, emits it again until its capsule was handed to a subscriber when emitted or returned by Mob.Defect.Bus.recent/1. Within one boot a re-sweep emits nothing it already emitted.

Redaction

MetricKit's MXCallStackTree carries binary UUIDs, image names and mangled symbol offsets: safe identifiers, no user data. The capsule's fingerprint key uses just the top frame's binary name and offset — enough to group the same crash across launches without embedding anything that varies per crash. The full payload JSON goes onto evidence, bounded by the capsule's existing string-truncation rule.

Platform gating

The NIF is registered on both iOS and Android (Android returns an empty list unconditionally — see android/jni/mob_nif.zig). This module additionally gates on :mob_nif.platform() == :ios so a caller on Android does no work at all, and a caller in a host test environment where the NIF is not loaded fails gracefully.

Summary

Functions

Sweep the MetricKit queue and emit a capsule for each new payload, and for every earlier one not yet observed.

Functions

sweep()

@spec sweep() :: [Mob.Defect.Capsule.t()]

Sweep the MetricKit queue and emit a capsule for each new payload, and for every earlier one not yet observed.

Returns the list of capsules emitted: unobserved journaled payloads first, then new ones in the order the OS delivered them. Empty on Android, on iOS < 14 (no MetricKit delivery API), and when nothing is new or waiting to be observed.