--- name: logs-start description: Start a VivaDicta log capture. Defaults to the simulator; takes sim, device, or structured to pick a tier. disable-model-invocation: true --- # logs-start Start a log capture. Stop it with [`logs-stop`](../logs-stop/SKILL.md), which resolves the active tier on its own - you never have to name it again. ## Argument `$ARGUMENTS` selects the tier. Empty means `sim`. | Argument | Tier | Reaches extensions? | Cost | | --- | --- | --- | --- | | *(empty)* or `sim` | Simulator, live unified log | yes (all sim processes) | none | | `device` | Device, live stdout | **no, main app only** | none | | `structured` | Device, unified log archive | yes | interactive `sudo`, large archive | Anything else: say what was passed, list the three, and stop. Do not guess. **When invoked with no argument**, start the simulator capture as usual, then close your report with this reminder so the other tiers stay discoverable: > Started the simulator capture (default). Other tiers: `/logs-start device` > for live device stdout (main app only), `/logs-start structured` for the > device unified log (the only tier that sees the keyboard and other > extensions). Do not ask which tier to use instead of starting - the default is the default. Only mention the alternatives after the capture is already running. ## Choosing a tier - **`sim`** is the default because it is free and the richest: real unified-log metadata, timestamps, levels, categories, threads. - **`device`** is for hardware-only bugs. Live and cheap, but see the limitation below - it cannot see the keyboard or any other extension. - **`structured`** is the only tier that sees extensions on hardware. Reach for it when the keyboard, share, action, or widget target is involved, or for a post-mortem `.logarchive` you want to reopen in Console.app. ## sim 1. `mkdir -p logs` 2. Run `./scripts/launch_simulator.sh` in a **background/long-lived** shell so `log stream` keeps running. It also writes `logs/.sim-capture.pid` naming the streaming process, which is what `logs-stop` reads to end this capture and nothing else. 3. Report the `logs/sim-YYYYMMDD-HHMMSS.log` path the script prints. Attaches to the already-booted Simulator; it does not relaunch the app, and it fails if none is booted. Captures `Logger` output filtered to `subsystem == "com.antonnovoselov.VivaDicta"`; raw `print()` is not included. ## device 1. `./scripts/launch_device.sh --check` first - it resolves device and bundle id without launching. Fix whatever it reports before continuing. 2. `mkdir -p logs` 3. Run `./scripts/launch_device.sh` in a **background/long-lived** shell. It writes `logs/.device-capture.pid` naming the `devicectl` process, which is what `logs-stop` reads. 4. Report the `logs/device-YYYYMMDD-HHMMSS.log` path. Uses `xcrun devicectl device process launch --console` with `ENABLE_PRINT_LOGS=1`. `--terminate-existing` restarts the app, so in-progress state is lost. ### Limitation: main app only, no extensions `--console` pipes the stdout of the **single process devicectl launched**. Every extension runs in its own system-spawned process, so devicectl never sees it. Their output is absent entirely and nothing says so - you get a complete-looking stream that is silently missing a target. Measured: a capture during keyboard use carried 213 lines from `VivaDicta/` and 0 from `VivaDictaKeyboard/`, though the keyboard logs from `KeyboardViewController`, `KeyboardTextProcessor` and `VivaModeManager`. Use `structured` for extensions. ### Timestamps Lines carry time, level and call site, because `LoggerExtension`'s print mirror stamps them itself: ``` 19:09:40.709 [INFO] VivaDicta/AIService.swift:666 Loaded 1 Viva Modes ``` That is app-side, so it only appears in builds you install. **Bare, unstamped lines mean the installed binary predates the print mirror - rebuild to the device rather than changing the capture.** Category and thread are still absent; stdout has no room for unified-log metadata. ## structured Starts nothing - it only records where to collect *from*. `logs-stop` does the work. 1. Find the device: ```bash xcrun xctrace list devices | grep iPhone | grep -v Simulator | head -1 ``` 2. `mkdir -p llmtemp` 3. ```bash date '+%Y-%m-%d %H:%M:%S' > llmtemp/.device-log-start-time echo "" > llmtemp/.device-log-udid ``` 4. Report the start time and UDID, and warn that `logs-stop` will need interactive `sudo` and will write a large archive. Why this tier reaches extensions: `sudo log collect --device-udid` pulls the device's whole unified log, and extension lines survive the subsystem filter because `LoggerExtension.swift` hardcodes one subsystem across all targets: ```swift private nonisolated let kLoggingSubsystem = "com.antonnovoselov.VivaDicta" ``` That constant is load-bearing. If a target is ever changed to log under its own bundle id, it disappears from these captures silently. ## Concurrent captures `sim` and `device` can run at once; `logs-stop` handles all active tiers, reading one marker file per tier. Do not start a second capture of the *same* tier - the marker is overwritten, so the first stream is orphaned and its file stops receiving the lines you expect. The markers are why `logs-stop` never has to guess. It ends the tier that was actually started, instead of matching `simctl spawn.*log stream` against the machine and catching an unrelated log stream someone else is running. ## Manual analysis ```bash ls -lt logs/sim-*.log | head -5 grep -i "error\|fault" logs/sim-*.log grep "\[AppState\]" logs/sim-*.log log show logs/vivadicta_device_*.logarchive \ --predicate 'subsystem == "com.antonnovoselov.VivaDicta"' ``` ## Related - [`logs-stop`](../logs-stop/SKILL.md) - [`xcodebuild-testing`](../xcodebuild-testing/SKILL.md) - [`axe-simulator-control`](../axe-simulator-control/SKILL.md) - drive the simulator before capturing