How to Retrieve Logs from a Running Container or System Process: Three Methods Explained
The container CLI provides three distinct commands—container logs, container machine logs, and container system logs—to retrieve stdout/stderr streams from containers, virtual machines, and the container subsystem itself via file handle-based XPC transport.
The Apple container repository provides a Swift-based CLI for managing containerized workloads on macOS. When debugging running applications or diagnosing system issues, you need to retrieve logs from a running container or system process efficiently. The tool implements three specialized commands that stream log data directly through file handles rather than copying buffers over XPC, ensuring high-performance log retrieval even for large outputs.
Retrieving Container Logs with container logs
The container logs command displays the stdout/stderr output of a container's main process. According to the source code in Sources/ContainerCommands/ContainerLogs.swift, the ContainerLogs.run() method creates a ContainerClient, calls client.logs(id:) (implemented in ContainerAPIService), and receives a pair of FileHandle objects—one for the container's stdio and one for the boot log.
Basic Usage and Flags
# Show the complete stdout/stderr of the container
container logs my-web-server
# Show only the last 100 lines
container logs -n 100 my-web-server
# Follow the log output in real-time (like tail -f)
container logs --follow my-web-server
# View the VM boot log instead of the app's stdio
container logs --boot my-web-server
Under the hood, the command selects the appropriate handle using the boolean flag: boot ? fhs[1] : fhs[0]. When retrieving historical lines, it invokes tail() which walks backward from the end of the file to collect the requested number of lines. When --follow is set, it switches to an AsyncStream that watches the file descriptor for new data, handling log rotation and container restarts gracefully.
Accessing Container Machine Logs
Container machines are the virtual machines that run one or more containers. The container machine logs command, implemented in Sources/ContainerCommands/Machine/MachineLogs.swift, mirrors the container-log flow but targets the VM itself.
# Show the latest logs from the default machine
container machine logs
# Show the last 200 lines of a specific machine
container machine logs -n 200 my-machine
# Follow the log output live
container machine logs --follow my-machine
# Include the VM boot log
container machine logs --boot my-machine
The MachineLogs command contacts the XPC MachineAPIService via MachinesService.logs(id:), which returns an array of FileHandles representing the machine's logs. The command then prints or follows the selected handle exactly as ContainerLogs does, using the same async streaming mechanism for real-time updates.
Querying System Logs
Unlike container and machine logs that read from file handles provided by XPC services, the container system logs command interfaces directly with macOS's unified logging system. Located in Sources/ContainerCommands/SystemLogs.swift, the SystemLogs.run() method constructs a log command line and executes it via Process.
# Show logs from the last 5 minutes (default)
container system logs
# Show logs from the last 2 hours
container system logs --last 2h
# Follow system-log events as they occur
container system logs --follow
The implementation builds arguments such as log show --last 5m --predicate "subsystem = 'com.apple.container'". If --follow is supplied, it uses log stream instead. The process stdout/stderr are attached directly to the CLI, so output appears immediately without buffering.
Programmatic Log Access
You can also retrieve logs programmatically using the Swift client libraries directly:
// Using the ContainerClient to fetch stdio and boot logs
let client = ContainerClient()
let handles = try await client.logs(id: "my-web-server")
// handles[0] = stdio log, handles[1] = boot log
let stdioData = try handles[0].readToEnd()
print(String(data: stdioData!, encoding: .utf8)!)
// Fetching machine logs via the XPC client
let machineClient = MachineClient()
let machineHandles = try await machineClient.logs(id: "my-machine")
let logData = try machineHandles[0].readToEnd()
print(String(data: logData!, encoding: .utf8)!)
// Executing system logs manually via Process
let process = Process()
process.launchPath = "/usr/bin/env"
process.arguments = ["log", "show", "--last", "10m",
"--predicate", "subsystem = 'com.apple.container'"]
process.standardOutput = FileHandle.standardOutput
try process.run()
process.waitUntilExit()
Key Implementation Files
The log retrieval architecture spans these critical source files:
ContainerLogs.swift– CLI command implementing thecontainer logssubcommand with tail/follow logic and file-handle selectionMachineLogs.swift– CLI command forcontainer machine logsthat mirrors container-log handling for virtual machinesSystemLogs.swift– CLI command forcontainer system logsthat builds and executes the OSlogutilityContainersService.swift– XPC server methodlogs(id:)that opens the container's log files and returns file descriptorsMachinesService.swift– XPC server methodlogs(id:)for container-machine log filesContainerClient.swift– Client wrapper that issues the XPClogsrequest and returns[FileHandle]MachineClient.swift– Client wrapper for machine-log XPC calls
Summary
- Three distinct commands handle different log sources:
container logsfor application output,container machine logsfor VM-level logs, andcontainer system logsfor subsystem diagnostics. - File-handle transport avoids network copying by passing direct file descriptors from XPC services to the CLI, enabling efficient streaming of large log files.
- Real-time following uses
AsyncStreamimplementations inContainerLogs.swiftandMachineLogs.swiftto watch file descriptors for new data without polling. - Boot log access is available for both containers and machines via the
--bootflag, which selects the second file handle in the returned array. - System integration leverages macOS's native
logutility for consistent formatting, time-filtering (--last), and predicate-based filtering of subsystem messages.
Frequently Asked Questions
What's the difference between container logs and machine logs?
Container logs (container logs) capture the stdout/stderr of the specific container process, while machine logs (container machine logs) capture output from the virtual machine hosting the containers. According to MachineLogs.swift, the machine logs include lower-level VM operations and boot sequences that occur before individual containers start, whereas container logs focus on application-level output.
How does the --follow flag work for real-time streaming?
When --follow is specified, the code in ContainerLogs.swift switches from a historical tail operation to an AsyncStream that monitors the file descriptor for new data. This approach handles log rotation and container restarts gracefully by watching the underlying file handle rather than buffering the entire output, providing true real-time log streaming similar to tail -f.
Can I retrieve boot logs from a stopped container?
Yes. The container logs --boot command accesses the boot log file handle (fhs[1] in the returned array) which persists independently of the running container process. As implemented in ContainersService.swift, the XPC service opens these log files directly from the filesystem, making them available even if the container has stopped, provided the log files haven't been rotated or deleted.
Why do system logs use a different implementation than container logs?
System logs (container system logs) target the macOS unified logging subsystem rather than container-specific files. As shown in SystemLogs.swift, this command constructs and executes the native log show or log stream commands via Process, leveraging the operating system's built-in log management, filtering, and formatting capabilities rather than implementing custom file parsing logic.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →