karagoz logs
Reads the device log once and prints the newest records as JSON.
- Android emulator Done
- Android device Done
- iOS simulator Done
- iOS device Done
karagoz logs [--package <package>] [--since <seconds>] [--lines <n>] [--device <id>]Each record is whole: a stack trace is one record with newlines in its message, not one entry per line.
Before you start
Section titled “Before you start”- On an iOS simulator: Xcode, opened once.
- On an iPhone: Xcode 15 or newer, and the iPhone paired with the Mac and connected by USB cable.
Options
Section titled “Options”| Option | Default | Meaning |
|---|---|---|
--package <package> |
all records | Keep only this app’s records. On Android, those written under its Linux user id (uid) in the main Android user; on a simulator, see On an iOS simulator; on an iPhone, see On an iPhone. |
--since <seconds> |
Android: the whole log. iOS: the last minute; with --package, the whole log on a simulator and the last hour on an iPhone |
Unix time in seconds on the device clock, up to 9 decimals and at most 4294967295. Only records stamped later are read. |
--lines <n> |
30 |
How many of the newest matching records to return, 1 to 999999999. |
--device <id> |
see Device selection |
Examples
Section titled “Examples”Read one app’s records
Section titled “Read one app’s records”karagoz logs --package com.android.shell{"device":"emulator-5554","package":"com.android.shell","uid":2000,"records":[{"time":1790521762.474251,"pid":24931,"tid":24931,"level":"I","tag":"KaragozManual","message":"hello from karagoz"}],"omitted":0}exit 0That record was written with adb shell log -t KaragozManual "hello from karagoz"; adb shell runs as com.android.shell.
Output
Section titled “Output”{ "device": "emulator-5554", "package": "com.android.shell", "uid": 2000, "records": [ { "time": 1790521762.474251, "pid": 24931, "tid": 24931, "level": "I", "tag": "KaragozManual", "message": "hello from karagoz" } ], "omitted": 0}An empty records with omitted 0 means nothing matched. It is not an error.
devicestring- Serial or UDID of the device read.
packagestring- Only with
--package: the package as given. uidnumber- Only with
--package, on Android: the uid the records were matched on.com.android.settingshas 1000, which it shares with the system server. recordsarray- The matching records, at most
--lines, oldest first: on Android in the order the device's log daemon received them, on iOS by time. records[].timenumber- When the record was written: seconds since the Unix epoch on the device clock, to the microsecond. Rounded up on Android; on iOS as
logprints it. records[].pidnumber- The process that wrote it.
records[].tidnumber- The thread that wrote it.
records[].levelstring- One of
V,D,I,W,E,F;?for any other priority. On iOS,D,I,E,For?(see On an iOS simulator). records[].tagstring- On Android the log tag; on iOS the name of the process that wrote it.
records[].subsystemstring- Only on iOS: the record's
os_logsubsystem,""when it has none. records[].categorystring- Only on iOS: the record's
os_logcategory,""when it has none. records[].messagestring- The message, newlines kept.
omittednumber- How many older matching records
--linesleft out.0whenrecordsholds every match.
Reading since your last call
Section titled “Reading since your last call”Pass the largest time of one result as the next --since to get only newer records. The rounding up makes this exact: no record comes back in the next call. After the call above:
karagoz logs --package com.android.shell --since 1790521762.474251{"device":"emulator-5554","package":"com.android.shell","uid":2000,"records":[],"omitted":0}exit 0Take the largest time, not the last record’s: records are in arrival order, and times can step back by a few milliseconds.
Errors
Section titled “Errors”| Code | When |
|---|---|
INVALID_ARGS | '<value>' is not a package name, as for launch; --since must be Unix time in seconds (got '<value>'); --lines must be a whole number from 1 to 999999999 (got '<value>'). Checked before any device call. |
APP_NOT_FOUND | package 'dev.karagoz.missing' is not installed on emulator-5554. |
ADB_FAILED | logcat printed its own error instead of records, such as Failed to wait for logd.ready to become true. logd not running?; the output was not whole log records; or pm list packages printed an error while Karagöz looked up the uid. The message is that output, cut at 300 characters. A log larger than the 64 MB Karagöz reads at once is ADB_FAILED too, with a message that says to pass --since. |
ADB_TIMEOUT | After 10 s. |
Every call can also get the device selection errors and the adb errors.
Notes on Android
Section titled “Notes on Android”- Logs read: main, system and crash, plus kernel from Android 11, logcat’s defaults. The events log is not read.
- The log is never cleared, resized or reconfigured, so other tools and the user keep their history. A call reads what is there and exits; nothing streams. To wait for a line, call again with
--since. - On a full log of 26 MB, about 133,000 records,
logs --lines 1took 0.7 s, 0.8 s with--package. - The default 30 records come to about 7 KB of JSON on average. The largest 30-record result measured was 47 KB.
--packagematches the uid, not a process: every process of the app, every restart, and its Java and native crash lines. Lines the system server writes about the app, such asStart procandANR in, have uid 1000 and are not included. A package that shares a system uid, such as Settings, gets the other processes of that uid as well.--packagereads the app in the main Android user. Its copies in Secure Folder, a work profile or a second instance (Dual Messenger, Dual Apps) run under another uid and are not included.- A record stamped at or before
--sincethat the log daemon receives after the previous read is returned by neither call. Records arrived up to 54 ms late in testing. --sinceis on the device clock, which can differ from the computer’s, even with automatic time on. Pass atimefrom an earlier result.- A missing line does not prove the app did not write it: some phones drop lines before they reach the log. A
log.tagproperty ofI(adb shell getprop log.tag) dropsDandVlines, Developer options’ “Logger buffer sizes: Off” drops nearly all, and Huawei and Honor phones keep app logs off until “AP Log” is turned on in a hidden menu. - Android cuts a record’s tag and message at 4068 bytes together when it is written. Bytes that are not valid UTF-8 become U+FFFD.
- From Android 15 the device ends a read after 5 s without data and still exits
0, so a read cut short looks complete.
On an iOS simulator
Section titled “On an iOS simulator”karagoz logs --package dev.karagoz.probe --lines 1{"device":"2EC769C1-2EF2-47EB-A441-E6843968F039","package":"dev.karagoz.probe","records":[{"time":1792054562.937849,"pid":43961,"tid":5236136,"level":"I","tag":"Probe","subsystem":"com.apple.Accessibility","category":"AXAppAccessibility","message":"Presentation controller doesn't modalize: <_UIRootPresentationController: 0x10536f350>"}],"omitted":297}exit 0- The window is a time span, since
log showhas no record count. On an idle simulator a minute took under 1 s and an hour 8 s. A read that passes 30 s fails withSIMCTL_TIMEOUT. --package:- keeps the records of every process that runs the installed build of the app: the app and its extensions, with the lines of the frameworks they load, from this install and from earlier installs of the same build.
- also keeps the
exited ...linelaunchd_simwrites when such a process ends, the only trace of a crash by signal. - leaves out records of other builds of the app, other processes’ lines about the app, and
printand stderr output. - does not tell apart two apps whose executables are byte for byte the same. Two Flutter apps made from the template with the same plugins are an example.
level: Debug isD, Info and Default areI, Error isE, Fault isF, anything else?.Logger.noticeandNSLogwrite Default.- The simulator keeps Info records in memory only for a few minutes (gone after 213 s in testing) and does not store Debug records by default.
timecomes from the computer’s clock. Chained reads, as in Reading since your last call, return nothing twice. Of two records in the same microsecond, the second can be lost.- The log is never cleared or reconfigured.
- Karagöz runs
simctl spawn <udid> log show --style ndjson --info --debugonce, bounded in time and filtered, and reads its output as it streams.
Errors on a simulator
Section titled “Errors on a simulator”| Code | When |
|---|---|
APP_NOT_FOUND | app 'dev.karagoz.nope' is not installed on <udid>, before the read. |
SIMCTL_FAILED | log's own error, such as log: Bad predicate .... |
SIMCTL_FAILED | Output that is not log show's records: unexpected log output: <line>, log show ended without its summary line. |
SIMCTL_TIMEOUT | After 30 s: log show did not finish within 30s. Pass a later --since, or --package, to read less of the simulator log. |
DEVICE_NOT_READY | The simulator is not booted. |
On an iPhone
Section titled “On an iPhone”Reads a paired iPhone attached by USB cable, with no sudo and nothing installed on the Mac or the phone.
- Each read needs about 230 MB of free space in the temporary folder for the length of the call. A short window does not make it smaller: a minute, an hour and a
--packageread each move an archive of about 230 MB from the phone, in 8 to 10 s. - The whole log moves more: five weeks of records came to 1.1 GB and took 30 s.
- The temporary folder is removed after the read, also when the call is cancelled.
- Records older than the default window are there. The phone stores Info records, unlike a simulator, and some Debug records.
--packagekeeps the records of the app’s install folders that are proven to be this app: the folder it is installed in now, and any folder one of whose processes launchd started under the app’s id in the archive read.- A build changed since the last install always logs under its current folder. A build installed again unchanged logs under the folder of its first install, and is proven once it starts or stops within the read.
- When nothing is proven, every folder with the app’s folder name is kept. Another app with the same name, such as a second Flutter app’s
Runner.app, can then mix in. The phone gives no way to read a build’s identity, which a simulator does. - Exit lines come from
launchd.
<private>stands where the app or the system marked a value private: the phone masks it before Karagöz reads it. Karagöz does not change the phone’s log settings.timeis the phone’s clock.- A locked phone reads the same. Two reads of one phone at once both work; the second took 14 s.
How the iPhone log is read
Karagöz takes this Mac’s pairing record for the phone from usbmuxd; it stays in memory. Karagöz opens a session with the phone over TLS and asks the phone’s log service for an archive of its records from a start time. tar unpacks the archive into a temporary folder, and macOS’s own log show reads it as on a simulator.
Errors on an iPhone
Section titled “Errors on an iPhone”| Code | When |
|---|---|
APP_NOT_FOUND | Before the read. |
DEVICE_NOT_READY | The phone is not connected by USB cable. |
DEVICE_NOT_READY | This Mac holds no pairing record for the phone: unplug it, plug it in, tap Trust. |
DEVICE_NOT_READY | The phone refuses the session. |
HELPER_FAILED | The phone's log service or the stream fails. |
HELPER_FAILED | Nothing comes for 30 s. |
HELPER_FAILED | tar or log fails, with its own message. |
HELPER_FAILED | log show did not finish within 30 s. Pass a later --since, or --package, to read less of the iPhone log. |
WRITE_FAILED | The temporary folder cannot be created. |
MCP equivalent
Section titled “MCP equivalent”Arguments: package, since, lines, device. The names are the CLI's option and positional names.
Related commands
Section titled “Related commands”- karagoz launch
- Rejects a malformed package name with the same message.