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 the container logs subcommand with tail/follow logic and file-handle selection
  • MachineLogs.swift – CLI command for container machine logs that mirrors container-log handling for virtual machines
  • SystemLogs.swift – CLI command for container system logs that builds and executes the OS log utility
  • ContainersService.swift – XPC server method logs(id:) that opens the container's log files and returns file descriptors
  • MachinesService.swift – XPC server method logs(id:) for container-machine log files
  • ContainerClient.swift – Client wrapper that issues the XPC logs request and returns [FileHandle]
  • MachineClient.swift – Client wrapper for machine-log XPC calls

Summary

  • Three distinct commands handle different log sources: container logs for application output, container machine logs for VM-level logs, and container system logs for 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 AsyncStream implementations in ContainerLogs.swift and MachineLogs.swift to watch file descriptors for new data without polling.
  • Boot log access is available for both containers and machines via the --boot flag, which selects the second file handle in the returned array.
  • System integration leverages macOS's native log utility 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →