Skip to content

Report iPad crashes and quiet five iPad log lines - #126

Merged
whoisaldo merged 3 commits into
release/0.3.4-candidatefrom
feat/ipad-crash-reporting
Oct 29, 2026
Merged

whoisaldo merged 3 commits into
release/0.3.4-candidatefrom
feat/ipad-crash-reporting

Conversation

@whoisaldo

@whoisaldo whoisaldo commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

What changed

The iPad app sent no crash information, so its crash-free rate couldn't be measured. It now reports crashes through the existing support log and upload. Every line follows the Automatically share diagnostics setting: uploaded while it is on, kept on the iPad while it is off. This PR also quiets five iPad log lines that the field scan found noisy or at the wrong level.

MetricKit (Core/CrashReporting.swift)

CrashReporting is an MXMetricManagerSubscriber, registered in EternalMonitorApp.init. Each report becomes one line:

  • Crash (error, category crash). The line has:
    • the exception type and code, and the signal, each with its name;
    • the uncaught Objective-C exception's name, if Apple named it: NS…Exception or one of a few Apple classes (such as CALayerInvalidGeometry). Any other name is other, and the exception's message is never logged;
    • the app version and build that crashed, and the OS version and build;
    • the top 8 frames of the crashed thread, as binary+0xoffset. MetricKit doesn't symbolize, so these offsets resolve with the build's dSYM through atos;
    • the termination reason's namespace and code, last on the line. Its description is dropped, and a reason that doesn't start Namespace <NAME>, Code <code> is other.
  • Hang (warning, hang): the duration and the attributed thread's top frames.
  • CPU exception (warning, cpu): CPU and sampled time, and the heaviest sampled path, innermost frame first.
  • Exit counts (info, exits): one line per daily metric payload. It counts foreground and background exits: normal, abnormal, memory limit, watchdog, bad access and illegal instruction. The background also has CPU limit, memory pressure, suspended with a locked file, and background task timeout.

Example lines:

MetricKit crash app=0.3.4 build=370 os=ipados:26.1 os_build=23B85 at=2026-10-26T07:33:20Z exception_type=1(EXC_BAD_ACCESS) exception_code=2 signal=11(SIGSEGV) objc_exception=- thread=attributed frames=libsystem_kernel.dylib+0x1a2b00,EternalMonitor+0x1a2b10,EternalMonitor+0x1a2b20 termination=namespace:SIGNAL code:11
MetricKit crash app=0.3.4 build=370 os=ipados:26.1 os_build=23B85 at=2026-10-26T07:33:20Z exception_type=10(EXC_CRASH) exception_code=0 signal=6(SIGABRT) objc_exception=NSRangeException thread=attributed frames=libsystem_kernel.dylib+0x1a2b00,EternalMonitor+0x1a2b10 termination=namespace:SIGNAL code:6
MetricKit hang app=0.3.4 build=370 os=ipados:18.6 os_build=22G86 at=2026-10-26T07:33:20Z duration_ms=2500 thread=attributed frames=libsystem_kernel.dylib+0x1a2b00,EternalMonitor+0x1a2b10,EternalMonitor+0x1a2b20
MetricKit cpu_exception app=0.3.4 build=370 os=ipados:26.1 os_build=23B85 at=2026-10-26T07:33:20Z cpu_ms=81000 sampled_ms=180000 thread=first frames=VideoToolbox+0x50,EternalMonitor+0x40,dyld+0x20
MetricKit exits app=0.3.4 build=370 os=ipados:26.1 os_build=23B85 versions=one from=2026-10-25T07:33:20Z to=2026-10-26T07:33:20Z fg_normal=12 fg_abnormal=1 fg_memory_limit=2 fg_watchdog=3 fg_bad_access=4 fg_illegal_instruction=5 bg_normal=30 bg_abnormal=0 bg_memory_limit=6 bg_watchdog=0 bg_bad_access=0 bg_illegal_instruction=0 bg_cpu_limit=1 bg_memory_pressure=0 bg_locked_file=0 bg_task_timeout=2

These lines carry no free text from MetricKit, and no user data:

  • The VM region info is dropped, because it can name mapped files. So are the termination reason's description and the Objective-C exception's message.
  • From the call stack tree, the line takes only binary names (ASCII tokens, at most 64 characters) and text-segment offsets.
  • As a last pass over the whole line, UUIDs, runs of 16 or more hex digits, and runs of 20 or more letters and digits (base64-like) become <id>.
  • Every field is bounded, so a line stays under 2 KiB, far inside the 8 KiB line cap.

Foreground marker

ForegroundMarker is a small UserDefaults entry:

  • It is set when the scene becomes active, and keeps the app version, the build and the connection state (active, connecting or none).
  • It is cleared when the scene goes to the background, and on willTerminate.

If the next launch finds it, the app logs Previous launch ended while EternalMonitor was on screen (possible crash or forced quit) with the stream state, as a warning in lifecycle. A marker left by another version or build is cleared without a line, so the first launch after an update reports nothing.

Log fixes from the field scan

  • No reply to HELLO. HELLO sent N×… but no datagrams came back is now a warning, for a PC that is off, asleep or elsewhere. It stays an error when the PC at that address accepted a session earlier in this launch. The wording is unchanged, so the daily check's hello_no_reply signal still matches.
  • Path not ready. Network.framework waiting and UDP path not ready were three lines per attempt: two warnings and an error. UDPReceiver now reports a waiting path once per connection and doesn't log it itself. ConnectionManager logs UDP path not ready once, as a warning, for both no path and Local Network turned off.
  • USB receive failure. A failure was logged three times. FramedLink no longer logs NWConnection receive, and it logs a failure itself only while no connection has taken the link (a link waiting for its preamble). The connection's state=… USB receive failed line stays.
  • Media for another session. Dropped media for foreign session is logged once per accepted session, and again after a reset.
  • Fragment count mismatch. Ignored fragment … mismatched count is logged once per session. FrameAssembler.mismatchedCountFragments counts the rest, and the stream summary line shows the count as mismatched_count_frags=N. A stream restart within the session keeps the count.

SupportDiagnostics takes its DiagnosticsUpload as an init parameter (default .shared), so a test can check the setting end to end.

Tests

New CrashReportingTests:

  • the crash, hang, CPU and exit line formats, from MetricKit's call stack tree JSON, both per-thread and aggregated;
  • the line cap, with a termination reason holding paths, quotes and newlines;
  • the termination reason: only the namespace and code are kept, with a path through a folder named after a person, a UUID in the reason, and reasons that don't parse;
  • the Objective-C exception name: NSRangeException is kept, and a person's name run together, names with spaces or a newline, and over-long names become other;
  • UUIDs and long hex or base64 runs taken out of binary names;
  • the exit counts, including a payload with only the four background-only exits;
  • the level and category of each report;
  • sharing on and off, through a real SupportDiagnostics and DiagnosticsUpload outbox;
  • the marker: a launch that ended on screen is reported once, background and clean exit leave no marker, a version or build change reports nothing, and start() logs it.

Other tests:

  • Log fixes: ConnectionProblemTests and NoPictureTimeoutTests cover the HELLO level. The second uses a real loopback PC that first doesn't answer, then answers.
  • Path not ready: UDPReceiverTests checks that a name that doesn't resolve is reported once per connection. SessionSummaryTests checks the one warning line.
  • Other drops: UDPReceiverTests covers foreign-session media, and FrameAssemblerTests covers the mismatched-count count.
  • Not unit-tested: the MetricKit object adapters (init(_ crash: MXCrashDiagnostic, …) and the others). MetricKit's classes can't be built in a test, and the simulator never delivers payloads. The formatting they feed is covered above.

Release notes

The iPad app now reports crashes, hangs and unexpected exits with its diagnostics, if you share them. That lets us find and fix crashes. The reports hold only app and system details, never your files, PC names or what's on your screen. A few connection messages in the support log are also quieter and easier to read.

A MetricKit subscriber logs one line per crash, hang and CPU exception
(exception, signal, termination reason, versions, top 8 frames) and one
line of exit counts per daily payload. A foreground marker reports a
launch that ended on screen, except across an update. The lines go
through the support log, so they follow the sharing setting.
An unanswered HELLO is a warning unless that PC answered earlier in the
launch. A path that isn't ready is one warning per connection. A USB
receive failure is logged once. Foreign-session media and mismatched
fragment counts are logged once per session; the stream summary counts
the mismatches.
The termination reason is reduced to its namespace and code, an
Objective-C exception name is kept only when Apple's pattern names it,
and a last pass takes UUIDs and long hex or base64 runs out of the
whole line. The exits line adds the four background-only counters.
@whoisaldo
whoisaldo marked this pull request as ready for review October 29, 2026 19:21
@whoisaldo
whoisaldo merged commit 37a3208 into release/0.3.4-candidate Oct 29, 2026
7 checks passed
@whoisaldo whoisaldo added the 0.3.4 Part of the 0.3.4 release label Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

0.3.4 Part of the 0.3.4 release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants