1. OpenClaw 在 macOS 上日志散乱到底难在哪
OpenClaw 在 macOS 上跑起来之后,日志这件事很容易被忽略,直到某天 WebChat 消息发不出去、Agent 任务卡住、或者某个子系统静默失败,你打开 Console.app 翻了半天,发现全是系统进程的噪音,自己应用的日志要么找不到,要么只有一行connection failed没有任何上下文。这就是 macOS 日志系统落地没做好的典型症状:日志分级混乱、输出路径不固定、排查时不知道该看哪里。
OpenClaw 本身通过 swift-log 来路由 macOS 应用日志,默认走的是统一日志(Unified Logging),也就是你可以在 Console.app 里按 subsystem 过滤看到的那套。swift-log 是 Swift 生态里的日志抽象层,它定义了一套LoggerAPI,底层可以接 os_log、文件、stdout 等不同后端。问题在于,默认配置下日志级别是写死的,输出路径也不受你控制,调试时想临时打开 verbose 还得改代码重新编译,这在排查线上问题时非常低效。
我试过在 OpenClaw 里追一个 WebChat 消息丢失的问题,统一日志里只能看到message enqueued和message dropped,中间发生了什么完全黑盒。后来才发现,OpenClaw 其实支持把本地轮转文件日志写到磁盘,路径是~/Library/Logs/OpenClaw/diagnostics.jsonl,但默认关闭,需要在 Debug 面板里手动开。这个文件是 JSONL 格式,自动轮转,旧文件后缀是.1、.2这样,适合做持久化排查。
但光有文件日志还不够。macOS 的统一日志默认会编辑掉大部分负载内容,比如字符串会被替换成<private>,这是系统级的隐私保护机制。对于 OpenClaw 这种需要看消息正文、电话号码、URL 参数的应用来说,这等于把最有用的信息藏起来了。解决办法是通过 subsystem 级别的 plist 覆盖,给ai.openclaw这个子系统开启Enable-Private-Data,让新的日志条目包含完整负载。
所以这一篇要解决的核心问题是:怎么用 swift-log 把 OpenClaw 的日志分级统一起来,怎么用 plist 管理日志级别和输出路径,怎么验证配置生效,以及排查时常见的坑在哪。适合正在 macOS 上开发或调试 OpenClaw 的人,也适合任何用 swift-log 做 macOS 应用日志的开发者参考。
2. TaoToken 前置:给 OpenClaw 接上模型能力
OpenClaw 本身是个 Agent 框架,它的日志系统解决的是「看得见」的问题,但要让 Agent 真正跑起来,还得接上模型。这里我用 TaoToken 来做模型接入,原因是它的 API 兼容 OpenAI 格式,OpenClaw 的模型配置里直接填 Base URL 和 Key 就能用,不需要额外写适配层。
TaoToken 的 API 地址是https://taotoken.net/api,注意这个地址不带任何查询参数,直接作为 Base URL 填进去。Key 的话去控制台生成,地址是https://taotoken.net/console,生成之后复制出来,格式一般是sk-开头的一串。模型 ID 根据你用的模型填,比如claude-sonnet-4-20250514或者gpt-4o这类,具体以控制台里列出的为准。
如果你用的是 Claude Code 或者类似的 coding agent,TaoToken 也支持 Anthropic 风格的接口,Base URL 同样是https://taotoken.net/api,Key 和 Model ID 的填法一致。OpenClaw 的配置文件里一般会有model、base_url、api_key三个字段,对应填进去就行。
这里要强调一点:OpenClaw 的日志系统配置和模型接入是两件独立的事,但排查问题时经常需要一起看。比如模型请求失败,日志里会记录 HTTP 状态码和响应体,如果统一日志把响应体编辑掉了,你就只能看到401或者429,不知道具体是 Key 过期还是额度用完。所以把日志的隐私负载打开,对排查模型接入问题很有帮助。
TaoToken 的接入文档在https://taotoken.net/doc,里面有各个客户端的配置示例,包括 Cline、Codex、Claude Code 这些。如果你用的是 Cline 的 MCP 模式,配置里需要写全三件套:Base URL、API Key、Model ID,缺一个都会报local proxy failed或者reading choices之类的错。Codex 的话看auth.json的配置方式,也是同样的三件套逻辑。
配置好之后,OpenClaw 启动时会先初始化日志系统,然后加载模型配置。如果日志级别设成了debug,你能看到模型请求的完整 URL、请求头、响应状态;如果设成info,就只看到关键节点。这个分级控制就是接下来要讲的 swift-log 初始化部分。
3. 可复制配置:swift-log 初始化与 plist 键值骨架
3.1 swift-log 初始化代码
OpenClaw 的日志系统基于 swift-log,核心是创建一个Logger实例并绑定后端。下面这段代码可以直接复制到你的 OpenClaw 项目里,放在应用启动的入口处,比如AppDelegate的applicationDidFinishLaunching或者 SwiftUI 的App.init里。
import Logging import OSLog // 1. 创建统一日志后端,绑定到 ai.openclaw 子系统 let osLogHandler = OSLogHandler( subsystem: "ai.openclaw", category: "general" ) // 2. 创建文件日志后端,写入 ~/Library/Logs/OpenClaw/diagnostics.jsonl let fileLogHandler = try FileLogHandler( logFileURL: FileManager.default .homeDirectoryForCurrentUser .appendingPathComponent("Library/Logs/OpenClaw/diagnostics.jsonl"), rollingPolicy: .bySize(maxSize: 10 * 1024 * 1024, maxFiles: 5) ) // 3. 用 MultiplexLogHandler 把两个后端组合起来 var multiplex = MultiplexLogHandler([osLogHandler, fileLogHandler]) // 4. 根据 plist 配置设置日志级别 let logLevel = LogLevel.fromPlist() ?? .info multiplex.logLevel = logLevel // 5. 注册为全局 Logger LoggingSystem.bootstrap { label in var logger = Logger(label: label, factory: { multiplex }) logger.logLevel = logLevel return logger }这段代码的关键点在于MultiplexLogHandler,它让同一条日志同时走统一日志和文件两个后端。统一日志方便在 Console.app 里实时看,文件日志方便做持久化排查和轮转。rollingPolicy设成按大小轮转,10MB 一个文件,最多保留 5 个,旧文件自动加.1、.2后缀。
LogLevel.fromPlist()是个自定义扩展,从 plist 里读日志级别。下面给出 plist 的键值骨架。
3.2 plist 键值骨架
OpenClaw 的日志级别和输出路径通过 plist 管理,这样不用改代码就能调整。plist 放在应用 bundle 里或者~/Library/Preferences/下,键值结构如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>LogLevel</key> <string>debug</string> <key>LogFilePath</key> <string>~/Library/Logs/OpenClaw/diagnostics.jsonl</string> <key>EnableFileLogging</key> <true/> <key>EnablePrivateData</key> <true/> <key>RollingMaxSizeMB</key> <integer>10</integer> <key>RollingMaxFiles</key> <integer>5</integer> </dict> </plist>LogLevel可选trace、debug、info、notice、warning、error、critical,对应 swift-log 的Logger.Level。EnableFileLogging控制是否写文件,默认建议关,只在主动调试时开。EnablePrivateData对应统一日志的隐私负载开关,下面单独讲。
3.3 统一日志隐私负载的 plist 覆盖
macOS 统一日志默认把字符串负载编辑成<private>,要看到完整内容,需要在/Library/Preferences/Logging/Subsystems/下放一个以子系统名命名的 plist。OpenClaw 的子系统是ai.openclaw,所以文件名是ai.openclaw.plist。
写入方式是用临时文件加install原子安装:
cat <<'EOF' >/tmp/ai.openclaw.plist <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>DEFAULT-OPTIONS</key> <dict> <key>Enable-Private-Data</key> <true/> </dict> </dict> </plist> EOF sudo install -m 644 -o root -g wheel /tmp/ai.openclaw.plist /Library/Preferences/Logging/Subsystems/ai.openclaw.plist这个操作不需要重启,logd会很快注意到新文件,但只有新的日志行才会包含隐私负载,已经写进去的旧日志不会变。所以要在重现问题之前启用它。
调试完之后记得删掉:
sudo rm /Library/Preferences/Logging/Subsystems/ai.openclaw.plist sudo log config --reloadlog config --reload强制logd立即丢弃覆盖,不然可能还会有一段时间的缓存生效。
3.4 文件日志的开关与清除
OpenClaw 的 Debug 面板里有几个开关,对应文件日志的控制:
Debug 面板 → Logs → App logging → Verbosity:设置日志级别,对应 plist 里的LogLevel。Debug 面板 → Logs → App logging → Write rolling diagnostics log (JSONL):开启文件日志,对应EnableFileLogging。Debug 面板 → Logs → App logging → Clear:清除现有日志文件。
文件日志默认关闭,因为 JSONL 里可能包含消息正文、电话号码、URL 参数这些敏感信息。开启之后,文件写在~/Library/Logs/OpenClaw/diagnostics.jsonl,自动轮转,旧文件后缀.1、.2这样。分享日志之前一定要先审查,不要直接把原始文件发出去。
4. 验证请求:启动后看日志落盘与级别过滤
配置写完,接下来要验证两件事:日志有没有正确落盘,级别过滤有没有生效。
4.1 验证文件日志落盘
启动 OpenClaw,触发一个会产生日志的操作,比如发一条 WebChat 消息。然后打开终端,看文件是否存在:
ls -la ~/Library/Logs/OpenClaw/正常的话你会看到diagnostics.jsonl,以及可能的轮转文件diagnostics.jsonl.1。用tail看最后几行:
tail -n 20 ~/Library/Logs/OpenClaw/diagnostics.jsonl每行是一个 JSON 对象,包含timestamp、level、subsystem、category、message这些字段。如果message里能看到完整的消息内容而不是<private>,说明隐私负载开关生效了。
4.2 验证级别过滤
把 plist 里的LogLevel改成info,重启 OpenClaw,再触发同样的操作。这时候debug级别的日志应该不再写入文件。你可以用grep过滤验证:
grep '"level":"debug"' ~/Library/Logs/OpenClaw/diagnostics.jsonl | tail -n 5如果返回空,说明级别过滤生效了。再把LogLevel改回debug,重启,同样的grep应该能返回结果。
4.3 用 clawlog.sh 查看统一日志
OpenClaw 仓库里有个辅助脚本scripts/clawlog.sh,封装了log show命令,方便按 category 和时间范围过滤。用法示例:
./scripts/clawlog.sh --category WebChat --last 5m这会显示最近 5 分钟内WebChat这个 category 的日志。如果隐私负载开关生效,你能看到完整的消息内容;否则只能看到<private>。
4.4 验证模型请求日志
如果你用 TaoToken 接入了模型,触发一次模型请求,然后在日志里找 HTTP 相关的记录。debug级别下应该能看到请求 URL、状态码、响应时间。如果看到401,去https://taotoken.net/console检查 Key 是否有效;如果看到429,说明额度或频率受限。这些信息在info级别下可能被省略,所以排查模型问题时建议临时开到debug。
5. 本篇常见错排查
5.1 401 报错:Key 无效或未填
日志里出现401 Unauthorized,最常见的原因是 API Key 没填、填错、或者过期。检查 OpenClaw 配置里的api_key字段,确认是sk-开头的完整字符串。如果用的是 TaoToken,去https://taotoken.net/console重新生成一个 Key,替换后重启。注意 Key 不要有多余空格或换行。
5.2 local proxy failed:Base URL 或 Model ID 缺失
这个报错通常出现在 Cline MCP 或类似客户端里,原因是三件套没写全。Base URL 必须是https://taotoken.net/api,Model ID 必须是控制台里列出的有效值,API Key 必须有效。缺任何一个都会导致代理层初始化失败。检查配置文件里的base_url、api_key、model三个字段,确保都填了。
5.3 reading choices 报错:响应格式不匹配
reading choices是解析模型响应时找不到choices字段。可能的原因是 Base URL 填成了不带/api的地址,或者模型 ID 写错了导致返回了错误格式。确认 Base URL 是https://taotoken.net/api,Model ID 和控制台一致。如果用的是 Anthropic 风格接口,检查客户端是否配置了正确的 API 类型。
5.4 OAuth 相关报错:认证方式冲突
有些客户端默认走 OAuth 认证,但 TaoToken 用的是 API Key 认证。如果日志里出现 OAuth 相关的错误,检查客户端配置里是否误开了 OAuth 模式。Codex 的话看auth.json里是不是混了 OAuth token 和 API Key,应该只用 API Key。Claude Code 的话检查settings.json里的认证配置。
5.5 日志文件不生成:EnableFileLogging 未开
如果~/Library/Logs/OpenClaw/diagnostics.jsonl不存在,先检查 plist 里的EnableFileLogging是不是true,或者 Debug 面板里的开关有没有打开。另外确认~/Library/Logs/OpenClaw/目录存在,不存在的话手动创建:
mkdir -p ~/Library/Logs/OpenClaw/权限问题也可能导致写入失败,检查目录的 owner 是不是当前用户。
5.6 统一日志看不到隐私负载:plist 未生效
如果clawlog.sh输出里还是<private>,检查/Library/Preferences/Logging/Subsystems/ai.openclaw.plist是否存在,权限是不是644、owner 是不是root:wheel。另外注意只有新的日志行才会包含隐私负载,旧的不变。如果还是不行,运行sudo log config --reload强制重载。
5.7 日志级别不生效:plist 读取顺序问题
如果改了 plist 里的LogLevel但日志级别没变,检查 plist 的读取顺序。应用 bundle 里的 plist 和~/Library/Preferences/下的 plist 可能冲突,后者优先级更高。确认你改的是实际生效的那个。另外LoggingSystem.bootstrap只能调用一次,如果在别处已经 bootstrap 过,后面的配置不会生效。
6. 把日志和模型接入串起来
日志系统配好之后,排查模型接入问题会顺畅很多。我的习惯是:平时LogLevel设成info,文件日志关着,减少噪音和磁盘占用;一旦要排查问题,临时把LogLevel改成debug,打开文件日志和隐私负载,重现问题,看diagnostics.jsonl里的完整记录,定位完之后再关掉。
TaoToken 的接入配置建议放在环境变量或者独立的配置文件里,不要硬编码在代码里。Base URL 用https://taotoken.net/api,Key 从https://taotoken.net/console生成,Model ID 按需选。如果你长期跑 coding agent 或者 Agent 任务,可以考虑 Coding Plan,地址是https://taotoken.net/coding-plan,适合需要稳定额度的场景。只是想验证模型对话的话,用模型对话页面就行:https://taotoken.net/chat。
最后提醒一句:diagnostics.jsonl里可能包含消息正文和电话号码,分享之前一定要审查。调试完记得删掉/Library/Preferences/Logging/Subsystems/ai.openclaw.plist,运行sudo log config --reload,把隐私负载开关关回去。日志系统是为了让问题可见,不是为了长期暴露敏感数据。