Engineering note

用 macOS 统一日志定位云端 Mac 构建与签名故障

CI/CD 实践 ·约 7 分钟阅读

用 macOS 统一日志定位云端 Mac 构建与签名故障

凌晨的自动构建突然失败,终端只留下 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 和实际执行脚本的进程。谓词字段应尽量使用 processsubsystemcategoryeventMessage,不要只对整段文本做宽泛匹配。

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 Cloud Mac

按天、周或月租用独享物理 Mac mini

整机独享的物理 Mac mini,远程桌面与 SSH 均可接入;具体机型、区域与租期以订购页当前信息为准。

查看订购选项