Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions Sources/Features/LogView/LogSessionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -414,15 +414,10 @@ private struct PackagePicker: View {
// Dismiss the popover first, then apply the filter on the next runloop —
// mutating ancestor state while the popover tears down can crash on macOS.
show = false
// Android & simulator resolve the package/bundle id to PIDs. Physical iOS
// streams structured os_log and scopes by the app's *process name* (the
// display name), so pass that there. `nil` = "All processes".
let value: String
if let app {
value = session.device.platform == .iosDevice ? app.display : app.id
} else {
value = ""
}
// Every platform now scopes by the package / bundle id: Android & simulator
// resolve it to PIDs; physical iOS launches that bundle under devicectl's console
// for un-redacted os_log + print(). Empty = "All processes" (whole-device passive).
let value = app?.id ?? ""
DispatchQueue.main.async { onSelect(value) }
}
}
Expand Down
19 changes: 13 additions & 6 deletions Sources/Model/AppModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -354,12 +354,19 @@ final class AppModel {
case .android: return adb.map { AndroidLogSource(adbURL: $0, serial: device.id) }
case .iosSimulator: return SimulatorLogSource(udid: device.id)
case .iosDevice:
// Structured logs (level · subsystem · category) via Apple's private
// LoggingSupport engine (OSActivityStream) — the Xcode/Console-grade
// stream. `bundleID` here is the selected app's process/display name and
// narrows the whole-device stream to that app (empty = whole device).
// Falls back to idevicesyslog internally if the private API is unavailable.
return IOSDeviceOSLogSource(udid: device.id, processFilter: bundleID)
// Whole device → passive structured stream via Apple's LoggingSupport
// engine (real level · subsystem · category, no app launch); falls back to
// idevicesyslog internally if the private API is unavailable. Its catch:
// private os_log args arrive as <private> (the relay redacts them).
//
// A targeted app → launch it under devicectl's console with
// OS_ACTIVITY_DT_MODE=enable, which mirrors that app's os_log **un-redacted**
// plus print()/stdout — the Xcode-console experience, scoped to the app.
// `bundleID` is the app's bundle id (launching is by bundle id, so it
// re-launches the app each time capture starts, by design).
return bundleID.isEmpty
? IOSDeviceOSLogSource(udid: device.id, processFilter: nil)
: IOSDeviceConsoleLogSource(udid: device.id, bundleID: bundleID)
}
}
// Simulators can additionally stream the targeted app's stdout (`print()`),
Expand Down
16 changes: 8 additions & 8 deletions Sources/Model/LogSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -387,9 +387,9 @@ final class LogSession: WorkspaceTab {
$0.pids = package.isEmpty ? nil : []
$0.processNameQuery = ""
case .iosDevice:
// The structured (LoggingSupport) source narrows to the targeted app's
// process itself, so no per-line LogFilter process query is needed; the
// label is the app's process/display name.
// The *source itself* is swapped per target (whole-device passive stream
// vs. devicectl-console launch of the bundle), so no per-line LogFilter
// process query is needed. The label is the app's bundle id.
$0.processNameQuery = ""
$0.pids = nil
}
Expand All @@ -399,11 +399,11 @@ final class LogSession: WorkspaceTab {
restartPrimaryForTargetChange()
}

/// Physical iOS re-scopes its *primary* structured source when the targeted app
/// changes (unlike Android/simulator, which keep one source and filter/launch-console
/// on top): an empty target streams the whole device, a name scopes to that app's
/// process. Stopping the current source lets the reconnect loop respawn with the new
/// scope; the source emits its own "▶︎ structured device logs (…)" marker.
/// Physical iOS swaps its *primary* source when the targeted app changes (unlike
/// Android/simulator, which keep one source and filter on top): an empty target →
/// whole-device passive LoggingSupport stream; a bundle id → a devicectl-console
/// launch of that app (un-redacted os_log + print()). Stopping the current source
/// lets the reconnect loop respawn the right one; each source emits its own marker.
private func restartPrimaryForTargetChange() {
guard isRunning, device.platform == .iosDevice else { return }
swappingSource = true
Expand Down