凌晨的自動建置突然失敗,終端機只留下 Command PhaseScriptExecution failed,重新執行卻又恢復正常。此時繼續盯著最後一行輸出並沒有意義。雲端 Mac 上的建置工具、簽署服務、網路程序與系統權限機制,都會分別將事件寫入 macOS 統一日誌。把這些事件放到同一條時間軸上,通常就能判斷問題究竟出在專案指令碼、系統服務,還是遠端連線中斷。
先固定故障時間與重現範圍
先記錄節點時間、命令開始時間、失敗時間與結束代碼。遠端用戶端顯示的本機時間可能與節點時區不同,因此排查全程應統一採用節點上的時間。
date -u '+%Y-%m-%dT%H:%M:%SZ'
set -o pipefail
xcodebuild \
-workspace Demo.xcworkspace \
-scheme Demo \
-configuration Release \
build 2>&1 | tee "$HOME/build-output.log"
printf 'exit=%s
' "${PIPESTATUS[0]}"
不要立刻清除 DerivedData、重新啟動程序或覆寫原始日誌。先備份建置輸出,再確認故障能否穩定重現。對於偶發問題,至少要記錄兩次執行結果,並註明兩次使用的提交、Xcode 路徑、Shell 環境與工作目錄是否一致。
統一日誌不能取代終端機輸出。前者用來說明系統與服務發生了什麼,後者則顯示建置命令執行到哪個步驟。兩份記錄必須採用相同的時間範圍。
使用述詞縮小統一日誌範圍
log show 適合回溯已經發生的事件。不要一開始就匯出數小時的完整日誌,這不但會產生大量雜訊,也會提高去識別處理的成本。先將範圍限定在失敗前後各十分鐘,再依程序名稱篩選:
log show \
--last 20m \
--style compact \
--info \
--debug \
--predicate 'process == "xcodebuild" OR process == "codesign"'
如果終端機只顯示簽署失敗,可繼續搜尋安全性服務傳回的拒絕訊息;如果建置過程受到遠端工作階段影響,則應檢查 sshd 與實際執行指令碼的程序。述詞欄位應盡量使用 process、subsystem、category 與 eventMessage,不要只對整段文字進行寬泛比對。
log show --last 15m --style compact \
--predicate '(process == "sshd") OR (eventMessage CONTAINS[c] "denied")'
常見訊號可依下表判讀,但最終結論仍須結合結束代碼與重現步驟。
| 日誌訊號 | 優先檢查 | 不應直接得出的結論 |
|---|---|---|
permission denied |
檔案權限、鑰匙圈存取權、執行帳號 | 節點硬體故障 |
| 程序遭到終止 | 記憶體壓力、父程序、指令碼逾時規則 | Xcode 本身損毀 |
| SSH 工作階段中斷 | 用戶端網路、連線保活參數、工作託管方式 | 建置一定失敗 |
| 簽署服務找不到相符項目 | 鑰匙圈、憑證名稱、描述檔對應關係 | 重新安裝整套工具鏈 |
重現時即時觀察關鍵事件
如果故障可以穩定重現,請另外開啟一個 SSH 工作階段執行 log stream。即時串流只用於觀察,篩選條件不要設得太寬,否則關鍵事件很容易被連續輸出淹沒。
log stream \
--style compact \
--level info \
--predicate 'process == "xcodebuild" OR process == "codesign" OR process == "securityd"'
接著在原本的工作階段啟動建置,並記錄第一筆異常日誌與建置失敗之間的時間差。如果簽署錯誤先出現,之後才由建置工具彙整為一般性失敗,應優先處理簽署鏈路;如果 SSH 先中斷,但建置程序仍在執行,就要檢查工作是否綁定互動式終端機,而不是誤判為編譯錯誤。
為指令碼加入可關聯標記
自有指令碼可以在開始與結束時寫入統一日誌,使用固定的 subsystem,並為每次執行指定唯一的工作編號。如此便不必依賴模糊的檔名搜尋。
job_id="$(date -u '+%Y%m%dT%H%M%SZ')"
logger -p user.notice -t minid-build "job=$job_id phase=start"
./scripts/build.sh
status=$?
logger -p user.notice -t minid-build "job=$job_id phase=end status=$status"
exit "$status"
logger 寫入的標籤可透過 process 或訊息內容搜尋。工作編號僅用於關聯單次執行,不應包含權杖、儲存庫憑證或客戶資料。
匯出可複查且可安全傳遞的證據
命令列文字適合快速分析;遇到複雜問題時,則應保留 .logarchive。封存前請再次確認時間範圍,避免收集與故障無關的歷史記錄。
mkdir -p "$HOME/diagnostics"
sudo log collect \
--last 15m \
--output "$HOME/diagnostics/build-incident.logarchive"
cp "$HOME/build-output.log" "$HOME/diagnostics/"
不要直接將整個目錄傳送出去。先檢查建置日誌中是否包含展開後的環境變數、專案絕對路徑、節點位址、儲存庫位址、憑證名稱與指令碼參數。統一替換敏感值時,應保留穩定的對應關係,例如同一個使用者名稱一律替換為 <USER>,否則後續將無法判斷多筆事件是否屬於同一個帳號。
建議同時準備一份純文字說明,內容包含 UTC 時間、執行命令、結束代碼、預期結果、實際結果、能否重現,以及故障前最後一次成功執行所使用的提交。日誌用來證明發生了什麼,說明文件則用來界定調查範圍。
根據證據決定下一步行動
完成資料收集後,先將問題歸類為專案、環境、連線或資源四種類型。專案類問題可在相同提交下改用乾淨的工作目錄驗證;環境類問題應核對 Xcode 選擇、Shell 初始化與鑰匙圈可見性;連線類問題要區分工作階段中斷與工作本身結束;資源類問題則應檢查磁碟剩餘空間、記憶體壓力與並行程序。
排查結束前,請執行一次最小驗收:針對相同提交連續建置兩次,確認結束代碼均為零;簽署產物可透過 codesign --verify --deep --strict 驗證;背景工作在 SSH 中斷後仍能按預期完成;診斷目錄中不存在明文權杖。只有這些條件同時成立,故障才算真正完成閉環。
常見問題
為什麼不能只保存 xcodebuild 的終端機輸出?
終端機輸出主要涵蓋建置步驟,權限拒絕、簽署服務、網路程序與系統元件異常可能只會進入統一日誌,兩者應依相同時間點一併保存。
提交日誌前必須移除哪些內容?
至少檢查使用者名稱、專案絕對路徑、節點位址、儲存庫位址、權杖、憑證名稱與業務資料,無法確認安全的欄位應先替換。
MiniD 雲端 Mac
按日、週或月租用獨享實體 Mac mini
整機獨享的實體 Mac mini,可透過遠端桌面與 SSH 連線;實際機型、區域與租期以訂購頁目前資訊為準。