Swift CLI + MCP server wrapping Apple's log for filtered, formatted, time-bounded macOS/iOS log capture.
slog/
├── Package.swift # SPM manifest (macOS 15+, Swift 6.2)
├── Sources/slog/
│ ├── Commands/ # One file per CLI command (ArgumentParser)
│ │ ├── RootCommand.swift # @main, 6 subcommands
│ │ ├── StreamCommand.swift # Live streaming
│ │ ├── ShowCommand.swift # Historical queries
│ │ ├── ProfileCommand.swift # Profile CRUD
│ │ ├── ListCommand.swift # Processes / simulators
│ │ ├── DoctorCommand.swift # System checks
│ │ └── MCPCommand.swift # MCP server entry
│ ├── Core/ # Log handling + shared services
│ │ ├── LogEntry.swift # LogEntry, LogLevel
│ │ ├── LogParser.swift # NDJSON + legacy parser
│ │ ├── LogStreamer.swift # `log stream` driver + PredicateBuilder
│ │ ├── LogReader.swift # `log show` driver
│ │ ├── SignpostAggregator.swift # Pairs os_signpost begin/end → intervals
│ │ ├── DurationParser.swift # "5s", "2m" → Duration
│ │ ├── SystemQuery.swift # Processes / simulators / UDID
│ │ ├── DoctorCheck.swift # System requirement checks
│ │ └── Version.swift # Version constant (CI-overridden)
│ ├── Config/
│ │ ├── XDGDirectories.swift # XDG path resolution
│ │ ├── Profile.swift # Profile model
│ │ └── ProfileManager.swift # Profile CRUD
│ ├── Filters/
│ │ ├── FilterChain.swift # Thread-safe filter chain
│ │ ├── FilterSetup.swift # Predicate + chain + auto-debug builder
│ │ └── Predicates.swift # Composable predicate types
│ ├── MCP/
│ │ ├── SlogTools.swift # 6 MCP tool definitions
│ │ └── SlogResultEnvelope.swift # ResultSummary, NDJSONSpill, envelope builders
│ └── Output/ # Formatters
│ ├── Formatter.swift # Protocol, registry, OutputFormat
│ ├── FormattedEntry.swift # Shared Encodable model (JSON/TOON)
│ ├── FormattedSignpost.swift # Shared Encodable model for signpost output
│ ├── SignpostFormatter.swift # Signpost table / JSON / TOON renderer
│ ├── PlainFormatter.swift
│ ├── ColorFormatter.swift
│ ├── JSONFormatter.swift
│ ├── ToonFormatter.swift # Token-optimized
│ └── DedupWriter.swift # Collapses consecutive identical messages
├── Sources/TestEmitter/main.swift # Test log emitter for e2e
└── Tests/slogTests/ # Apple Testing framework
swift build [-c release]
swift test [--filter <name>]
swift run slog [args]
swiftformat .Requirements: macOS 15+, Swift 6.2, Xcode toolchain.
Dependencies: swift-argument-parser, swift-subprocess, Rainbow (ANSI), ToonFormat, SwiftMCP (swift-cli-mcp).
Test emitter (separate executable for e2e):
swift run slog-test-emitter [--repeat N | --continuous] [--signpost | --smoke <nonce>]--signpost emits os_signpost intervals (concurrent same-name, in-flight, event) instead of os_log messages. --smoke <nonce> emits one tagged line through every Apple logging mechanism (print/NSLog/os_log/Logger/signpost) for the logging-visibility check; scripts/logging-smoke-test.sh drives it and prints a capture matrix of what slog/the unified log can see.
Six subcommands; stream is the default. See slog --help or skills/use-slog/SKILL.md for flags.
- stream — Live logs, bounded by
--timeout/--capture/--count. - show — Historical logs from
--last/--start/--end/ archive path. Caps display with--limit(not--count). - stream/show
--signpost— Reportos_signpostinterval durations instead of log messages. Pairs begin↔end by (process, signpost name, signpost id), aggregates per name (count/p50/max/total), and prints a table (or--format json/toon). In-flight begins show null duration. Livestream --signpostneeds no persistence;show --signpostreads the persisted store (custom subsystems may needlog config --mode persist:debug). - profile —
create/list/show/deletesaved filter combos. Apply via--profile <name>on stream/show. - list —
list processes [--filter],list simulators [--booted] [--all]. - doctor — Verify log CLI / stream / archive access, simctl, profiles dir.
- mcp — Start MCP server.
--setupprints integration instructions.
Shared by slog_show, slog_stream, slog_list_processes (in MCP/SlogResultEnvelope.swift):
- ≤50 items → inline. >50 items →
summary(where applicable) +head/tail(10 each) + NDJSON spill atoutput_file(default$XDG_CACHE_HOME/slog/runs/). full: true→ inline everything.slog_showextras:summary_only: true(just the aggregate);source_file: "<path>"(re-query a previous spill, skip the OS scan);next_sincein every response (latest matched timestamp + 1µs) for tailing — pass back as nextstart;scan_capped: truewhen the 100k-event ceiling is hit.limitcaps retained entries; summary always covers the full matched population.slog_stream.countis optional (1–1000); omit to capture untiltimeout, capped at 1000. Whenstopped_by == "error"the response carrieserror_message; ifcaptured == 0it also setstry_doctor: true.slog_signpostdoes not use the shared envelope. It returns aggregated intervals:{ count, in_flight, orphan_ends, elapsed_ms, mode, intervals, hint? }, whereintervalsis grouped by name withmin_ms/p50_ms/max_ms/total_ms(nil stats omitted).full: trueadds per-occurrence detail.mode: "stream"(live capture, setlive: true) vs"show"(persisted query vialast/start/archive_path).- Error payloads are
{ "error": ... }; system-level failures (missing CLI, permission denied, simctl issues) add"try_doctor": trueto steer the caller towardslog_doctor.
ResultEnvelopeBuilder is LogEntry-specific (computes ResultSummary); ListEnvelopeBuilder<T: Encodable> handles list-style tools without a summary.
Sources/slog/Commands/NewCommand.swiftimplementingAsyncParsableCommand.- Register as a subcommand in
RootCommand.swift. - Shared logic in
Core/(service struct/enum). - Add the MCP tool in
MCP/SlogTools.swift. - Tests in
Tests/slogTests/(Apple Testing:@Suite,@Test,#expect,#require).
- Swift 6.2; async/await, Sendable types.
- Protocol-based extensibility (
LogFormatter,LogPredicate). - Builder pattern for predicates and filter chains.
- CLI commands and MCP tools share the same Core services (thin wrappers).
- CI:
.github/workflows/ci.ymlrunsswift test(compiles + tests) on every push/PR tomain. .slog-versionat repo root is the source of truth.Sources/slog/Version.swiftdefaults to"dev"; CI generates the real value before building.- Release: bump
.slog-version→ push → trigger "Release" workflow in GitHub Actions → workflow runs tests, tags, builds universal binary, publishes release, updatesalexmx/homebrew-tools/Formula/slog.rbSHA256. - Homebrew install:
brew install alexmx/tools/slog.
- Never add
Co-Authored-Bytrailers. - Conventional Commits (
feat:,fix:,docs:,chore:, etc.) with scope where it helps.