Skip to content
karagöz docs

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
~/app
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.

  • 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.
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
~/app
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 0

That record was written with adb shell log -t KaragozManual "hello from karagoz"; adb shell runs as 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
}

An empty records with omitted 0 means nothing matched. It is not an error.

device string
Serial or UDID of the device read.
package string
Only with --package: the package as given.
uid number
Only with --package, on Android: the uid the records were matched on. com.android.settings has 1000, which it shares with the system server.
records array
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[].time number
When the record was written: seconds since the Unix epoch on the device clock, to the microsecond. Rounded up on Android; on iOS as log prints it.
records[].pid number
The process that wrote it.
records[].tid number
The thread that wrote it.
records[].level string
One of V, D, I, W, E, F; ? for any other priority. On iOS, D, I, E, F or ? (see On an iOS simulator).
records[].tag string
On Android the log tag; on iOS the name of the process that wrote it.
records[].subsystem string
Only on iOS: the record's os_log subsystem, "" when it has none.
records[].category string
Only on iOS: the record's os_log category, "" when it has none.
records[].message string
The message, newlines kept.
omitted number
How many older matching records --lines left out. 0 when records holds every match.

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:

~/app
karagoz logs --package com.android.shell --since 1790521762.474251
{"device":"emulator-5554","package":"com.android.shell","uid":2000,"records":[],"omitted":0}exit 0

Take the largest time, not the last record’s: records are in arrival order, and times can step back by a few milliseconds.

CodeWhen
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_FOUNDpackage 'dev.karagoz.missing' is not installed on emulator-5554.
ADB_FAILEDlogcat 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_TIMEOUTAfter 10 s.

Every call can also get the device selection errors and the adb errors.

  • 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 1 took 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.
  • --package matches 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 as Start proc and ANR 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.
  • --package reads 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 --since that the log daemon receives after the previous read is returned by neither call. Records arrived up to 54 ms late in testing.
  • --since is on the device clock, which can differ from the computer’s, even with automatic time on. Pass a time from 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.tag property of I (adb shell getprop log.tag) drops D and V lines, 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.
~/app
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 show has 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 with SIMCTL_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 ... line launchd_sim writes 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 print and 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 is D, Info and Default are I, Error is E, Fault is F, anything else ?. Logger.notice and NSLog write 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.
  • time comes 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 --debug once, bounded in time and filtered, and reads its output as it streams.
CodeWhen
APP_NOT_FOUNDapp 'dev.karagoz.nope' is not installed on <udid>, before the read.
SIMCTL_FAILEDlog's own error, such as log: Bad predicate ....
SIMCTL_FAILEDOutput that is not log show's records: unexpected log output: <line>, log show ended without its summary line.
SIMCTL_TIMEOUTAfter 30 s: log show did not finish within 30s. Pass a later --since, or --package, to read less of the simulator log.
DEVICE_NOT_READYThe simulator is not booted.

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 --package read 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.
  • --package keeps 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.
  • time is 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.

CodeWhen
APP_NOT_FOUNDBefore the read.
DEVICE_NOT_READYThe phone is not connected by USB cable.
DEVICE_NOT_READYThis Mac holds no pairing record for the phone: unplug it, plug it in, tap Trust.
DEVICE_NOT_READYThe phone refuses the session.
HELPER_FAILEDThe phone's log service or the stream fails.
HELPER_FAILEDNothing comes for 30 s.
HELPER_FAILEDtar or log fails, with its own message.
HELPER_FAILEDlog show did not finish within 30 s. Pass a later --since, or --package, to read less of the iPhone log.
WRITE_FAILEDThe temporary folder cannot be created.
MCP equivalentlogs {"package":"com.android.shell"}MCP tools

Arguments: package, since, lines, device. The names are the CLI's option and positional names.

karagoz launch
Rejects a malformed package name with the same message.