Skip to content

Commit 41526a9

Browse files
authored
Merge pull request #1667 from doronz88/feature/syslog-no-debug-info
syslog: Add `--no-debug` and `--no-info`
2 parents fcd45a9 + a1df4ff commit 41526a9

3 files changed

Lines changed: 160 additions & 8 deletions

File tree

pymobiledevice3/cli/syslog.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@
1919
user_requested_colored_output,
2020
)
2121
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
22-
from pymobiledevice3.services.os_trace import OsTraceService, SyslogEntry, SyslogLogLevel
22+
from pymobiledevice3.services.os_trace import (
23+
OS_TRACE_RELAY_STREAM_FLAGS_DEFAULT,
24+
OsActivityStreamFlag,
25+
OsTraceService,
26+
SyslogEntry,
27+
SyslogLogLevel,
28+
)
2329
from pymobiledevice3.services.syslog import SyslogService
2430

2531
logger = logging.getLogger(__name__)
@@ -194,6 +200,8 @@ async def syslog_live(
194200
include_label: bool,
195201
regex: list[str],
196202
insensitive_regex: list[str],
203+
no_debug: bool = False,
204+
no_info: bool = False,
197205
image_offset: bool = False,
198206
start_after: Optional[str] = None,
199207
output_format: SyslogFormat = SyslogFormat.TEXT,
@@ -206,14 +214,27 @@ async def syslog_live(
206214
print(f'Waiting for "{start_after}" ...', flush=True)
207215

208216
should_color_output = output_format is SyslogFormat.TEXT and user_requested_colored_output()
217+
stream_flags = OS_TRACE_RELAY_STREAM_FLAGS_DEFAULT
218+
if no_debug:
219+
stream_flags &= ~OsActivityStreamFlag.DEBUG
220+
if no_info:
221+
stream_flags &= ~OsActivityStreamFlag.INFO
209222

210-
async for syslog_entry in OsTraceService(lockdown=service_provider).syslog(pid=pid):
223+
async for syslog_entry in OsTraceService(lockdown=service_provider).syslog(pid=pid, stream_flags=stream_flags):
211224
if process_name and posixpath.basename(syslog_entry.filename) != process_name:
212225
continue
213226

214227
if pid != -1 and syslog_entry.pid != pid:
215228
continue
216229

230+
if no_debug and syslog_entry.level == SyslogLogLevel.DEBUG:
231+
continue
232+
233+
if no_info and syslog_entry.level == SyslogLogLevel.INFO:
234+
# I don't really understand why INFO is retrieved when not requested, but this is imitating
235+
# Console.app behavior
236+
continue
237+
217238
if output_format is SyslogFormat.JSON:
218239
json_line = format_json_line(syslog_entry)
219240
_emit_line(json_line, out)
@@ -350,6 +371,20 @@ async def cli_syslog_live(
350371
help="Include image offset in log line (text mode only; JSON always emits image_offset).",
351372
),
352373
] = False,
374+
no_debug: Annotated[
375+
bool,
376+
typer.Option(
377+
"--no-debug",
378+
help="Suppress DEBUG entries.",
379+
),
380+
] = False,
381+
no_info: Annotated[
382+
bool,
383+
typer.Option(
384+
"--no-info",
385+
help="Suppress INFO entries.",
386+
),
387+
] = False,
353388
start_after: Annotated[
354389
Optional[str],
355390
typer.Option(
@@ -382,6 +417,8 @@ async def cli_syslog_live(
382417
include_label,
383418
regex or [],
384419
insensitive_regex or [],
420+
no_debug,
421+
no_info,
385422
image_offset,
386423
start_after,
387424
output_format,

pymobiledevice3/services/os_trace.py

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#!/usr/bin/env python3
21
import dataclasses
32
import plistlib
43
import struct
@@ -20,6 +19,54 @@
2019
SYSLOG_LINE_SPLITTER = "\n\x00"
2120

2221

22+
class OsActivityStreamFlag(IntEnum):
23+
"""
24+
Exact `os_activity_stream_flag_t` values from Apple private headers.
25+
26+
Based on: https://github.com/limneos/oslog/blob/master/ActivityStreamAPI.h
27+
"""
28+
29+
PROCESS_ONLY = 0x00000001
30+
SKIP_DECODE = 0x00000002
31+
PAYLOAD = 0x00000004
32+
HISTORICAL = 0x00000008
33+
CALLSTACK = 0x00000010
34+
DEBUG = 0x00000020
35+
NO_SENSITIVE = 0x00000080
36+
INFO = 0x00000100
37+
PROMISCUOUS = 0x00000200
38+
39+
40+
class OsActivityStreamType(IntEnum):
41+
"""
42+
Exact `os_activity_stream_type_t` values from Apple private headers.
43+
44+
Based on: https://github.com/limneos/oslog/blob/master/ActivityStreamAPI.h
45+
"""
46+
47+
ACTIVITY_CREATE = 0x0201
48+
ACTIVITY_TRANSITION = 0x0202
49+
ACTIVITY_USERACTION = 0x0203
50+
TRACE_MESSAGE = 0x0300
51+
LOG_MESSAGE = 0x0400
52+
LEGACY_LOG_MESSAGE = 0x0480
53+
TIMESYNC = 0x0500
54+
SIGNPOST = 0x0600
55+
LOSS = 0x0700
56+
STATEDUMP_EVENT = 0x0A00
57+
58+
59+
OS_TRACE_RELAY_MESSAGE_FILTER_ALL = 0xFFFF
60+
61+
# Sniffed from Console.app when enabling both info and debug messages
62+
OS_TRACE_RELAY_STREAM_FLAGS_DEFAULT = (
63+
OsActivityStreamFlag.PAYLOAD
64+
| OsActivityStreamFlag.HISTORICAL
65+
| OsActivityStreamFlag.CALLSTACK
66+
| OsActivityStreamFlag.DEBUG
67+
)
68+
69+
2370
class SyslogLogLevel(IntEnum):
2471
NOTICE = 0x00
2572
INFO = 0x01
@@ -229,14 +276,19 @@ async def collect(
229276
await self.create_archive(f, size_limit=size_limit, age_limit=age_limit, start_time=start_time)
230277
TarFile(file).extractall(out)
231278

232-
async def syslog(self, pid=-1) -> typing.AsyncGenerator[SyslogEntry, None]:
279+
async def syslog(
280+
self,
281+
pid: int = -1,
282+
message_filter: int = OS_TRACE_RELAY_MESSAGE_FILTER_ALL,
283+
stream_flags: int = OS_TRACE_RELAY_STREAM_FLAGS_DEFAULT,
284+
) -> typing.AsyncGenerator[SyslogEntry, None]:
233285
await self.connect()
234286
assert self.service is not None
235287
await self.service.send_plist({
236288
"Request": "StartActivity",
237-
"MessageFilter": 65535,
289+
"MessageFilter": message_filter,
238290
"Pid": pid,
239-
"StreamFlags": 60,
291+
"StreamFlags": stream_flags,
240292
})
241293

242294
(length_length,) = struct.unpack("<I", await self.service.recvall(4))

tests/cli/test_syslog.py

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,29 @@
66
import pytest
77

88
from pymobiledevice3.cli import syslog as syslog_module
9-
from pymobiledevice3.services.os_trace import SyslogEntry, SyslogLabel, SyslogLogLevel
9+
from pymobiledevice3.services.os_trace import (
10+
OS_TRACE_RELAY_STREAM_FLAGS_DEFAULT,
11+
OsActivityStreamFlag,
12+
SyslogEntry,
13+
SyslogLabel,
14+
SyslogLogLevel,
15+
)
1016

1117
pytestmark = [pytest.mark.cli]
1218

1319
_FAKE_SERVICE_PROVIDER = object()
1420

1521

1622
class _FakeOsTraceService:
23+
last_pid = None
24+
last_stream_flags = None
25+
1726
def __init__(self, entries):
1827
self._entries = entries
1928

20-
async def syslog(self, pid=-1):
29+
async def syslog(self, pid=-1, stream_flags=OS_TRACE_RELAY_STREAM_FLAGS_DEFAULT, **_kwargs):
30+
type(self).last_pid = pid
31+
type(self).last_stream_flags = stream_flags
2132
for entry in self._entries:
2233
yield entry
2334

@@ -34,6 +45,12 @@ def _create_syslog_entry(message: str) -> SyslogEntry:
3445
)
3546

3647

48+
def _create_syslog_entry_with_level(message: str, level: SyslogLogLevel) -> SyslogEntry:
49+
entry = _create_syslog_entry(message)
50+
entry.level = level
51+
return entry
52+
53+
3754
def _create_syslog_entries(messages: list[str]) -> list[SyslogEntry]:
3855
return [_create_syslog_entry(message) for message in messages]
3956

@@ -55,6 +72,8 @@ async def _run_syslog_live(
5572
"include_label": False,
5673
"regex": [],
5774
"insensitive_regex": [],
75+
"no_debug": False,
76+
"no_info": False,
5877
}
5978
kwargs.update(syslog_live_kwargs)
6079

@@ -244,6 +263,50 @@ async def test_syslog_live_regex_filters_and_writes_plain_output_to_out(monkeypa
244263
assert out_lines == printed_lines
245264

246265

266+
@pytest.mark.asyncio
267+
async def test_syslog_live_no_debug_suppresses_only_debug(monkeypatch, capsys):
268+
entries = [
269+
_create_syslog_entry_with_level("info line", SyslogLogLevel.INFO),
270+
_create_syslog_entry_with_level("debug line", SyslogLogLevel.DEBUG),
271+
_create_syslog_entry_with_level("notice line", SyslogLogLevel.NOTICE),
272+
_create_syslog_entry_with_level("error line", SyslogLogLevel.ERROR),
273+
]
274+
275+
printed_lines, _ = await _run_syslog_live(
276+
monkeypatch,
277+
capsys,
278+
entries,
279+
no_debug=True,
280+
)
281+
282+
assert len(printed_lines) == 3
283+
assert printed_lines[0].endswith("info line")
284+
assert printed_lines[1].endswith("notice line")
285+
assert printed_lines[2].endswith("error line")
286+
assert _FakeOsTraceService.last_stream_flags == (OS_TRACE_RELAY_STREAM_FLAGS_DEFAULT & ~OsActivityStreamFlag.DEBUG)
287+
288+
289+
@pytest.mark.asyncio
290+
async def test_syslog_live_no_info_suppresses_only_info(monkeypatch, capsys):
291+
entries = [
292+
_create_syslog_entry_with_level("info line", SyslogLogLevel.INFO),
293+
_create_syslog_entry_with_level("debug line", SyslogLogLevel.DEBUG),
294+
_create_syslog_entry_with_level("notice line", SyslogLogLevel.NOTICE),
295+
]
296+
297+
printed_lines, _ = await _run_syslog_live(
298+
monkeypatch,
299+
capsys,
300+
entries,
301+
no_info=True,
302+
)
303+
304+
assert len(printed_lines) == 2
305+
assert printed_lines[0].endswith("debug line")
306+
assert printed_lines[1].endswith("notice line")
307+
assert _FakeOsTraceService.last_stream_flags == (OS_TRACE_RELAY_STREAM_FLAGS_DEFAULT & ~OsActivityStreamFlag.INFO)
308+
309+
247310
def test_format_json_line_with_label():
248311
entry = SyslogEntry(
249312
pid=42,

0 commit comments

Comments
 (0)