mcpField
S
super-log

super-log

本地服务
github.com/saxonnicholls开发工具14 周前更新

关于 super-log

super-log 是可自托管的日志与事件流汇聚中心,可收集应用、设备、机器和服务的数据。开发者可通过命令行、查看器和只读 MCP 工具检查并查询可回放的统一流。

主要功能

  1. 01收集多种语言和来源的事件流
  2. 02按中心序号交错排列事件
  3. 03筛选、暂停和导出日志视图
  4. 04搜索并回放已收集事件
  5. 05为 Agent 提供只读 MCP 工具
  6. 06通过命令行查看状态并转发数据流

适用场景

  1. 01关联不同设备和服务的日志
  2. 02检查构建或应用故障
  3. 03让 AI Agent 查询近期事件
  4. 04回放数据流以调查事件顺序

MCP 相关

通过中心向 Agent 提供只读工具,用于查询本地 super-log 数据。MCP 工具是该中心提供的读取接口之一。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"com-super-log-super-log": {
"command": "npx",
"args": [
"-y",
"@super-log/mcp"
],
"env": {
"SUPER_LOG_URL": "<SUPER_LOG_URL>"
}
}
}
}
启动命令npx -y @super-log/mcp
需要配置的环境变量
SUPER_LOG_URLBase URL of the super-log hub (default http://127.0.0.1:7333)

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npm@super-log/mcp0.4.1

工具

9

收录的工具列表 · 更新于 2 周前

此工具无需参数。

详细介绍

1788338555134# super-log

ci licence: MIT release

MCP Toplist

所有日志流的统一中心——设备、服务、容器、浏览器、链和应用。

这个项目整合了我过去十五年来以各种形式使用过的一系列零散工具——日志合并器、端口监视器、构建封装器、临时代理,以及每个长期运行的工作台都会积累的一次性脚本——并将它们重建为一个统一的系统,使用一种通信协议,呈现在一个界面上。

永久免费,并且可自托管。它负责收集和整合;分析则是一个独立、更纯粹的任务——将整合后的数据流交给 super-log.com,进行实时大模型分析并使用团队功能,也可以交给你自己的存储系统。请参阅收集不是分析——这正是整个设计的核心。

十二路数据流交错呈现在一个界面上

十二个数据源在一个界面上按到达顺序交错显示:通过 SN_LOG 和 spdlog 输出的 C++、Rust、Go、Python、Swift、Fortran、一个 POSIX shell 脚本、两台 React Native 设备、报告真实带宽的 Metal GPU 任务,以及实时 Binance WebSocket。其中最后一项每秒产生数百帧,因此会进行速率限制,并明确告知你这一点,而不是悄悄丢弃数据。

如果你需要在多台设备上进行开发,就知道这种例行操作:一个 Metro 控制台用于 iOS 模拟器,另一个用于 Android 模拟器,桌上的手机要用 adb logcat,C++ 引擎要开一个终端,Rust 服务再开一个,Web 构建还要用浏览器开发者工具,云端机器则需要一个 ssh 会话。要查看的地方有六处,时间戳却彼此不一致,而 Bug 总是出现在这些数据交错之处。

super-log 将所有内容汇集到一个进程和一个界面中:

 apps, 14+ languages ┐
 GPU and graphics    │
 devices and boards  │                                     ┌─▶ native viewer (ImGui)
 machines, services  ├── POST NDJSON ──▶  superlogd :7333 ─┼─▶ web viewer (React)
 network and DNS     │                    (fan-out+replay) ├─▶ journal → search / replay
 builds and repos    │                                     ├─▶ GET /recent  (scripts)
 blockchain          │                                     ├─▶ MCP tools    (agents)
 anything that prints┘                                     └─▶ alerts → webhook
分组包含内容
appsC++(spdlog sink 和原生SN_LOG)、纯 C(一个头文件)、C# / .NET(仅 BCL — 控制台、ASP.NET、Unity 配置文件、Xbox Dev Mode)、Rust(tracing)、Python(logging)、Go(log/slog)、Java、Kotlin 和 Scala(可直接替换的 SLF4J 后端 — 替换日志后端,无需更改代码 — 另有 Log4j 2 和 Logback appenders,以及java.util.logging)、Swift、Fortran、OCaml、Haskell、Lean 4、Ruby 和 Rails(可直接替换的::Logger)、Perl(仅核心模块)、Lua(5.1+,PUC 或 LuaJIT)、Zig(对 C 头文件使用@cImport)、COBOL(通过 C SDK 接入,没错,确实如此)、POSIX sh,以及适用于 Node、浏览器和 React Native 的 JS — 还会捕获console、fetch 和 WebGL。任何其他支持 C FFI 的语言只需链接一个对象:cc -DSUPERLOG_API= -c impl.c
OpenTelemetry已完成 OTel 插桩?无需新增 SDK即可加入基准环境 — 将任意 exporter 指向127.0.0.1:4318(OTEL_EXPORTER_OTLP_ENDPOINT,标准端口,零配置)。通过 OTLP/JSON 和 OTLP/protobuf 传输日志、指标和追踪;severityNumber映射为级别,每个service.name都会成为一个otlp.<service>主题,而 OTel span 的trace_id会成为基准环境的trace — 一次/recent?trace=即可将 OTel spans 和基准环境自己的日志行作为一个完整故事返回。反向也支持:superlog-otlp-export可将基准环境的数据发送到任意 OTLP 后端或收集器(OTLP/JSON 日志 + gauge 指标),因此基准环境是双向 OTLP 成员,而非封闭花园
GPU 和图形Metal 和 CUDA 内核计时、WebGL 上下文丢失和着色器故障,以及通过nvidia-smi、rocm-smi 或ioreg获取显卡本身的信息
设备和开发板通过 USB 连接的 iOS 和 Android、读取 ESP-IDF、Zephyr 和括号格式的串口控制台、ROS 2/rosout,以及通过 MAVLink 连接的无人机(ArduPilot/PX4 — 电池、GPS 定位、模式/解锁、故障保护,以及飞控自身的 STATUSTEXT,可从 UDP 流或.tlog获取)
机器和服务macOS、Linux 和 Windows 上的 OS 日志;约 20 种已知服务(postgres、nginx、redis、kafka…);Unity 和 Unreal Engine 编辑器日志(按级别解析;Blender 和 AutoCAD 可通过配方解析);Docker 容器;通过 ssh 连接的任意远程主机;功耗、热量和能耗最高的进程(macOS);崩溃报告、内核崩溃、关机原因、卷和睡眠/唤醒事件(macOS);精确到变更行的文件系统改动,实时比较差异;大型下载 — 逐个分片获取 Hugging Face 模型 — 并提供停滞告警;完整桥接其他基准环境;以及基准环境下的每个版本 — OS、编译器、运行时、数据库和包管理器所知的每个包 — 均进行差异比较,终于能回答“昨天还好好的”
网络和 DNSHTTP/S 日志代理、WebSocket 帧、syslog 和原始 TCP/UDP 入口、带 TLS 到期时间的 DNS 记录、附带进程的监听端口 — 以及持续监控网络自身状态的变化:网关、Wi-Fi、VPN、解析器、ARP(网关 MAC 发生变化即为 ERROR — MITM 就是这样开始的)、带 RTT/丢包读数的 ping 目标,以及附加到每个降级告警中的 traceroute,还有对域名前缀的源 AS 监控(从外部观察 BGP 劫持的样子);以树形结构呈现 LAN,并持续监控任意路由,以及此计算机持有的出站连接 — 卡在 SYN-SENT 的套接字,就是让开发者白白耗上一下午的被过滤/关闭端口
构建和仓库cmake、clang、gcc、rustc、swiftc、npm、xcodebuild、Vivado 和 Quartus — 还会完整捕获 sanitizer 和 valgrind 发现的问题、本地 git 以及 GitHub Actions
区块链监控任意 EVM 链上的地址,解码转账并按合约读取代币小数位;监控运营密钥余额,并在资金需补充时触发边沿告警 — EVM(gas 或 ERC-20)、Solana(SOL 或 SPL)、Tron(TRX 或 TRC-20,包括 USDT)以及Bitcoin(任意 Esplora API,包括自有节点)
交易和 FIXFIX 会话日志(QuickFIX、FIX8 或任何会写入标准消息日志的引擎):从以 SOH 分隔的tag=value线路格式解码每条消息,并按级别分类,让静默故障也能大声告警 — Reject 或被拒绝的成交为 ERROR,并附上原因;Logout 或序列缺口为 WARN(会话在交易日中途停止成交);Heartbeat 为 DEBUG — ClOrdID、Symbol、Side、OrderQty、Price 和 OrdStatus 均作为字段附带;每个会话使用一个fix.<begin>-<sender>-<target>主题,主题从消息本身推导,绝不从文件名推导
任何会输出内容的程序your-command 2>&1 | superlog tee — 可直接替换的tee
告警和 Webhook基于基准环境设置规则(级别、速率、静默、组合),并为生产环境提供经隧道转发的公开 webhook — 按键去重、重复计数、心跳失效监测 — 告警会进入两个查看器的稀疏告警记录表,通过一个通道注册表投递(桌面应用、webhook;Telegram/Twilio/email 需配置启用);公开端点可每次点击配置一个,也可通过清单文件批量配置,每个端点都有自己的 ping 时钟和健康指示灯,每条路由都可通过测试按钮进行往返测试,实时 URL 写入endpoints.env;内置webhook 测试 — 将 Stripe/GitHub 投递捕获为wh.*事件,在接收时验证 Stripe 签名,或将每次投递中继到本地处理程序并返回其真实响应(stripe listen,并附带记录)
每个生产者都使用一种小型线缆协议
(docs/PROTOCOL.md:每行一个 JSON 事件,通过
普通 HTTP POST 批量发送),集线器会向任意数量的读取器分发数据,并在连接时
重放历史记录;所有内容都按集线器序列交错排列,而不是按会漂移的
设备时钟排列。

快速开始

macOS — Homebrew:

brew install saxonnicholls/tap/super-log && brew services start super-log

Ubuntu(包括 Pi 上的 Ubuntu Server)— 使用 PPA,支持所有架构,并自动更新:

sudo add-apt-repository ppa:super-log/stable && sudo apt update && sudo apt install super-log

Debian / Raspberry Pi OS — 直接使用 .deb(PPA 仅适用于 Ubuntu;这里不要 使用 add-apt-repository)。选择你的架构:

curl -fsSL -O https://github.com/saxonnicholls/super-log/releases/download/v0.4.0/super-log_0.4.0_arm64.deb   # or _amd64.deb
sudo apt install -y ./super-log_0.4.0_arm64.deb

然后用两行命令看看效果 — 集线器已经在 :7333 上运行:

echo "the bench is live" | superlog tee --topic hello
curl -s "http://127.0.0.1:7333/recent?topic=hello"

Fedora/RHEL、npm、vcpkg、从源代码构建,以及 Web 和原生查看器,均见下文的 安装。

superlog CLI

一条命令即可读取工作台、管理尾随器,并将数据流传输到集线器:

superlog status            what's running, and the hub's health
superlog alarms            firing alarms   (every read command is NDJSON when piped — | jq)
superlog versions          the version inventory, per host
superlog start vitals      start a tailer in the background
superlog stop vitals       stop it
superlog viewer            open the native viewer  (builds it once if needed)
superlog tee               a stream onto the hub   (make 2>&1 | superlog tee --topic build)
superlog git install-hooks stamp every commit onto the bench
superlog git recall <sha>  replay the logs the bench saw behind a commit
superlog login             open super-log Cloud in your browser

完整参考文档:docs/CLI.md。或运行 superlog help。

它能为你做什么

一个屏幕,一览所有内容。 数据流按来源和级别以不同颜色显示, 可按数据流、最低级别或子字符串筛选。暂停会冻结显示 但采集仍会继续;可复制一行或整个筛选视图;导出为 JSON、CSV 或纯文本。

五种语言同时出现的同一个故障

最低级别设为 ERROR:1131 行中有 29 行。同一个定价 故障同时出现在 Swift、Python、Go 和 shell 脚本中 — 每种语言都有自己的表达方式:PricingError、KeyError、返回的 错误、shell 测试 — 此外,GPU 还拒绝了一次大小为显存四倍的分配。 Python 的那一行带有故障帧中的局部变量;否则你得添加一条打印语句, 才能找到这些变量。

你的应用几乎不需要做什么。 十四种零依赖 SDK:仅含头文件的 C++(同时提供 spdlog 接收器和原生的 snicholls::log 接收器),纯 C 单个 stb 风格头文件 — 零分配,并且可证明生产构建中完全不包含日志记录 — 带可选 tracing 层的 Rust crate,接入标准库 logging 的 Python,带 log/slog 处理器的 Go,带 java.util.logging 桥接的 Java(Kotlin 和 Scala 也可直接使用,只需一次导入,无需胶水代码), Swift,通过原始 POSIX 套接字工作的 Fortran,同样通过原始套接字工作的 OCaml,只需要 GHC 引导库和 curl 的 Haskell,可用于整夜运行的证明任务的 Lean 4 (核心 IO 加 curl — Lean 先有了内核,后来才有套接字),仅使用核心模块的 Perl (HTTP::Tiny 从 Perl 5.14 起就随 Perl 一同发布),与 shell SDK 一样选择依赖 curl 的 Lua (Lua 从未支持过套接字),仅使用 BCL 的 C# — 适用于 .NET 所到之处,包括 Unity 配置文件和 Xbox Dev Mode — 通过 @cImport 直接使用 C 头文件的 Zig,通过一个 20 行 C 垫片接入 仅含头文件 C SDK 的 COBOL,仅使用标准库的 Ruby — 附带一个 可直接替换的 ::Logger 适配器,让整个 Rails 集成只需设置一次 config.logger — 适用于脚本的一行 sh 命令,以及一个可用于 React Native、浏览器和 Node 的 JS 客户端 — 设置 patchConsole: true,每条 console.log 都会出现在工作台上。

其中六种 SDK 可接入语言现有的日志框架 — spdlog 接收器、 logging.Handler、slog.Handler、java.util.logging.Handler、 Ruby 的 ::Logger 适配器和 patchConsole — 因此,程序已经记录的所有内容 都会到达工作台,无需更改任何调用位置。

一个 tick 展开为四个事件

这就是交错排列的意义所在。中间,一个 Swift tick 按顺序展开 — INFO 级别的 tick、DEBUG 级别的定价过程、由其异常引发的 ERROR,以及随后出现的 WARN — 与此同时,另外十一个生产者仍在周围持续写入。从不同终端中重建这段序列, 正是这套工具要取代的繁琐流程。 跨越每一层追踪一次操作。 一次点击会在四条流中变成请求、数据库写入和链上调用。withTrace() 会生成一个关联 ID,在各次 await 之间传递,并自动将其附加到出站 HTTP 请求;服务器会接管该 ID,并使用同一个 ID 记录日志。之后只需一个查询——查看器中的一个 ⇢、GET /recent?trace=…,或一个 Agent 工具——就能按顺序返回完整过程。

捕获每个错误,包括从未有人记录的错误。 默认情况下,每个 SDK 都会捕获未捕获的异常和未处理的拒绝,并保留完整堆栈——不会截断到抛出位置,因为深层 React Native 渲染错误会经过桥接层,产生 100 多帧,而你真正需要粘贴的正是那条路径。捕获机制会链接到先前安装的处理程序,因此 React Native 仍会显示红色错误框,Node 仍会以状态码 1 退出,C++ 仍会中止。C++ 堆栈会进行反修饰(pricer::Engine::quote(int)),且不依赖 boost。对于最棘手的情况——某个库抛出异常,而组件边界捕获并显示该异常,使其无法到达全局处理程序——请用 SuperLogErrorBoundary 包装组件树(或将你自己的边界的 componentDidCatch 转发给 log.exception):这样也会记录组件堆栈,这是 JS 堆栈永远不会包含的唯一信息——它会指出抛出异常的组件。此外,还可以选择为每次 Error 构造添加面包屑记录。

无需修改应用的备用方案。 主机端的日志跟踪器会抓取现有日志:adb logcat(仅限一个应用,因为 OEM 手机每秒会输出约 600 行)、iOS 模拟器的日志流、macOS 统一日志、journald、Docker 容器以及任意日志文件。目录会记录 macOS 和 Linux 上约 20 种常见服务的日志位置——postgres、mysql、mongodb、redis、nginx、apache、kafka、elasticsearch、rocksdb——包括两个 Homebrew 前缀。目录还涵盖引擎和内容工具:Unity 的 Editor.log(C# 编译器的 error CS1234 会被归类为 ERROR,抛出的异常也一样,而名为“Exceptions”的文件夹仍会归类为 INFO),以及 Unreal Engine 按项目区分的编辑器日志(LogNet: Warning: 会根据 Unreal 自身的详细级别词汇进行映射,并保留类别)。Blender 会将日志写入 stdout——这正是 tee 的用途;AutoCAD 的 LOGFILEMODE 文件也可以像其他文件一样跟踪。

通过 ssh 拉取整支服务器群。 一个配置文件即可启动每台服务器上的所有流:操作系统日志、服务日志、容器日志。远程无需安装任何内容,也无需开放端口,而且服务器无需连接到中枢——因此中枢可以保持仅绑定到 loopback,同时监控生产环境。

HTTP 调用,包括请求和响应。 在服务前端接入日志代理,每次调用都会成为一个事件(方法、路径、状态、延迟、大小);或者在应用中启用 patchNetwork,查看应用发出的调用。HTTPS 目标无需处理证书。请求体需显式启用;凭据始终会被隐藏。

区块链地址,与触及这些地址的代码并列显示。 监控任意 EVM 地址,其转账、合约事件和原生代币余额变动都会显示在同一屏幕上,按中枢的顺序排列,并与发起这些操作的应用代码并列显示。

绝不会悄然耗尽的运维密钥。 Keeper 或 oracle 的 gas 耗尽会像服务器崩溃一样导致生产系统停止,而且故障会悄无声息——它只会停止运行,没人知道原因。superlog-gas 会轮询你指定的余额(原生代币或 ERC-20,按链、按密钥配置,并存放在 gitignore 忽略的配置文件中——带标签的密钥列表就像给攻击者绘制的地图),并遵循工作台的规范:读取结果会作为 metric 事件显示在图表上;低于需要立即充值的阈值时,只发出一次 CRITICAL 告警;低于低水位线时只发出一次 WARN;充值后也会发出通知。每条链每次轮询只发送一个 JSON-RPC 批处理请求,因为公共 RPC 的速率限制也是设计需要考虑的一部分。 只有发生变化时才会发声的基础设施。 DNS 记录和 TLS 过期时间、监听端口及其所属进程——通过比较快照持续监控,因此只有重要事件发生时,数据流才会发声:你没有更改过的 NS 记录、三周后就要过期的证书、生产服务器上新出现的公网监听端口、悄无声息重启的服务。

将构建作为事件,而不是一堵文本墙。 包装任意构建流程——cmake、clang、gcc、cargo、npm、xcodebuild,无论本地还是通过 ssh——编译器诊断都会变成带有 file:line 的 WARN/ERROR 行,并附带一条汇总事件,记录退出状态、耗时和数量。包括链接阶段:GNU ld 的 undefined reference 和 multiple definition 不带严重级别字样,否则会作为 INFO 一闪而过;它们、Apple 的 Undefined symbols 和 duplicate symbol、lld、collect2,以及驱动程序自己的 clang: error: linker command failed,都会作为 ERROR 记录。调试器也走同一套流程:将批处理会话通过 tee 管道传输—— gdb --batch -ex run -ex bt ./app 2>&1 | superlog tee --topic dbg.app --classify (lldb 也一样)——或者像其他日志文件一样,跟踪 gdb 的 set logging 文件。

监控大型下载。 从 Hugging Face 下载一个 70B 模型需要 15 个分片,耗时半天;进度条通过 \r 重写,只存在于启动下载的终端中——而 tqdm、curl 和 wget 一旦将输出写入管道,就都会静默或改写这些进度条,因此 tee 什么也看不到。用 superlog-dl 包装下载任务,百分比、字节数和速率就会变成指标事件;或者将 --watch 指向目标目录,在文件系统层面测量进度。任何工具都无法将其静音;当每个分片都会重置自己的进度条时,这也是唯一真实的汇总方式。最重要的事件是停滞:连续 30 秒没有收到字节,就会在工作台上触发 WARN——比下载任务自己的耐心耗尽早数小时;后者要等到 140GB 已下载 97% 才会放弃。

功耗、温度,以及功耗来自何处。 在 macOS 上,每次工作台运行都会采样 CPU 封装功耗、芯片温度、风扇 RPM、汇总为单个数值的 CPU 使用情况,以及能耗最高的进程——因为失控进程会让风扇发出警报,远早于图表发现问题(有关由此引发的事件,请见下方的 power 行)。

机器自身的生命周期事件。 诊断报告会解析 DiagnosticReports 中的崩溃报告和内核崩溃信息——进程、异常、信号——并在它们出现时立即记录(启动时也会检查近期记录,因为崩溃会先写入报告,然后才重启并重新启动监控器);每次启动都会记录一次上次关机原因,并转换为可读信息(cause -128: uncontrolled power loss),如果关机不正常则标记为 ERROR;还会记录卷挂载、卸载和重命名——重命名会更改卷上所有路径,这就是长时间写入可能中断、却没有任何记录说明原因的方式;以及休眠和唤醒事件,它们能解释其他所有数据流中的每个空档。这个工作台曾崩溃过四次,相关证据却都未被查看;首次实时运行就发现了一次异常关机,以及此前三天内的 38 次崩溃。演示程序会在 macOS 上无条件启动此功能,并与 power 一同运行。

文件,以及发生变化的行。 生成的头文件、被工具重写的锁文件、被脚本覆盖的配置文件——superlog-watch 会将变更放入与随后失败的构建相同的有序数据流中;使用 --diff 时,它还会指出具体变化的行:每个区块对应一条事件,删除和新增内容一并呈现;一次保存中的所有区块都会与其修改事件共享同一个 trace,因此一次查询就能返回完整编辑内容。基于内容哈希实现幂等——如果重写没有改变任何字节,就不会发布事件,而这是 mtime 监视器无法保证的。

完整历史,而不只是最近几分钟。 日志会将每一帧原样写入磁盘;search 使用与实时数据流相同的筛选条件读取历史记录(包括 --trace),replay 则按原始速率重新发布。一个 1 GB、包含 480 万条事件的日志,搜索耗时约为 2.3 秒。

脚本和 Agent 均可读取。 GET /recent?since=<cursor>&level=ERROR 会回答“自上次查看以来发生了什么”,其游标可确保事件既不会遗漏,也不会重复。 一个 MCP 服务,让编码 Agent 能读取调试台。 七个工具,设计方式很重要:Agent 的上下文有限,海量输出并不适用,因此每个工具都会先筛选、限制输出,并为每个事件返回一行精简信息。文档也可供查询:stream_guide 会详细说明任意功能——power.* 的指标是什么意思、dl.* 停滞为何会升级、差异为何可能没有输出——内容来自 guide.json,同一文件中的操作手册(故障排查、跟踪调用链、静默流……)也会作为原生 MCP 提示词提供。每项日志记录功能都附有相应条目,因此 Agent 无须猜测某个主题的含义。

工具用途
hub_status调试台是否启动——区分“hub 已关闭”和“应用没有记录任何内容”
list_streams查看概况:哪些主题处于活动状态、各自的级别分布,以及哪些主题包含错误
tail_logs按主题、级别或文本查看近期事件,并提供游标,让重复调用只返回新增内容
search_logs知道消息内容但不知道所属流时,按文本查找
search_history磁盘上的日志:查询几小时或几天的记录,回答“凌晨 3 点发生了什么”
wait_for阻塞等待匹配事件到达,而不是休眠并寄希望于事件出现
list_webhooks查看告警网关的路由及其公开 URL 和健康状态——获取交给 webhook 发送方的 URL,无须询问人工
agent_report将自身信息登记到Agent 日志:运行所用的大模型、状态、任务、完成百分比和承诺的更新频率——这是本服务执行的唯一写入操作,且只能写入 agent.*
stream_guide调试台自带的手册:按需获取各项功能的详细文档和操作手册
npm run demo:mcp        # drives all six over stdio and prints what an agent sees
# from a clone:
claude mcp add super-log --scope user -- node $PWD/sdk/js/packages/mcp/bin/superlog-mcp.mjs
# or, once published, with no clone at all:
claude mcp add super-log --scope user -- npx -y @super-log/mcp

每台机器只需注册一次,而非每个项目各注册一次:一个 hub 服务于所有项目,Agent 可按主题前缀缩小范围。设计上只读——只有一个刻意限定的例外:Agent 遥测信息,且只能写入 agent.* 状态主题(见下文的 Agent 日志)——并且无需依赖项——基于 stdio 的 MCP 使用以换行符分隔的 JSON-RPC 2.0。

不只支持 Claude——支持任何 MCP 客户端。 同一服务可接入任何支持 MCP 的工具;配置始终为 npx -y @super-log/mcp:

// the universal server entry — Cursor, VS Code, Cline, Windsurf, Zed, Gemini CLI, …
{ "command": "npx", "args": ["-y", "@super-log/mcp"], "env": { "SUPER_LOG_URL": "http://127.0.0.1:7333" } }

有三种添加方式:

  • MCP Registry — 收录为 com.super-log/super-log,可在应用内发现并按名称安装(claude mcp add com.super-log/super-log);它会自动同步到 Cursor / VS Code / Glama / PulseMCP 目录。
  • Claude Code 插件 — 先运行 /plugin marketplace add saxonnicholls/super-log,再运行 /plugin install super-log@super-log(添加该服务以及一个 /super-log:stream-guide 技能)。
  • Claude Desktop — 双击 release 中的 .mcpb,无需命令行。 面向 Cursor、VS Code、Cline、Windsurf、Zed、Continue、LibreChat 和 Gemini CLI 的各客户端配置,以及 OpenAI Agents SDK / Google ADK 配方,见 docs/CONNECT.md。

已经在使用 OpenTelemetry?只需添加一个 exporter 配置,无需新增 SDK。 superlog-otlp 是一个运行在标准地址 127.0.0.1:4318 上的 OTLP/HTTP 服务, 因此将现有 exporter 指向它无需配置 endpoint: OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318。它同时支持 OTLP/JSON 和 OTLP/protobuf(采用手写的有界解码器,因为大多数 SDK 默认使用 protobuf),会将 severityNumber 映射为 bench 级别,并将每个 resource 的 service.name 转为 otlp.<service> 主题。让它不止是网关的关键在于:OTel span 的 trace_id 会成为事件的 trace,因此能与 withTrace() 自身的 ID 对齐 ——一次 /recent?trace= 查询即可将 OTel spans 和 bench 的日志行串联成一个完整故事。指标会以 metric 读数的形式到达(直方图汇总为 count 和 sum,绝不编造百分位数);spans 则作为携带 trace 的 DEBUG 事件,因为这是一个入口,而不是 span 存储库。

反向也可以:从 bench 将数据发送到现有的 OpenTelemetry 后端。 superlog-otlp-export 会读取 hub,并将 OTLP/JSON POST 到任意 collector 或供应商——superlog-otlp-export --endpoint https://otlp.example.com --header 'authorization: Bearer …'。每个事件都会变成一个 OTLP LogRecord (反转 severity 级别,service.name 取自 origin.app,字段作为 attributes,32 位十六进制的 trace 作为 traceId);bench 的 metric 事件会变成 OTLP gauge 数据点。我们有意只支持 JSON——这是所有 collector 都接受的格式; 需要 gRPC/protobuf 的后端只需经过一跳 OTel Collector(见 docs/OTLP.md)。bench 没有自己的 spans,因此不会导出 traces。入口和出口结合起来,让 super-log 成为 bench 中支持双向 OTLP 的成员,而非封闭花园。

当无人关注时,仍有消息会送达。 实时数据流上的规则通过一个通道注册表触发——目前支持 console、桌面、webhook 和 command;Telegram、Twilio SMS / WhatsApp 和 email 则是同一注册表中由配置启用的条目(superlog-alert --channels 会列出所有通道,以及每个缺失通道所需的确切配置)——并将告警写回 bench,作为 alert.*。规则有四种,因为生产环境会以四种方式出问题:记录了某些坏事、某类日志过多、某些内容停止记录(安静的服务器和宕机的服务器看起来一样,直到你检查为止),以及多件事同时发生——当所有条件都在同一个时间窗口内满足时,combo 规则就会触发。这种关联(“发生了部署 AND 错误激增”)是任何单个过滤器都无法表达的。 生产环境可以拉响警报,而不记录任何日志。 每个 SDK 的 PRODUCTION 模式都按设计不发送任何内容——但 ALARM 不是日志记录, 而是一种罕见且有意为之的操作,superlog-alarm 就是它的入口:受令牌保护的 公共 webhook,通过 Cloudflare 隧道接入——可以使用无需配置的快速 隧道,也可以在提供令牌后通过 API 自动配置稳定的命名隧道。Cloudflare 是经过验证、日常使用的路径:快速隧道、命名隧道、 DNS 配置和端点工厂都已在真实环境中运行验证。--tunnel ngrok 和 --tunnel zrok 已有代码路径,但尚未以同样的方式运行验证——请参阅 Future directions。任何语言都可以通过一次 curl 触发 alert.inbound.<name>;同一个键再次触发时,仍是一条警报,只增加重复计数, 不会变成 113 条通知;恢复会闭合整个流程;每个检查器都可以 POST 心跳,这样当监视器无响应时,网关本身会触发 monitor_dead:<name>——这是失效的监视器无法发送的警报。两个查看器都带有警报记录面板——一个稀疏面板,有意与信息洪流截然不同,每个键占一行——并配有测试警报按钮,逐步验证整个路径:hub、隧道、从互联网经公共 URL 返回的真实往返、通知渠道(包括当你自己的路由器 DNS 屏蔽隧道名称、而生产环境却能正常解析时给出的诊断),然后是已登记的每一条路由,每条都有自己的结论——只有通过公共 URL 发出的探测最终作为 wh.<name> 事件返回 hub,捕获端点才算通过;返回 502 的转发端口会报告“隧道正常,但你的服务未运行”;而仅监视 URL 只要返回任何 HTTP 响应就算通过,因为任何响应都能证明链路正常。

还能直接从代码触发:SN_ALARM。 网关是生产系统通过网络发出警报的入口;而对于运行中的程序本身想要发出的警报——应当唤醒某人的断言、失效的依赖——每个 SDK 都提供了一流的原语,可以绕过日志,直接写入记录面板:

SN_ALARM("settlement engine unreachable");        // C++: P0, fires in the panel
SN_ALARM_CLEAR("settlement engine unreachable");  // clears it

super-log 支持的每种语言都有同样的调用——sn_alarm()、 sn_alarm!、.alarm() 及其同类接口——因为只能从一种语言触发的警报,是事故会趁虚而入的缺口。它会在 alert.native.<key> 上发出事件,规则引擎按设计会忽略该事件(因此绝不会触发规则),并按键去重、仅在状态变化时触发,因此热点循环只会产生一条警报,而不会刷屏;CRITICAL 会触发警报,INFO RECOVERED 会将其关闭。它会直接发送到 hub——即时且本地;网关的跨进程去重和重复计数适用于跨网络传递的警报。并且它刻意不受 PRODUCTION 模式限制:你主动请求的警报,是生产环境中绝不能静默的唯一内容(如有需要,可设置 SUPER_LOG_ALARMS=0 将其静默)。每个演示客户端都会触发一条,方便你观察警报到达。 该网关也是一个端点工厂,而查看器分别面向两类用户:告警(生产环境)——简洁的事件列表,以及告警路径自身的路由;webhook(开发环境)——端点网格,以及实时信息流,展示捕获到的每次投递及其载荷、签名验证结果和中继状态。ImGui 和 React 查看器会从同一份声明式配置 viewer/menu.json 渲染菜单栏,用户可通过菜单栏切换视图;其中还包括一个Agent 事件列表(View → Agents):每个在工作台上运行的 Agent 占一行,显示名称、所用的大模型、最新状态和最后一次活动时间。MCP 消费者一连接就会显示(服务会公布自身的 initialize 握手),其工具调用会显示为请求事件;任何对象都可以通过 agent_report MCP 工具,或向 agent.<name> 发送一次 POST 来报告信息,声明其大模型、任务、进度百分比和承诺的报告频率——默认每 15 分钟一次,并在事件发生时报告,这样一个持续 8 小时的任务也能保持可见。活跃度指示灯按每个 Agent 自己的承诺频率判断:2 倍时间内为绿色,之后为 late,再之后为 silent——Agent 没有遵守自己的承诺时,界面会如实显示。还有一块服务面板: 每个事件都带有 origin.device,因此中心的流量就是服务列表——每台机器占一行,显示活跃度指示灯(up / quiet / silent)、最后一次活动时间,以及过去一分钟内最高的级别;无论事件是由什么机制产生的(生命体征读数、ping 指标或应用自己的 SDK)。只需看一眼,就能知道“构建机正常吗”;如果答案显示为灰色,只需在事件洪流中筛选一个主题,就能查看详细日志——而 alerts.json 中的 silence 规则可将灰色状态转为告警。每条路由都显示为网格中的一行—— status : route : url : seen : ping——展开后可查看完整诊断信息,并提供ping(使用同一套看门狗记录立即测量此路由)、copy(复制完整公共 URL)和删除按钮;每条路由都会按照各自可配置的时钟进行 ping(连续两次失败会触发 tunnel_down:<name>,恢复后则会清除告警)。点击 + endpoint 即可一键配置端点——仅输入名称即可将投递捕获为 wh.<name> 事件(将 URL 粘贴到 Stripe 或 GitHub webhook 表单中,即可查看投递到达),输入名称和端口则会转发本地服务。许多端点更适合通过文件配置:npm run alarm -- --provision endpoints.json 会应用声明式清单({"name":"stripe"} 用于捕获,{"name":"webapp","port":5173} 用于转发,{"name":"partner","url":"https://…"} 用于仅监视,每项均可选填 interval_s),并在文件变更时持续应用配置——新增名称会显示,已删除的名称会被移除,但仅限由该文件创建的名称。endpoints.json.example 展示了所有格式;实际文件会被 Git 忽略,因为名称、端口和签名密钥都涉及工作台环境。每次变更时,网关当前持有的所有 URL 都会写入 endpoints.env(被 Git 忽略),因此脚本和 Agent 只需加载该文件即可——Agent 也可以直接查询:MCP 服务的 list_webhooks 会返回包含公共 URL 和健康状态的端点列表。 正确地测试 Webhook——以 Stripe 为例。 仅有一个捕获端点就已经是 Webhook 检查器:将其 URL 粘贴到 Stripe 控制面板中(或对其运行 stripe trigger payment_intent.succeeded),每次投递都会作为 wh.stripe 事件到达,其中包含方法、请求头(保留 stripe-signature)和最多 32KB 的正文——真实的发票事件大小为 5–15KB,且会完整到达。再加上两项功能,它就会从窥视孔变成开发工具。向端点提供签名密钥(在清单中填写 "secret":"whsec_...",或设置 STRIPE_WEBHOOK_SECRET),网关便会在到达时验证 Stripe 签名方案:每个事件都会带有 sig: verified 或 sig: FAILED(WARN),过期时间戳会标记为可能的重放攻击——如果没有密钥,则不会显示该字段,也绝不会给出虚假的判定。为端点设置转发目标({"name":"stripe","relay":5000} 或完整 URL,例如 http://127.0.0.1:5000/webhook),每次投递也会转发给你的本地处理程序,真实响应会返回给 Stripe——这相当于 stripe listen --forward-to,但每次投递、签名判定以及处理程序的状态码,都会按顺序显示在工作台上,与应用自身的日志并列。如果处理程序不可用,WARN 会显示 relay unreachable——这正是 Webhook 测试要找出的问题。(当工作台只需测试自身时,local: true 可完全跳过隧道。)GitHub 的方案(x-hub-signature-256)会与 Stripe 的方案一样,使用同一个密钥进行验证;其他提供商各需一条配置语句。

Webhook 面板的投递记录流专为查看结果而设计:可以像主日志一样按端点和级别筛选(WARN 可单独显示签名验证失败和无法连接的转发目标),就地展开任意投递的有效载荷,并复制——每条投递各有一个按钮,整个筛选结果集也有一个按钮——即可获取可直接粘贴的证据:记录流条目、签名和转发判定,接着是原样的有效载荷。整个流程通过 Stripe CLI 进行端到端测试:

stripe listen --print-secret            # the whsec_... for endpoints.json
stripe listen --forward-to http://127.0.0.1:7336/hook/stripe-test
stripe trigger payment_intent.succeeded # real events, signatures verified

生产端永不阻塞。 每个 SDK 都使用有界队列,在突发流量下丢弃最早的条目——丢弃数量会统计,绝不隐藏。会拖慢所观察应用的日志记录器,还不如没有。

从设计上确保生产环境中关闭。 每个 SDK 都要求声明 DEVELOPMENT 或 PRODUCTION(两者都不声明或同时声明都会拒绝构建),并且每种模式只会打包其策略允许的内容。生产环境默认什么都不启用。

它是什么,以及它不是什么

它是一个开发工具。 就像你坐在工作台前:工作时,你的机器和设备发出的所有信息都会按发生顺序显示在同一个屏幕上。它针对故障发生后的十秒进行了优化——一个中心、不必声明架构、无需安装 Agent,只需输入一条命令即可添加数据流,而且在第一条信息出现前无需进行任何配置。

它不是一套可观测性工具,也不应被当作可观测性工具使用。 这不是谦虚,而是设计使然:让它擅长完成第一项工作的若干特点,也让它不适合完成第二项工作。

super-logPrometheus / Grafana / Loki / Datadog
运行位置工作时运行在你的机器上永久运行在生产环境中
保留期限内存中的环形缓冲区,以及你选择开启的日志文件数月,已建立索引,可查询
身份验证无 — 默认仅限环回访问租户、RBAC、审计
规模一个工作台,少量服务数千台主机
告警“我在这里时通知我”的规则值班、升级处理、SLO
添加数据流的成本一条命令修改数据管道

具体来说,不要将它指向生产环境后就置之不理。中心没有身份验证:任何能够访问该端口的人都可以读取所有数据流,并向任何主题发布消息。它会在内存中为每个主题保留 2000 个事件;除非日志文件正在运行,否则其余事件都会被丢弃。不过,吞吐量并不是阻止你使用它的限制:在一台开发机器上,单个中心处理了 250 台机器集群产生的突发流量——2.3 秒内处理了 500,000 个事件(约 217,000 个事件/秒,67 MB/s),没有丢失任何事件——即使在数据到达时,按级别筛选的 /recent 仍能在 16 毫秒内响应(bench/loadtest.mjs — 你也可以用临时中心亲自复现)。它不适合生产环境,原因就是上面那两点:没有身份验证,且使用内存环形缓冲区,而不是数据接收路径。

在生产环境中,拉取数据是合理的:ssh tailer 和集群运行器会通过 ssh 将远程日志读取到你的工作台,因此生产环境无需连接中心,中心也无需对外暴露。这正是集群支持的预期用法。

当你发现它不够用了时,你并没有白费功夫——它使用纯 HTTP 上的 NDJSON 线格式(参见 docs/PROTOCOL.md),因此,只需编写一个小脚本订阅数据流并重新发送,即可将数据转发到 Loki、Elasticsearch 或 OTLP collector。这两种工具回答的是不同的问题,同时使用两者也很合理:这个工具用于回答“我正在查看时,现在发生了什么”,那个工具则用于回答“上周二凌晨三点发生了什么”。

收集不是分析——这正是整个设计的核心

super-log 只做一件事:收集并整合。工作台产生的每路数据流,都通过同一条线路、按同一顺序显示在同一个屏幕上。它刻意不做分析——没有查询语言,没有需要搭建的仪表板,也没有必须在获得第一个洞察前编写的规则引擎。这种分离并非功能缺失;这正是设计的要点。收集应该简单、快速且可信;分析是另一项工作,需要不同的工具。把两者耦合起来,日志技术栈就会从帮助你运维的工具,变成需要你运维的对象。

这与 log4j 带来的教训正好相反。 Java 日志领域把一个庞大、强大、会执行评估的框架放进了每个应用程序中——2021 年,全世界见识到了这要付出什么代价:日志消息中的一个字符串,竟会在半个互联网范围内变成远程代码执行漏洞(log4shell)。本该观察应用程序的框架,反而可能危及它。super-log 将这一切彻底反转:

  • 日志记录在应用程序之外。 super-log 生产端是一个轻量、零依赖的 SDK——通常只有一个文件,甚至完全不需要代码:将现有的 OpenTelemetry 导出器指向它,或直接读取文件即可。你的进程中没有会成为负担的重型框架。
  • 内容是数据,绝不是代码。 收集端的每个解析器都有严格边界,会将输入视为要存储的字节,而不是要执行评估的表达式——log4shell 这类漏洞在结构上不可能发生。
  • 生产环境默认不发送任何内容。 每个 SDK 都是 DEVELOPMENT-xor-PRODUCTION;PRODUCTION 是一个空壳,只有你选择启用某个级别后才会发送内容——编译型语言会将线路代码完全编译掉,而且可以验证这一点(strings 在二进制文件中找不到任何端点)。

因此,应用程序保持简单安全,工作台仍然只是工作台,而你如何处理整合后的数据流,是一个独立且明确的选择——亲自查看、转发到长期存储,或交给能够进行推理的工具。

super-log.com — 面向团队的分析与 AI

开源工作台是收集与整合层,永久免费,且可自托管。super-log.com 是构建在它之上的分析层——当整合后的数据流多到一个人无法监看时,可使用这项商业服务:

  • 对整合后的日志进行实时大模型分析。 AI 会实时读取交错的全部数据流——同时涵盖每个服务、设备和链路——并用队友会使用的通俗语言告诉你发生了什么变化、哪些内容相关联,以及什么即将出错。人类通常会在问题发生后的十秒内做出这些判断;AI 则会持续分析那些无人能全部监看的数据流。
  • 专为团队打造。 通过一个整合视图查看所有人的工作台和服务,共享上下文,保留不受笔记本电脑更替影响的历史记录,并向能够解决问题的人发送警报。开源查看器中的服务器、Agent 和 PR 看板展现了大致形态;托管服务则将它们变成共享、持久且经过分析的界面。
  • 保持同样清晰的边界。 super-log.com 使用的正是开源中心所处理的 NDJSON 线路,无需新增任何插桩。收集的数据仍由你掌控,并由你自托管;你可以选择将数据流发送上去进行分析,也可以随时停止。收集与分析始终彼此分离。

开源项目本身已完整且实用——商业服务提供的是你原本需要自行构建的分析能力,而不是给工作台设一道付费门槛。

安装

最快捷的入场方式是使用 Homebrew,还附带一个可运行示例:

brew install saxonnicholls/tap/super-log     # the hub + all the tailers

# See it work in three lines:
brew services start super-log                        # hub on :7333
echo "the bench is live" | superlog tee --topic hello    # a line of your own
curl -s "http://127.0.0.1:7333/recent?topic=hello"   # read it back

# Then put your machine on the bench — any of these, immediately:
superlog-netstate      # gateway, Wi-Fi, VPN, resolvers — changes only
superlog-otlp          # OpenTelemetry inlet on :4318
superlog-otlp-export   # OpenTelemetry outlet: bench -> OTLP/HTTP JSON backend
your-build 2>&1 | superlog tee --topic build    # anything that prints

其他安装渠道都会安装中心服务和日志采集端:

# npm — the tailers and the MCP server as commands, anywhere Node ≥18 runs
npm install -g @super-log/tailers @super-log/mcp

# Ubuntu (incl. Ubuntu Server on a Pi) — the PPA: apt install super-log, every
# architecture Launchpad builds (arm64 included), with apt upgrade after.
sudo add-apt-repository ppa:super-log/stable
sudo apt update && sudo apt install super-log

# Debian / Raspberry Pi OS — PPAs are Ubuntu-only (no add-apt-repository here),
# so take the .deb from the release (apt, not dpkg -i, so nodejs and libatomic1
# come with it). Pick your arch — arm64 for a 64-bit Pi, amd64 for x86:
curl -fsSL -O https://github.com/saxonnicholls/super-log/releases/download/v0.4.0/super-log_0.4.0_arm64.deb   # or _amd64.deb
sudo apt install -y ./super-log_0.4.0_arm64.deb

# Fedora / RHEL / Rocky / Alma — dnf takes the URL directly:
sudo dnf install -y https://github.com/saxonnicholls/super-log/releases/download/v0.4.0/super-log-0.4.0-1.fc41.x86_64.rpm

# vcpkg — the SDK for C/C++ (incl. Windows/MSVC). C header by default;
# [cpp] adds the header-only C++ SDK (pulls the ts-moveables vcpkg port).
vcpkg install super-log          # the zero-dependency C header
vcpkg install super-log[cpp]     # + the C++ SDK (event model, SN_LOG)

根据你的架构选择 .deb(dpkg --print-architecture)。在使用 systemd 的主机上,中心服务会在安装时启动;在没有 systemd 的容器中,请自行运行 superlogd。

安装时,中心服务会绑定到 回环地址(127.0.0.1)——这适用于开发机器,但如果这台机器是其他设备通过局域网向其发送日志的采集器(树莓派的常见用途),其他设备将无法连接,除非你绑定网络:在服务环境中设置 SUPER_LOG_LAN=1(或 SUPER_LOG_BIND=0.0.0.0),然后重启 superlogd。两端都不会显示错误——设备会显示“从未报告”,中心服务也不会记录任何日志——因此,如果你要将其设置为局域网日志接收端,请参阅 docs/DEVICES.md 中的“回环地址陷阱”以及如何让局域网绑定在重启后继续生效。

MCP 服务也可以直接通过 npx 运行,无需安装: claude mcp add super-log -- npx -y @super-log/mcp。查看器(原生和 Web 版)随克隆仓库提供——请参阅下文的快速开始。打包源文件和维护者的发布步骤见 packaging/。

快速开始(从克隆仓库开始)

git clone --recurse-submodules --shallow-submodules <this repo>
cd super-log
cp .env.example .env             # optional: chain endpoints, hub defaults

# The one-command install: preflight the toolchain, build the hub and
# viewer, and verify every SDK actually delivers to a real hub before
# calling itself done. Add --persist to start the hub and the default-on
# tailers at login and keep them running across reboots.
./scripts/install.sh             # build + verify
./scripts/install.sh --persist   # ...and run at login, forever

# The whole demo: hub, C++/Rust/iOS/Android/browser/container clocks,
# OS-log streams, both viewers - one command
npm run demo                     # see demo/README.md for the tour

# ...and the other languages on the same screen, if their toolchains are here
SUPER_LOG_LANGS="go python java swift fortran shell" npm run demo

# Or piece by piece:
./scripts/build_all.sh           # rebuild everything (hub, native + web viewers, SDK tests)
./scripts/dev.sh                 # build + run the hub
npm install && npm run viewer    # web viewer on http://localhost:7334
npm run tail:android             # first stream: the Android emulator

演示会绑定到回环地址。真实手机需要连接到局域网中的中心服务: SUPER_LOG_LAN=1 ./demo/run.sh——请先阅读安全部分。查看器会根据提供页面的主机找到中心服务,因此从另一台机器打开页面无需配置。

配置文件

大多数数据流无需配置——直接运行即可。少数包含机密(API 密钥、带密钥的 RPC URL、钱包地址、令牌)的数据流会读取一个已加入 Git 忽略列表的文件,因此凭据绝不会进入提交记录。每个配置文件旁边都有一个对应的 .example 文件:复制,然后编辑。整个流程就是这样——运行 cp <name>.example <name>,填入你的值,然后运行。

复制此文件 → 到此文件用途文件内容
.env.example → .envhub、链、警报和所有需要 API 密钥的监控器Hub URL/绑定地址、链的 RPC 端点、Cloudflare/Stripe/Twilio/Telegram 凭据、PR 登录信息、Starlink 天线、通知渠道——每个区块都有注释,留空的项目就不会被监控。
rpc.json.example → rpc.jsonnpm run rpc — RPC 节点健康状况每条链、每个提供商对应一项 {chain, provider, url, kind?}。每条链配置两个提供商(一个 QuickNode 和一个 Alchemy)才是关键——面板会显示哪个提供商宕机。URL 格式已预填;粘贴你的密钥即可。
gas.json.example → gas.jsonnpm run gas — 密钥余额/立即补充警报每条链上需要监控的运维密钥,以及crit/warn阈值。包括 EVM、Solana、Tron、Bitcoin(kind)。即使地址是公开的,带标签的密钥列表也会成为攻击者的地图——因此该文件已加入 Git 忽略列表。
alerts.json.example → alerts.jsonnpm run alert — 针对工作台的规则规则:记录了不良内容、记录过多、数据流静默,或在一个时间窗口内满足多种条件——以及需要触发的通知渠道。
endpoints.json.example → endpoints.jsonnpm run alarm — webhook 端点工厂以声明方式配置的公共端点:捕获、中继、转发或仅监控——也包括 Stripe/GitHub webhook 测试。
fleet.json.example → fleet.jsonnpm run fleet — 同时管理多台机器一次性描述一组主机(ssh、OS 日志、docker、文件);一个监管程序便会启动所有数据流,并重启任何已停止的数据流。

按设计无需配置——无需创建任何内容:服务器、设备(USB)、Agent和PR面板会根据 hub 自身的流量或 gh/env 设置生成数据;OS/应用/网络监控器则接受命令行标志。如果监控器找不到所需文件,它会告诉你准确的 cp 命令并停止——绝不会在配置不完整的情况下运行。

在项目中安装

克隆一次,在每个项目中使用。 super-log 不是要添加到仓库中的依赖项,而是安装在机器上的工具,就像调试器一样。集线器在整台机器上共享:每个工作台一个,所有项目共用。两个集线器就意味着两个端口相互竞争,两个查看器也各自只能显示三分之一的内容。

这样也能保持仓库整洁,还能避免实际的成本——对本仓库使用 Cargo git 依赖会克隆全部 5 个子模块,以及 Rust crate 根本不会用到的 36 MB C++ 代码。

git clone --recurse-submodules --shallow-submodules \
  https://github.com/saxonnicholls/super-log ~/dev/super-log
cd ~/dev/super-log && npm install        # once, for the tailers and the web viewer

然后,在每个项目中:

~/dev/super-log/scripts/setup.sh ~/code/my-app

这会在项目中准确写入两个文件,不会改动其他内容:

文件说明
superlog.conf本项目的日志配置——唯一需要编辑的文件
logging.sh独立的 POSIX-sh 启动脚本,约 250 行

.superlog/(PID 和日志)会添加到 .gitignore 中。系统会检测项目类型,因此配置文件会预先填好内容,而不是空白——package.json 会配置为 npm run build,CMakeLists.txt 会配置为 cmake --build build -j,依此类推。

cd ~/code/my-app
$EDITOR superlog.conf     # topic prefix, dirs to watch, log files, services
./logging.sh start        # hub + viewer + this project's streams
./logging.sh status       # what is running, and where
./logging.sh stop         # stops what THIS project started - not the shared hub

让它随构建和运行一起启动,正是最后两条命令的作用。如果日志服务尚未启动,它们会先启动日志服务,因此只需将它们接入你已有的命令即可:

./logging.sh build        # your build, with its compiler diagnostics as events
./logging.sh run          # your program, output teed to the terminal AND the bench

build 会通过构建包装器运行,因此警告和错误会以带有 file:line 的 WARN/ERROR 形式送达,sanitizer 或 valgrind 发现的问题也会完整送达。run 会通过 superlog-tee 运行,因此标准输出会逐字节同时输出到终端和工作台。它们都可以直接替代所包装的命令——使用 alias b='./logging.sh build',或者使用 setup.sh --wire,在 Node 项目中添加 npm run log / log:stop / log:status。

在多个项目之间共用一个工作台,效果正如你所期望的那样。 第二个项目执行 start 时会发现集线器和查看器已经启动,只添加自己的数据流;每个项目的主题都带有各自的前缀,因此在查看器中彼此可区分;停止一个项目不会影响其他项目的运行。

如果你更愿意将 SDK 直接接入代码,而不是从外部查看,请看下一节——不过这不是必须的,对于大多数项目而言,启动器就足以查看所有内容。

将自己的应用接入工作台

React Native / 浏览器 / Node(@super-log/client,零依赖):

import { createSuperLog } from '@super-log/client';

const slog = createSuperLog({
  url: 'http://192.168.1.20:7333',   // your bench machine
  topic: 'expo.ios.device',          // topics name streams - PROTOCOL.md
  app: 'my-app',
  development: __DEV__,              // exactly one of these two, or it throws
  production: !__DEV__,
  patchConsole: true,                // console.* now reaches the bench
  patchNetwork: true,                // ...and every HTTP call it makes
});

// One id for everything this action causes, on every tier it reaches
await slog.withTrace(async () => {
  slog.info('checkout mounted', { user: '42' });
  await fetch('https://api.example.com/v1/pay');   // header added for you
});

React 树可以通过 @super-log/react 一次性封装:<SuperLogProvider> 管理客户端和错误边界,后者会记录组件堆栈——全局处理器无法看到这一信息,因为 React 会吞掉渲染错误。

C++(仅头文件;使用 -DSUPERLOG_DEVELOPMENT 或 -DSUPERLOG_PRODUCTION 编译):

superlog::transport_config cfg;
cfg.topic = "cpp.pricer";
auto bat = std::make_shared<superlog::batcher>(cfg);   // before the logger

superlog::origin who;
who.app = "pricer";
spdlog::default_logger()->sinks().push_back(
    std::make_shared<superlog::spdlog_sink_mt>(bat, who));

superlog::install_terminate_handler(bat, who);  // uncaught exceptions + stack

纯 C(单个头文件,采用 stb 风格;使用相同的两个宏;PRODUCTION 会将每次调用都编译为空——用 strings 检查二进制文件中是否存在 /ingest/,以此证明日志记录器不存在,而不是仅仅相信一个标志):

#include "superlog.h"                    /* sdk/c, zero-alloc, POSIX sockets */

superlog_t lg;
superlog_init(&lg, "c.engine", "engine");
superlog_info(&lg, "engine up, port %d", 9000);
superlog_kv(&lg, "ERROR", "no rate", "symbol", "DOGE", NULL);
superlog_metric(&lg, "queue.depth", 17);
superlog_flush(&lg);

Python(仅使用标准库;在 development= / production= 中二选一):

import logging, superlog

log = superlog.SuperLog(topic="python.pricer", app="pricer", development=True)

logging.getLogger().addHandler(log.handler())  # everything already logged
log.install_excepthook(capture_locals=True)    # and every crash, with locals

with log.traced():                 # ContextVars: async- and thread-safe,
    log.info("order received")     # and inherited by everything called inside
    stdlib_logger.debug("pricing") # ...including plain logging calls

Python 版本有两项其他 SDK 无法提供的功能。logging.Handler 意味着程序已有的每一行日志都会送达工作台,无需修改调用位置。capture_locals 会附加失败帧中的局部变量,因此异常会显示 symbol='DOGE', n=7,而不只是说明异常发生的位置——看起来像秘密的名称会被隐藏,值也会截断。

Go(log/slog 处理器,因此现有调用无需修改):

log, _ := superlog.New(superlog.Config{
    Topic: "go.pricer", App: "pricer", Development: true,
})
defer log.Close()
slog.SetDefault(slog.New(log.SlogHandler(nil)))   // everything already logged

ctx, _ := superlog.WithTrace(context.Background(), "")
slog.InfoContext(ctx, "order received")           // ...on the tick's trace

go func() { defer log.Recover("worker"); work() }()  // panics, with stack

Trace 存放在 context.Context 中,而不是 goroutine 局部变量中,因为 Go 特意没有提供这种变量——因此 ID 会随 context 一同传递。Recover 会记录 panic,然后重新触发 panic:如果日志记录器吞掉了崩溃,就改变了它本应观察的程序。

Java 和 Kotlin(java.util.logging 桥接;使用 InheritableThreadLocal 传递 trace,因此池化任务会继承提交任务的 ID):

var log = SuperLog.builder().topic("java.pricer").app("pricer")
                  .development(true).build();
Logger.getLogger("").addHandler(log.julHandler());   // everything already logged
log.installUncaughtHandler();                        // and every crash

log.traceScope(() -> {
    log.info("order received");
    pool.submit(log.wrap(() -> log.debug("settled")));  // same trace
});

Swift(@TaskLocal 跟踪信息由子任务继承):

let log = try SuperLog(topic: "swift.pricer", app: "pricer", development: true)
try SuperLog.withTrace {
    log.info("order received")
    Task { log.debug("pricing pass") }   // same trace, nothing passed in
}

刻意不提供 setTrace():TaskLocal 只绑定到作用域,不会绑定到其他地方。因此,常见的泄漏问题——一个请求的 ID 延续到下一个请求——不是仅仅不建议,而是根本无法表达。

Fortran(通过 ISO_C_BINDING 使用原始 POSIX 套接字,不使用 libcurl):

call sl_init(topic='fortran.solver', app='solver')
call sl_set_trace(sl_new_trace())
call sl_metric('solver.residual', residual)
if (residual /= residual) call sl_error('residual is NaN')
call sl_close()

求解器是测试台上最难观测的程序:运行数小时,通常无法连接到它,而证据都在一份 slurm 文件里,直到作业时长用完才有人去看。DEVELOPMENT 与 PRODUCTION 只能二选一,与 C++ SDK 一样,两者同时定义会触发预处理错误;初始化时会忽略 SIGPIPE,因此即使 hub 在运行 12 小时后关闭,也不会导致任务终止。

Shell(任何脚本,一行即可):

superlog-log --topic deploy "starting rollout"
tail -f /var/log/app.log | superlog-log --topic app.foo --level WARN

Rust(使用 --features development 或 --features production 构建):

let log = super_log::SuperLog::new(super_log::Config {
    topic: "rust.pricer".into(),
    app: "pricer".into(),
    ..Default::default()
});
log.install_panic_hook();                 // panics, with location
log.log(super_log::Level::Info, "engine up", None);
log.metric("fps", 58.9);

Ruby,以及 Rails(仅使用标准库;模式由 SUPERLOG_MODE 指定):

require_relative "superlog"                        # sdk/ruby/superlog.rb
log = SuperLog.new(topic: "ruby.myapp", app: "myapp")
log.info("up", port: 3000)
log.metric("queue.depth", 17)

# Rails: everything it already logs, one assignment, no controller touched
config.logger = ActiveSupport::BroadcastLogger.new(
  ActiveSupport::Logger.new($stdout), log.logger_adapter)

Scala(有意不提供 SDK——使用 Java 客户端,只需导入一项,无需胶水代码,与 Kotlin 完全相同):

import com.snicholls.superlog.SuperLog

val log = SuperLog.builder().topic("scala.pricer").app("pricer")
  .development(true).production(false).build()
log.installUncaughtHandler()              // every thread's uncaught, chained
log.info("engine up", SuperLog.fields("port", Int.box(9000)))

OCaml(只使用 unix 库;模式由 SUPERLOG_MODE 指定):

(* ocamlc -I +unix unix.cma superlog.ml myapp.ml -o myapp *)
let log = Superlog.create ~topic:"ocaml.solver" ~app:"solver" () in
Superlog.info log "solver up" ~fields:[ ("mesh", "1.2M cells") ];
Superlog.metric log "solver.residual" 1.6e-9;
Superlog.flush log

Haskell(GHC 的引导库加上 curl;模式在编译时确定——使用 -DDEVELOPMENT 或 -DPRODUCTION,两者都未定义或同时定义都会导致构建失败):

import SuperLog
main = do
  lg <- newLog "haskell.pricer" "pricer"
  info lg "engine up" [("port", "9000")]
  metric lg "queue.depth" 17
  flushLog lg

Lean 4(核心 IO 加上 curl;模式由 SUPERLOG_MODE 指定——证明搜索会持续整夜,每千个目标记录一次指标,远胜于终端一片沉寂。superlog-build -- lake build 会监控构建本身,并将 lake 的 [n/m] 进度转换为可绘图的 build.progress_pct):

import Superlog

def main : IO Unit := do
  let lg ← Superlog.init "lean.search" "search"
  Superlog.info lg "search up" [("depth", "12")]
  Superlog.metric lg "goals.open" 4123
  Superlog.flush lg

C#(仅使用 BCL——HttpClient 和 System.Text.Json 随 .NET 一同提供;模式由 SUPERLOG_MODE 指定)。将单个文件放入任何项目即可:控制台应用、ASP.NET、使用 .NET 配置文件的 Unity 项目,或 Xbox Dev Mode 构建:

var log = new SuperLog(topic: "csharp.myapp", app: "myapp");
log.Info("up", new() { ["port"] = "3000" });
log.Metric("queue.depth", 17);
log.Flush();

Perl(仅使用核心模块——HTTP::Tiny 和 JSON::PP 随 Perl 一同提供;模式由 SUPERLOG_MODE 指定),适用于那些支撑着半个世界运行的胶水脚本和 cron 作业:

use lib "sdk/perl";
use SuperLog;
my $log = SuperLog->new(topic => "perl.backup", app => "backup");
$log->info("nightly backup starting", { host => "web1" });
$log->metric("backup.bytes", 48_211_233_792);
$log->flush;

Lua(任何 Lua 5.1+、PUC 或 LuaJIT;Lua 标准库不含套接字,因此它与 Shell SDK 一样,坦诚地选择使用 curl):

package.path = "sdk/lua/?.lua;" .. package.path
local superlog = require "superlog"
local log = superlog.new{ topic = "lua.game", app = "game" }
log:info("level loaded", { level = "e1m1" })
log:metric("frame.ms", 6.9)
log:flush()

COBOL(没错,真的如此——GnuCOBOL 通过 demo/cobol/shim.c CALL 仅含头文件的 C SDK,约 20 行代码,因此测试台上最古老的业务语言也能完整继承 C SDK,包括可通过编译验证、在生产环境中彻底移除的代码):

CALL "cobol_superlog_init"
    USING BY CONTENT Z"cobol.batch", Z"payroll"
CALL "cobol_superlog_log"
    USING BY CONTENT Z"INFO", Z"payroll run 4711 starting"

Zig 则更简单:@cImport 可直接读取 sdk/c/superlog.h(参见 demo/zig/clock.zig),只需为链接器编译一次头文件——cc -DSUPERLOG_API= -c impl.c——这也是任何具有 C FFI 的语言都能采用的单对象方案。

机器、服务、容器、链路(完全无需修改应用):

npm run tail:os -- --process MyApp          # this Mac's unified log
npm run tail:apps                           # what services log here
npm run tail:app -- postgres nginx redis    # ...then turn them on
npm run tail:app -- unity unreal            # engine editor logs, level-parsed
npm run tail:file -- /srv/app/production.log
npm run tail:ssh -- my-server               # a remote box, OS auto-detected
npm run tail:ssh -- db1 --app postgres      # ...or its postgres
npm run net -- 9000 http://localhost:3000   # every HTTP call through :9000
npm run grpc -- --listen 50052 --target localhost:50051  # every RPC, status from the trailer
npm run chain                               # watched addresses (see .env)
npm run gas                                 # operational key balances, alarmed (gas.json)
npm run gas -- --once                       # every key, one reading, right now
npm run sql -- --pg "postgres:///mydb"      # LISTEN superlog: NOTIFY from any trigger lands here
npm run sql -- --sqlite var/app.db          # an SQLite file, watched from outside the process
npm run prs -- --author you --repo owner/x  # PRs: alarms when one waits on YOU
npm run rpc                                 # RPC node health: block height, DOWN/STALLED, per provider
npm run starlink                            # the dish: latency, obstruction, alerts, outages
npm run otlp                                # OpenTelemetry inlet on :4318 (OTLP/JSON + protobuf)
npm run otlp-export -- --endpoint URL       # OpenTelemetry outlet: bench -> OTLP/HTTP JSON
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318 your-app   # ...point any OTel exporter at it
npm run fix -- --file 'log/*.messages.*.log'  # FIX sessions: Reject/rejected-fill loud, fills INFO, heartbeats DEBUG
npm run mavlink -- --udp 14550              # a drone's MAVLink: battery/GPS metrics, failsafes and STATUSTEXT loud
npm run mavlink -- --tlog flight.tlog       # ...or replay a telemetry capture
npm run usb                                 # the USB device tree - is the phone connected?
npm run netstate                            # gateway, Wi-Fi, VPN, resolvers, ARP - changes only
npm run netstate -- --ping 1.1.1.1 --ping api.example.com|30   # path quality, traceroute on failure
npm run netstate -- --once                  # what does the network look like right now
npm run topology                            # the local network as a tree, in its own window
npm run topology -- --discover              # ping-sweep the /24 to show every device, not just known ones
npm run topology -- --to 1.1.1.1 --geo      # watch a route; AS/owner per public hop (opt-in outbound lookups)
npm run dns -- example.com --asn            # ...and which AS originates it (hijack watch)
npm run dns -- example.com --once           # every DNS record + cert, then exit
npm run dns -- example.com mail.example.com # ...or watch them for change
npm run ports -- --once                     # what is listening, and which process
npm run ports -- --ssh web1 --procs nginx   # ...on a server, watched
npm run connections                         # the OUTBOUND side: who this box is talking to
npm run connections -- --ssh web1           # ...on a server; a stuck SYN-SENT is a WARN (filtered/down)
npm run vitals -- --once                    # disk, memory, CPU, load
npm run vitals -- --ssh web1                # ...on a server, watched
npm run versions -- --once                  # every version under the bench: OS, compilers, runtimes, packages
npm run versions -- --ssh web1              # ...on a server (no node needed there), diffed over time
npm run versions -- --check-conflicts       # match a downloaded known-bad list, LOCALLY
npm run alert                               # rules from alerts.json
npm run alert -- --test                     # prove delivery without waiting
npm run alert -- --channels                 # the notification roster, and what is missing
npm run alarm                               # production's webhook door, tunnelled + tested
npm run alarm -- --provision endpoints.json # ...plus many public endpoints, from a manifest
                                            # incl. Stripe webhook testing: capture, verify
                                            # signatures, relay to your local handler
npm run build -- --label cxx -- cmake --build build -j
npm run build -- --label asan -- ./build/tests   # sanitizer findings, whole
npm run git                                 # this repo: commits, branches, conflicts
npm run git -- --ssh web1 --repo /srv/app   # ...a deployed checkout
npm run github -- --repo owner/name         # CI runs, PRs, releases
npm run watch -- --dir src                  # files created, modified, deleted
npm run watch -- --dir config --diff        # ...and the changed LINES, hunk by hunk
make 2>&1 | npx superlog tee --topic build.local # superlog-tee: a drop-in tee
npm run ws -- wss://stream.binance.com:9443/ws/btcusdt@trade
npm run serial -- --list                    # boards plugged in
npm run serial -- --port /dev/ttyUSB0       # the serial console, as events
npm run cf -- --worker my-api               # a Cloudflare Worker, live
npm run stripe -- --live --account acme     # payments, redacted by default
npm run socket -- --udp 5514                # syslog from routers, switches, NAS
npm run socket -- --tcp 5515                # ...or plain lines on a raw socket
npm run ros                                 # a robot's nodes, from /rosout
npm run ros -- --files                      # ...including past runs in ~/.ros/log
npm run gpu                                 # this machine's GPU, watched
npm run gpu -- --ssh trainer1               # ...or the box with the card in it
npm run power                               # watts, thermals, top energy hogs (macOS)
npm run power -- --once                     # one power reading, then exit
npm run sys                                 # crashes, panics, shutdown causes, volumes (macOS)
npm run sys -- --once --backfill 72h        # what has this machine suffered lately?
npm run bridge -- --ssh otherbench          # another hub's whole feed, into this one
npm run dl -- -- curl -LO https://host/model.safetensors   # a download, with progress
npm run dl -- --watch ~/models --size 140GB -- hf download org/model
npm run build -- --ssh web1 -- 'cd /srv/app && cargo build --release'
基础设施监控

dns、ports 和 netstate 会对比快照,而不是持续流式传输,因此首次轮询只会建立静默基线,之后仅报告变化——监控器如果对看到的一切都发出通知,只会让人学会忽略它。其余监控器会将读数作为 DEBUG metric 事件发布,仅在触发边界条件时才提高告警级别。

Topology 窗口中的本地网络树状图

superlog-topology 会在两个查看器各自的窗口中将 LAN 渲染为树状图——包含此主机及其地址、网关,以及其下的所有设备。--discover 会扫描子网,因此机器尚未通信过的设备也会显示出来;--to <target> 会添加受监控路由,并显示每一跳的 RTT 和 AS/所有者信息。

监控项发布内容值得注意的级别
dnsdns.<domain> — A、AAAA、NS、MX、TXT、CAA 和 TLS 证书;使用 --asn 时,还会显示每个域名的前缀由哪个 AS 发起(通过 RIPEstat)NS/CAA 变更为 WARN(你大概不是有意改的;域名被劫持就是这么发生的),记录类型消失为 ERROR,证书到期前 3 周变为 WARN、1 周变为 ERROR、过期后变为 CRITICAL。TXT 变更会按类型命名,因此会说 “SPF/DMARC 记录已变更”,而不是让你比较两段长字符串。源 AS 变更或前缀消失为 CRITICAL——工作台没有 BGP 视图,但 RIPEstat 有;从外部看,这就是劫持的样子。
portsnet.<host>.listeners — 监听套接字、所属进程、pid,以及防火墙规则公共地址上的新监听器为 WARN,环回地址上的同类监听器为 INFO;监听器消失为 WARN;pid 变更会报告为重启,而不是一个服务消失、另一个服务出现;受监控进程消失为 ERROR。
connectionsnet.<host>.connections — ports 的出站补充信息:哪个本地进程连接到哪个远程端点,以进程 → 端点树的形式呈现(Linux 上来自 ss,macOS 上来自 lsof),定期发布 DEBUG 结构化读数。--ssh 可监控远程机器。套接字在两次轮询间一直卡在 SYN-SENT 状态时为 WARN——SYN 已发出,却没有任何响应,因此端口可能被过滤/丢弃,或服务已宕机;这是开发人员会花一下午排查的那种精确卡顿(端口被拒绝时会立即收到 RST,不会卡住;只有被过滤的端口才会一直卡着)。连接恢复时报告(INFO);首次连接到远程主机时报告一条 INFO——回答“我的应用指向的是开发环境还是生产环境”。
vitalshost.<name>.vitals — 磁盘、内存、CPU 和负载,支持 macOS/Linux/Windows读数是 DEBUGmetric 事件(始终可用于绘图,但不会出现在默认 INFO 视图中);阈值越界会触发边沿触发的 WARN/ERROR,因此达到 85% 时只报告一次,而不是每次轮询都报告,恢复时也会报告。只读文件系统会跳过:macOS 模拟器运行时按设计会达到 98% 占用,而这条规则出现之前,对它的告警曾导致 25 次误报 ERROR。
buildbuild.<host>.<label> — 每条诊断生成一个事件,并给出一个判定编译器错误为带有file:line的 ERROR;构建退出码为 0,却报告了错误时为 WARN,而非成功,因为这通常意味着本该使用 && 的地方写成了 ;——而构建报告有错误却声称一切正常,正是损坏产物得以发布的原因。
watchfs.<host>.<dir> — 创建文件(INFO)、修改文件(INFO)、删除文件(WARN);短时间内的大量变更会合并为一条摘要;使用 --diff 时,将变更的行按差异块作为独立事件发布差异块会同时包含删除行和新增行,同一次保存的每个差异块都带有相同的trace,并与作为锚点的“已修改”事件关联——/recent?trace= 会将整个编辑过程作为一个完整故事返回。按内容哈希实现幂等:编辑器仅更新时间戳,或以相同字节原子性地重新保存,都不会发布任何内容。差异会锚定在两侧都唯一的行上,因此移动一个大括号不会让一行配置编辑的差异扩散到整个文件;二进制文件以及超过 --diff-max 的文件仅按哈希跟踪,并会注明(undiffable),绝不会假装能比较。
powerpower.<host> — CPU 封装功耗(瓦)、热压力、风扇 RPM、CPU/GPU 芯片温度、汇总 CPU 使用率(单个数值),以及附在每个样本上的主要能耗进程;仅限 macOS该功能的由来是,这台机器曾达到1258% 的汇总 CPU 使用率——11 个核心满载,罪魁祸首是一个 VS Code 扩展——却一直无人察觉,直到风扇开始狂转、kernel_task 开始降频;它还曾因功耗失控而崩溃。“过高”取决于具体机器,因此提供 3 种检测方式:你设置绝对功耗上限、持续功耗高于机器自身学得的基线,以及机器自身的判定(热压力/CPU 速度限制),完全无需调优。读取功耗需要 root——sudo scripts/install-power-tailer.sh 只会授权执行一个固定的 powermetrics 调用,不会授权其他操作——即使没有 root,它仍会发布热状态、汇总 CPU 使用率和主要进程,并将每条读数标记为 power_unavailable: not root。macOS 上的演示会无条件启动此功能。
dldl.<host>.<label> — 正在进行的下载:以 metric 事件报告百分比、字节数和速率,并在下载结束时给出一条判定
otlpotlp.<service> — OpenTelemetry 接入端:任何经过 OTel 插桩的内容都能加入工作台,无需新的 SDK,只需添加一个 exporter 配置段。127.0.0.1:4318 上的 OTLP/HTTP 服务(标准端口,因此 exporter 无需配置 endpoint)可接收 OTLP/JSON 和 OTLP/protobuf 格式的 /v1/logs、/v1/metrics 和 /v1/traces(使用手写的有界解码器——大多数 SDK 默认使用 protobuf)。severityNumber 映射为工作台级别,service.name 成为主题,trace_id 成为事件的trace——因此,OTel span 和 withTrace() 日志行会共享同一个 id,/recent?trace= 会同时返回两者。Metrics 会成为 metric 读数(汇总直方图,绝不虚构百分位数);span 则成为携带 trace 的 DEBUG 事件。为已使用 OpenTelemetry 的团队提供桥接:现有插桩会与其他所有内容一同显示在工作台上,其 trace id 也会与工作台自身的 trace id 对应——无需第二个 SDK,也无需重新插桩。
starlinkstarlink.dishy — dish 本身,通过其 LAN gRPC API 获取(grpcurl + server reflection 负责传输):以 metric 读数报告流行延迟、丢包率、上/下行吞吐量、遮挡比例和 GPS 卫星数;每个告警标志(热节流、电机卡住等)、遮挡、固件更新、重启和不可达状态都会作为边沿触发事件报告,并包含恢复信息。与 netstate 的网关监控配合使用,可清楚区分从旋转加载的浏览器看来完全相同的 3 种故障:dish 宕机、路由器宕机,或只是网络路径性能下降。缺失的读数会保持缺失——dish 没有报告丢包率,不代表它报告了零丢包。
rpcrpc.<chain> — RPC 节点端点健康状况:以 metric 读数报告每个 provider 的区块高度和延迟,并报告会悄无声息地破坏链集成的 2 种故障——DOWN(无响应,连续两次未命中后为 ERROR)和 STALLED(仍有响应,但区块高度停滞——为 WARN,朴素的健康检查会漏掉这种故障)。支持 4 种方言——EVM、Solana、Tron、Bitcoin(Esplora)。每条链运行多个 provider(例如同时使用 QuickNode和Alchemy),这样其中一个宕机时不会连带影响你——但前提是你能看出哪个宕机了。两个查看器都带有 RPC 面板:每个端点一行,列出链、provider、url、区块、延迟、最后可见时间和健康状况。
prspr.<owner>.<repo> — pull request,监控那个会悄无声息地毁掉它们的状态:等待我们处理。包括你配置的 GitHub 登录名在任何地方创建的每个未关闭 PR(也包括你在他人仓库中的 PR)、你维护的仓库中的未关闭 PR,以及你最近关闭的 PR。由谁采取下一步行动取自记录中的最后一条非机器人消息;days_waiting 是可绘图的 metric。超过 --warn-days(默认 3 天)时产生一条 WARN,达到其 3 倍时产生一条 ERROR,有人回复时则视为恢复——每次越过阈值都会触发告警网关(去重键 pr:<repo>#<n>,包含通知渠道)。未合并即关闭为 ERROR,因为从外部看,“因过时而关闭”就是这种情况。两个查看器都带有 PR 面板,按“我们负责的优先、等待时间最长的优先”排序——排序顺序就是待办清单。源于一次真实故障:审查者要求修改,请求 51 天无人回复,随后 PR 因过时而关闭。没有人拒绝它——它只是悄悄过期了。gh 负责传输,并包含身份验证。
usbusb.<host> — 机器的 USB 设备树(macOS 上使用 ioreg 的 USB 层,Linux 上使用 lsusb):通过快照差异生成热插拔 INFO 事件,每次变更都会将整棵树放入 fields.tree。两个查看器都带有一个可实时呈现设备树的设备窗口,并且会将手机作为重点显示——📱 iPhone — connected (mymac),或 UNPLUGGED 40s ago,因为设备缺席必须和设备存在一样显眼。刷新按钮会触发本地 tailer(环回 POST :7338/poll)立即测量;否则,插入设备后最多 5 秒的轮询周期内就会更新。要解决的问题是每个设备开发者都熟悉的:“adb 看不到设备”和“Xcode 丢失了手机”都始于设备到底连上了吗,而回答这个问题不应该需要打开终端。macOS 上的演示会无条件启动此功能。
fixfix.<session> — FIX 会话日志,已解码。 QuickFIX、FIX8 或任何会写入标准消息日志的引擎,都会使用以 SOH 分隔的 tag=value 格式,每行一条消息;此功能按名称跟踪这些日志(tail -F,因此 logrotate 不会悄悄终止数据流)。MsgType(tag 35)表示操作,而级别判定才是关键,因为 FIX 会话故障往往悄无声息:Reject / BusinessMessageReject 为 ERROR,并附带原因(Text 字段)和违规的 RefTagID;ExecutionReport 成交时为 INFO,报告订单被拒时为 ERROR——在所有 8 中,这是绝不能漏掉的那一个;Logout、SequenceReset 或 ResendRequest 为 WARN(会话悄悄停止成交);Heartbeat 为 DEBUG。ClOrdID、Symbol、Side、OrderQty、Price 和 OrdStatus 会作为字段附带;主题为 fix.<begin>-<sender>-<target>,根据消息生成而非根据文件名生成,因此同一日志中的多个会话可以清楚区分。被拒订单或日间注销,只是引擎消息日志中的一行,埋没在心跳消息的海洋里,而此时每一刻都关系到资金——却没人正在跟踪那个文件。容错读取器也接受竖线 / caret-A 分隔符以及前置的本地时间戳,因此复制出来的消息也能解码;不会重新验证校验和,因为引擎已经验证过了。
mavlinkmavlink.<sysid> — 无人机遥测数据,已解码。 ArduPilot 和 PX4 使用 MAVLink,而那些会导致飞行终止的故障——电池电量低于储备值、GPS 定位丢失、EKF 失效保护、链路静默——都在这段二进制数据流中,其他地方不会出现;地面站只会显示一秒,下一帧滚动过来后它们就消失了。此功能绑定 GCS/SITL 转发的 UDP 数据流(或重放 .tlog),解码 v1 和 v2 帧并进行 CRC 校验(仅仅看起来像帧起始符的字节不会被误认为幽灵消息),同时遵循工作台的规范:电池和卫星数是 DEBUG metric 读数;低电量(--batt-warn/--batt-crit %)、3D 定位丢失、EMERGENCY 系统状态和心跳停止的链路会触发边沿触发的 WARN/ERROR,并报告恢复;模式和解锁状态变更为 INFO;飞控的原始 STATUSTEXT 会按其 MAV_SEVERITY 级别报告(EKF failsafe 会成为 ERROR,因为自动驾驶仪如此判定)。无需配置——MAVLink 可自我描述。这是机器人工作台对“设备到底连上了吗”的回答:无人机健康状况存在于日志工具都无法读取的协议中,因此永远不会显示在汇集其他所有信息的那块屏幕上。测试时没有使用无人机——而是使用手工编码的真实 CRC 帧——因此解码器经过了验证,而非仅凭希望。
netstatenet.<host>.state — 网络自身的状态,经过差异比较:接口地址、默认网关、Wi-Fi SSID、VPN 隧道、DNS 解析器集合,以及经过判定的 ARP 邻域——首次发现新的 LAN 设备时产生一条 INFO,缓存过期时不产生事件,网关 MAC 变更时为 ERROR,因为这可能是路由器被替换或遭遇 ARP 欺骗式中间人攻击。--ping 目标(网关免费纳入)会以 metric 读数发布 RTT 和丢包率,并在网络状态边沿变化时触发告警——而在目标越过状态边沿的瞬间,会运行一次 traceroute,并与告警放在同一条trace中:告警会附带自身诊断信息。开发工作台上,“一切都坏了”的情况有一半是网络在开发者身下发生了变化——VPN 断开、DHCP 重新分配地址、强制门户更换了解析器——但这些变化无处可见。此工作台曾因路由器悄悄过滤隧道的 DNS 名称而错失一次现场调试机会;解析器变更事件本可立即标记出问题。macOS 上的演示会无条件启动此功能。
topologynet.<host>.topology(以及 net.<host>.route.<target>)——以树形呈现本地网络,并在独立查看器窗口中显示:此主机及其地址、网关和下属设备,呈现方式类似 USB 设备树。ARP 表只包含此机器曾通信过的设备,因此 --discover 会对 /24 网段执行 ping 扫描,以显示其余设备(需选择启用——会向每台主机发送一个数据包)。使用 --to <target> 指定服务器(或使用 --ssh 从客户端发起),即可持续监控路由:每一跳的 RTT 是 DEBUG 读数,但目标变为不可达时会触发边沿触发的 ERROR,路径长度持续变化(即路由重选)时为 WARN;中间路径的波动只绘图,不触发告警——因为 ECMP 会让逐跳差异比较误报。只要能解析,地址就会附带反向 DNS 名称;--geo 会为公共网络跃点标注其 AS/所属网络(以及注册机构确实掌握时的国家/地区)。“问题在他们、在我们,还是在路径上”有一半无法回答,而平面日志也无法呈现网络的结构。默认安全:读取本地网络并向本地 hub 报告;唯一会访问机器外部的查询(--geo 查询 RIPE、--discover 扫描 LAN)都需选择启用。它报告的是路径可用,而非服务健康——即使路由畅通,gRPC 数据流仍可能因资源不足而停滞,此时其他一切仍显示绿色;可达并不等于已验证。
versionshost.<name>.versions — 工作台中所有版本,均经过差异比较:操作系统和内核、编译器和工具链、语言运行时及其当前启用的版本管理器(nvm/pyenv,版本漂移往往藏在这里)、数据库,以及 CPU/GPU。基线状态保持静默;变更本身就是事件——主版本升级为 WARN(主版本容易造成破坏性变更),工具消失为 WARN,新工具为 INFO,缺失的工具只报告一次,不会反复提醒。此功能只报告,不提供建议——只会说“openssl 在周二发生变更”,不会说“这很重要”,因为判断需要维护知识库(这是付费层,因此这里不会为了向你销售东西而故意阉割功能)。每条事实都会说明其获取方式;无法排序的版本会标记为 unorderable,而不是猜测。选择启用 --check-conflicts 后会下载已知问题列表并在本地检查——你的版本绝不会离开本机(版本列表相当于 CVE 路线图)。“昨天还好好的”有一半归结于一个问题——什么变了——但工作台上没有任何东西能回答它:gcc 13.1→13.2 开始产生错误编译,postgres 15→16 改变了查询计划,版本管理器悄悄替你换了 node,但这些情况都不会自行报告。最难察觉的版本变化发生在硬件上——一个团队因 FPGA 运行着工具链已无法生成的比特流而损失了 3 天,因为已安装和已部署之间出现了偏差,却没有任何提示。
sqlsql.<name> — 工作台上的 SQL,支持两种引擎。Postgres:tailer 会保持 LISTEN 连接打开,因此任何触发器、存储过程或批处理作业都能用一条内置语句写入日志——NOTIFY superlog, '{"level":"ERROR","msg":"reconciliation drift"}'——无需扩展,也不需要超出 NOTIFY 的权限;JSON payload 会携带 level/msg/fields,纯文本则原样作为 INFO 接收。SQLite:从进程外监控数据库文件——读取文件头自身的变更计数器、页数和 -wal 增长情况,且不会加锁——因为进程内代码已经有语言 SDK,缺少的是外部观察者。两种引擎都会轮询 --query "name=SELECT …",并将结果作为 metric 读数发布;查询失败时只产生一条 WARN,直到恢复。批处理作业和触发器正是那些凌晨 3 点会悄无声息失败的代码;NOTIFY 桥接只需一条语句就能让它们发出信号。传递延迟上限由心跳间隔决定(默认 1s),没有监听器时不会排队保存通知——tailer 会用 WARN/RECOVERED 标记任意中断,因此缺席绝不会被误认为健康。为数百 GB 的Hugging Face时代打造:tqdm 进度条使用 \r 重写,在管道中会自动静音,并会在每个分片开始时重置;因此除了读取进度条之外,它还能用 --watch 监控目标文件本身,而没有任何工具能将这一监控静音。停滞——30 秒没有进展——会触发边沿触发的 WARN,远在下载任务自身失去耐心之前就会发出警告,恢复时也会报告。它是一个像 build 一样透明的包装器:输出、stdin 和退出状态都保持不变。
gitgit.<host>.<repo> — 时间线上的检出记录:提交(主题 + 简短统计)、分支切换、重写历史、标签和合并冲突。superlog-git 监视仓库(静默建立基线,之后仅报告变更);superlog git install-hooks 会添加一个 提交后钩子,在每次提交落地的瞬间为其盖上时间戳;superlog git recall <commit> 会重放工作台在 该提交与下一个提交之间收集的每条日志——运行的构建、测试、发生波动的服务,并关联到引发这些情况的变更。提交是 INFO(其主题就是消息);重写历史是 WARN——变基、修订提交或强制推送,在自己的分支上不足为奇,在共享分支上却是当天最糟糕的消息,而且监视器无法判断是哪一种;合并冲突是 WARN,解决后会恢复;消失的检出项是 ERROR,只报告一次。recall 读取本地日志(跨封存片段和其他主机的持久回溯功能请使用 Cloud 控制台)。

dns 会查询一个指定的解析器(默认是 1.1.1.1),因此变更表示记录发生了变化,而不是笔记本电脑切换了网络,命中了不同的缓存。 build 是透明的:它会打印输出,并以构建自身的状态码退出,因此可以原样放进 Makefile 或 CI 步骤中。

dl 像 build 包装编译器那样包装下载——输出、标准输入和退出状态均保持不变——它适用于如今人人都会进行的下载:从 Hugging Face 分片下载大型模型或数据集,耗时数小时。

npm run dl -- -- curl -LO https://huggingface.co/Qwen/Qwen2.5-7B/resolve/main/model-00001-of-00004.safetensors
npm run dl -- --watch ~/.cache/huggingface --size 140GB -- hf download meta-llama/Llama-3.1-70B
npm run dl -- --watch /data/corpus --size 100GB     # a fetch some other process owns

第一种形式读取工具自身的进度条(tqdm/hf、curl 的进度表、wget,或任何裸露的 NN%)。第二种形式适用于大型多分片下载:hf download 会为每个分片运行一个 tqdm 进度条,且每个进度条都会重置为 0%,因此 --watch 会在每个刻度直接测量目标目录——可识别符号链接,因此 Hugging Face 缓存中的 blob 和快照链接会让每个字节只计数一次——而 --size 会据此计算真正的总体百分比。第三种形式无需命令:它会跟踪由其他进程负责的下载,并在达到指定大小时退出。

整个服务器群

每台服务器有 8 个容器,就有 30 个日志跟踪器,而且没人会重复运行 30 条命令。只需描述一次(fleet.json.example):

{ "url": "http://127.0.0.1:7333",
  "hosts": [
    { "ssh": "web1", "name": "web1", "os": true, "apps": ["nginx"] },
    { "ssh": "[email protected]", "name": "api", "os": true,
      "identity": "~/.ssh/id_ed25519",
      "docker": ["api", "worker"], "files": ["/srv/app/log/production.log"] }
  ] }

npm run fleet -- fleet.json 会启动每条流,并重启任何已停止的流。 name 是主题名称,因此凌晨 3 点时,os.api 比 os.ubuntu-4gb-nbg1-1 更易读。所有内容都通过 ssh 拉取——无需 Agent、无需开放端口,也无需从生产环境连接到中枢的路由。

多个工作台:superlog-bridge

Hub 会在 /ws 上重新广播收到的所有内容,因此多个 hub 可以组合使用:

npm run bridge -- --ssh otherbench

通过 ssh 隧道订阅另一台机器的回环 hub,并将它的整个数据流原样重新接收到这里——主题相同、字节相同,因此下游无法分辨桥接的数据流和本地数据流。两个 hub 都不会监听网络。只能单向传输:两个 hub 互相桥接会形成反馈循环,因此请选择一个工作台作为主工作台。

可以关闭重新广播。 可组合是默认行为,而非强制要求:hub 会在 /ws 上重新广播,因为这是实现可组合性的方式;只需设置一个环境变量即可恢复原状。SUPER_LOG_NO_EGRESS 用于指定 hub 接受但不向任何对象提供的主题——既不会出现在 /ws 上,也不会出现在 /recent 中——因此数据流可以存在于工作台上,但绝不会离开这台机器:

SUPER_LOG_NO_EGRESS='secrets.*,vault.*' superlogd   # the scalpel: these topics only
SUPER_LOG_NO_EGRESS='*'                  superlogd   # the fire alarm: rebroadcast nothing

模式可以是精确主题、prefix.* 通配符,或表示整个 hub 的单独 *;启动时,hub 会在控制台说明哪些主题已被屏蔽。这是一项采用 MIT 许可且代码可供审查的安全控制——Agent、桥接器、日志记录器以及任何 /ws 订阅者都无法收到被屏蔽主题的内容;而且由于被屏蔽的主题永远不会进入重新广播环形缓冲区,重新连接的订阅者也无法重放这些内容。有一点需要如实说明:目前“不向任何对象提供”就是全部承诺——被屏蔽的主题不会在本地记录到日志,因为 super-log 自带的日志记录器本身就是一个 /ws 订阅者。后续计划加入 hub 内部日志记录;在此之前,被屏蔽的主题会被丢弃,而非持久化。

历史记录

npm run journal                                   # capture everything, rotated
npm run search -- --since 3d --level ERROR --topic node.
npm run search -- --trace 9f1c0a2b7d4e5f60        # one action, days later
npm run replay -- --dir superlog-journal --speed 1

时间窗口按到达 hub 的时间筛选,而不是按生产者的时钟筛选:到达时间是单调递增的,因此时间窗口精确无误,扫描也可以提前停止。

模式与策略

每个 SDK 都会强制要求 DEVELOPMENT 与 PRODUCTION 二选一——两者都未启用或同时启用都会导致编译错误(C++ 和 Fortran 定义、Rust 功能),或抛出错误(JS、Python、Go、Java、Swift)。每种模式随后只会发送其策略允许的内容:开发模式发送所有内容,生产模式发送任何内容都不发送。想从发布版本中收集崩溃排查信息?请明确指定——使用 -DSUPERLOG_PROD_POLICY=ERROR、prod_policy: Policy::AtLeast(Level::Error) 或 productionPolicy: 'ERROR'。低于策略级别的事件只需进行一次比较;OFF 策略会将传输编译为无操作外壳,并在控制台提示一次,因为客户端静默无操作,看起来就像出了故障。

生产机器上发出的日志行涉及安全决策,因此这里不会替你做决定。

安全态势

完整模型——MIT 工具的威胁模型,以及它与 super-log Cloud 的区别——请参阅 docs/SECURITY_ARCHITECTURE.md。 简而言之:

没有身份验证,也没有 TLS:任何能够访问该端口的人都可以读取所有数据流,并向任何主题发布内容。默认设置旨在确保是否暴露由你选择,而非意外发生:

  • Hub 仅绑定到回环地址,无论是否处于演示模式;当真实设备确实需要访问时,可设置 SUPER_LOG_LAN=1 或 SUPER_LOG_BIND=0.0.0.0,在可信网络中开放访问。
  • ssh 跟踪器和设备集群运行器都采用拉取方式,因此生产日志可以传到工作台,而生产环境始终不会连接 hub。
  • 除非你放宽策略,否则生产构建不会转发任何内容。
  • 可以在 hub 上屏蔽某个主题的出口。 SUPER_LOG_NO_EGRESS='secrets.*'(或屏蔽整个 hub 的 *)会让 hub 接受数据流,但不向任何对象提供——没有 /ws,没有 /recent,也无法重放——因此数据会留在这台机器上。被屏蔽的主题会被丢弃,而非记录到日志;请参阅上文的多个工作台。
  • 凭据会被遮盖,而不会记录到日志:代理中的 Authorization、Cookie 和 X-API-Key 标头,URL 中形似令牌的查询参数值,以及 RPC 端点中的服务商密钥。
  • .env 已添加到 Git 忽略列表——含密钥的 RPC URL 是可以直接使用的凭据。
  • CSV 导出会防止电子表格公式注入;查看器会将日志内容渲染为文本,而非标记语言。
SaaS 和托管服务

同样的思路,适用于完全无法附加调试器的系统部分。这两种方式都驱动供应商自己的 CLI,因此无需在服务中安装任何东西,也无需托管 Webhook。

Cloudflare Workers

npm run cf -- --worker my-api            # live, from now on
npm run cf -- --worker my-api --status error

发布到 cf.<worker>。一次调用会变成共享同一条跟踪记录的多个事件——请求、处理程序写入的每一行 console 输出,以及任何异常——因此 /recent?trace=… 会返回一次调用的完整过程。级别来自 Worker,而非猜测:console.error 对应 ERROR,无论处理程序是否这样标记,500 都是错误;结果不是 ok 也属于错误,即使没有任何日志——exceededCpu 会悄无声息地终止隔离区,而这正是你在内部无法看到的故障。CPU 时间和实际耗时会作为 DEBUG 指标传入。

它使用 wrangler 自带的登录方式,因此无需 API 令牌。它仅支持实时数据:wrangler tail 无法读取过去的数据,而 --since 会说明原因并拒绝执行,不会悄悄从现在开始跟踪,让你误以为看到的是一小时前的数据。

Stripe

npm run stripe                                   # test mode, default account
npm run stripe -- --live                         # real money
npm run stripe -- --live --account acme --account beta

发布到 stripe.<account>.<mode>,每个账户各有一个进程和一个主题,因此繁忙账户不会拖慢安静账户。级别取决于事件的含义:争议属于 CRITICAL,因为款项已损失且有截止期限;付款失败属于 ERROR;退款或已取消的订阅属于 WARN。金额会作为 stripe.amount 指标传入。

通过允许列表进行脱敏,这正是重点。 payment_intent.payment_failed 会包含客户的电子邮箱、姓名、电话、完整账单地址、卡品牌、卡号末四位和指纹。这是客户记录,不是日志数据,而且此中心没有身份验证。只有明确列出的字段会离开进程——如果使用阻止列表,Stripe 一添加字段,数据就会开始泄露。保留下来的内容才是实际调试所需的:拒付代码、失败消息、金额和客户 id。--unsafe-full 会关闭此功能,并在关闭前发出警告。

设置使用 Stripe CLI 自带的方式:

stripe login                        # the default account
stripe login --project-name acme    # a second account, then --account acme

stripe login 会授予测试模式密钥;--live 则需要获得相应授权的账户。CLI 密钥也会过期——如果几个月后数据流变得静默,请先重新运行 stripe login,再怀疑跟踪器。

为什么中心端没有身份验证

(superlog login 是通往可选 Cloud 的浏览器入口——它不会为本地中心端添加身份验证,也不会自行发起网络请求。这里讨论的是中心端本身。)

这里的目标刻意保持适度且明确:安全性不低于开发者已有的日志,也绝不比它们更危险。

常见的日志都是本地文件,例如 /var/log 和 ~/Library/Logs、adb logcat、Metro 和 Xcode 控制台、journalctl。它们全都仅限本地,由操作系统强制保护,也都无法从另一台机器写入。绑定到 loopback 时,这里也是同样的情况:操作系统就是身份验证机制,负责处理日志文件的也是同一个操作系统,做的也是它原本就在做的事。在此基础上再加密码,并不能保护任何原本未受保护的内容。

因此,对于“难道不应该有身份验证吗?”这个问题,诚实的答案是:对于实际使用此工具的场景,身份验证只是做做样子。重要的不是添加登录,而是不能在不知不觉中变得可被访问。

由此得出两点,这就是全部策略:

默认设置就是安全的设置。 过去并非如此。中心端曾经绑定到 0.0.0.0,而本文件却声称暴露是“有意选择,而非意外”——这对演示脚本来说没错,对这些说明让你运行的二进制程序来说却不成立。代码没有兑现的安全承诺,比两者单独存在都糟糕。现在它会绑定到 loopback,并在启动时明确说明;如果没有绑定到 loopback,也会发出更醒目的提示。

令牌无法解决人们以为它能解决的问题。 没有 TLS 时,每次请求都会以明文在网络上传输共享密钥,因此任何能嗅探该网络的人只需截获一次请求就能拿到密钥,并一直持有它。它能阻止意外访问,却挡不住攻击者——同时还会引入真正的新泄漏,因为浏览器无法在 WebSocket 上设置请求头,查看器的令牌只能放在 URL 中,进而进入浏览器历史记录、Referer 和每个被粘贴的链接。与操作系统在更底层已经妥善处理的事情相比,这种交换并不划算。

设备:无需开放端口

真正的缺口是手机推送日志,因为手机无法访问 Mac 上的 loopback。请使用 USB,而非网络:

adb reverse tcp:7333 tcp:7333          # Android - the phone's localhost is yours
iproxy 7333 7333                       # iOS, via libimobiledevice

这样就能在各处与常见日志的安全性保持一致,不需要新增代码,也不需要开放端口。它也严格优于绑定到局域网并使用令牌的做法,而且更简单。

容器和其他机器

在 macOS 上使用 Docker 无需任何配置。 容器访问 host.docker.internal 时会到达主机的环回接口,因此仓库自带的 Ubuntu 生产端可以继续连接到绑定环回接口的 hub——这一点已经过验证, 并非假设。反直觉之处在于,--network host 在 Docker Desktop 上 不起作用:“host” 在那里指 Linux 虚拟机,因此容器中的 127.0.0.1 是虚拟机的地址,而不是 Mac 的地址。听起来更宽松的那个标志反而会 失败。

extra_hosts: ["host.docker.internal:host-gateway"]   # what the compose file does

在 Linux 上使用 Docker 则恰好相反:--network host 会共享主机的 网络命名空间,因此容器中的 127.0.0.1 确实是主机的环回接口,可以 直接访问绑定环回接口的 hub。

无论哪种情况,容器中都不会运行 GUI——因此无需挂载 X11 套接字,也无需配置 Wayland 或 waypipe 转发。只有 HTTP 会跨越容器 边界;查看器会在你所在的位置原生运行。Mac 上 Docker 中的 Linux 工作负载由原生 macOS 查看器可视化,这正是 hub/查看器分离的设计初衷。

Raspberry Pi 或任何其他机器,又会遇到手机的问题——它无法访问你的 环回接口。这里有两种解决方案,而且都不会开放端口:

# 1. Pull. Nothing runs on the Pi, nothing is installed, no port is opened.
npm run tail:ssh -- pi4                     # its OS logs
npm run gpu -- --ssh pi4                    # its GPU, temperature and throttling
npm run build -- --ssh pi4 -- make          # a build on it

# 2. Push, through the ssh connection you already have, if an SDK runs there.
ssh -R 7333:127.0.0.1:7333 pi4              # then SUPER_LOG_URL=http://127.0.0.1:7333

拉取模式是更好的默认方案,也是支持设备群的原因:日志通过 ssh 传送 到 工作台,因此被监控的机器无需访问 hub,hub 也无需对外可访问。反向 隧道适用于该机器上的代码想直接使用 SDK 的情况——它通过你已经信任的 ssh 会话传输流量,并且已在真实服务上验证可用。

如果你必须对外开放

如果设备确实必须通过网络访问它,就在该放置允许列表的地方设置允许列表—— 防火墙,而不是应用程序。在进程内进行 IP 过滤,就是在糟糕地重写 pf 或 nftables,而且会被同一个攻击者以完全相同的方式攻破。

# macOS, /etc/pf.conf - only this handset may reach the bench
block in proto tcp to any port 7333
pass in proto tcp from 192.168.1.20 to any port 7333

# Linux, and you are probably already running it
ufw allow from 192.168.1.20 to any port 7333
ufw deny 7333

接着,按工作台的本来面目来对待它:一个开发工具,保存着你的机器所记录的 内容。如果其中包含生产环境访问日志、ssh 身份验证失败记录,或任何涉及 客户的内容,那么写入端和读取端同样重要——没人能从网络另一端伪造内容写入 /var/log,而开放的 hub 正是唯一会让这一点不再成立的地方。

请参阅 docs/ARCHITECTURE.md 中的 Auth/TLS 部分。

要求

  • **Hub + 查看器:**macOS 或 Linux(第一阶段支持 POSIX)、C++17 编译器、 CMake ≥ 3.16。Windows 机器作为生产端加入(SDK、ssh tailer)。 **纯 C SDK:**任何支持 POSIX 套接字的 C99 编译器。
  • **显示,在 Linux 上:**原生查看器通过 GLFW 绘制;此处构建的是 X11 版本——Wayland 桌面也会通过 XWayland 运行 X11,因此无需改动即可正常工作。原生 Wayland 输出是 GLFW 的一个构建开关 (-DGLFW_BUILD_WAYLAND=ON,并安装 wayland/xkbcommon 开发包),无需修改代码。本基准环境没有 Linux 桌面,因此首次运行任一版本都应视为环境调通;Web 查看器只需要浏览器,不受合成器影响。
  • **JS:**Node ≥ 18(journal、chain watcher 和 MCP 需要 ≥ 22)。 Ruby:≥ 3.0,仅标准库(Rails 通过 ::Logger 适配器运行)。 OCaml:≥ 4.14(ocamlc -I +unix unix.cma)。**Haskell:**GHC ≥ 9, boot 库加上 curl。Scala:≥ 3,基于 Java SDK 运行。 **Lean:**通过 elan/lake 使用 4.x,核心 IO 加上 curl。 Perl:≥ 5.14,仅核心模块(HTTP::Tiny、JSON::PP)。 **Lua:**任意 5.1+ 版本(PUC 或 LuaJIT)加上 curl。**COBOL:**GnuCOBOL 3 和 C 编译器,通过一个 20 行的 shim 基于 C SDK 运行。 C#:.NET ≥ 8(RollForward 可在更新版本上运行),仅使用 BCL。 **Zig:**近期版本的 Zig(已在 0.16 上验证)和 C 编译器,通过 @cImport 基于 C SDK 运行。SQL:psql 或 sqlite3 CLI。 **Rust:**任意近期的 stable 版本。Python:≥ 3.8,仅标准库。 Go:≥ 1.21(log/slog)。Java:≥ 17,直接使用 javac,无需构建工具。 Swift:≥ 5.9,使用 SwiftPM。**Fortran:**gfortran 或任何支持 -cpp 的编译器。Shell:sh 和 curl,不需要其他工具。
  • 在 macOS 上,Go 1.21 的内部链接器会省略 LC_UUID,当前的 dyld 会因此拒绝加载;请使用 -ldflags=-linkmode=external 构建,或使用 Go ≥ 1.22。
  • ts-moveables 提供传输、扇出和日志记录基础设施。它不是子模块:如果存在同级目录中的 ../TSMoveables 检出,CMake 就会使用它;否则会获取一个固定的 SHA。配置过程会打印它选择了哪一种,因为悄悄使用某人的工作副本来构建,正是“在我的机器上能运行”这种情况的由来。
  • spdlog + fmt、imgui + glfw 和 nlohmann/json 是固定版本的子模块,位于 third_party/ 中(git submodule update --init)——这些版本经过搭配测试,因此绝不会信任系统安装的版本。
  • Docker(可选),用于 Ubuntu 构建和冒烟测试镜像。

目录结构

路径内容
hub/superlogd——所有内容汇聚的单一进程
scripts/build_all.sh(重新构建所有内容)、setup.sh(将 super-log 添加到项目)、smoke.sh、verify-sdks.sh、dev.sh
tests/106 项测试:通过子进程运行工具,并针对真实 hub 进行测试
sdk/cpp/仅包含头文件:转发 sink、spdlog sink、终止处理程序
sdk/c/superlog.h:一个 stb 风格的头文件、零分配、POSIX 套接字;生产环境编译为空
sdk/rust/super-log crate:核心、tracing 层、panic 钩子
sdk/python/superlog:客户端、logging 处理程序、excepthook、本地变量捕获
sdk/go/superlog:客户端、log/slog 处理程序、panic 恢复
sdk/java/SuperLog:客户端、java.util.logging 桥接、Kotlin 说明
sdk/swift/SuperLog:客户端、@TaskLocal 跟踪
sdk/fortran/superlog.F90:通过原始 POSIX 套接字连接的客户端
sdk/ocaml/superlog.ml:通过 Unix 套接字连接的客户端,模式由 SUPERLOG_MODE 指定
sdk/lean/Superlog.lean:Lean 4 客户端、核心 IO + curl,模式由 SUPERLOG_MODE 指定
sdk/haskell/SuperLog.hs:GHC 引导库 + curl,模式通过 -DDEVELOPMENT 编译进程序
sdk/ruby/superlog.rb:标准库客户端 + Rails 接入的 ::Logger 适配器
sdk/js/@super-log/client、@super-log/react、@super-log/mcp
tailers/adb、simctl、OS 日志、文件、服务、docker、ssh、fleet、chain、journal、搜索、回放、网络代理、电源、下载、hub 桥接
viewer/imgui/原生查看器
viewer/react/Web 查看器
demo/多客户端时钟演示:一条命令,运行整个基准测试
docker/Ubuntu 构建与冒烟测试镜像,以及 Linux 基准测试数据生成器
third_party/固定版本的子模块
docs/PROTOCOL.md(协议约定)、ARCHITECTURE.md(架构概览)

如实说明状态

大部分内容都已在真实测试环境中现场验证,仅完成编写的部分也都已明确标注。已验证:Hub、两种 C++ 路径、Rust、在真实 Expo 应用中通过模拟器和硬件测试的 JS 客户端,以及真实浏览器和 Node 环境、两个查看器、macOS 和 journald 日志跟踪器、针对实体手机的 adb 日志跟踪器、从云主机进行的 ssh 流式传输、由四台服务组成的集群、针对 Ethereum 主网的链监视器、针对 1 GB 日志的搜索和回放、跨层级关联、全部四个 SDK 中的错误钩子,以及 Docker 镜像(会在 docker build 期间自行进行冒烟测试)。

此后也已验证:针对实时 Hub 测试的 Go、Java、Swift、Fortran 和 shell SDK;针对真实 ASan/TSan/UBSan 和 valgrind 输出的 sanitizer 与 valgrind 捕获;git 和 GitHub 监视器(后者捕获了刚推送的真实提交);针对真实 ROS 2 Jazzy /rosout 的 ROS 日志跟踪器;针对真实 syslog 数据报的套接字入口;针对 pty 的串口日志跟踪器;以及一次持续 20 分钟的 Binance 压力测试,它发现 Hub 的回放环形缓冲区为一个主题保留了 66 MB——leaks(1) 确认没有泄漏,环形缓冲区按块数而非字节数设定上限,现在在相同负载下峰值为 30 MB。

之后还验证了:在两台真实 Mac(一台 Mac Pro 和一台 Ventura iMac)上以完整 root 模式运行的电源日志跟踪器,其功率读数经手动运行的 powermetrics 交叉核对;Hub 桥接器通过 ssh 将另一台机器上的 loopback Hub 逐字节转发到此测试环境;以及 superlog-dl,既测试了实时 curl 传输,也测试了真实的 100 GB Hugging Face 数据集下载,并在旁边的机器上通宵观察。

所有 CI 作业都一次性通过,值得准确说明这一点,因为它验证了此测试环境无法验证的内容。它会在 Linux 上使用 gcc 和 clang 构建干净检出的代码,也会在 macOS arm64 上构建(此测试环境为 x86_64);在 macOS 的 TSan 存在运行时级别故障的情况下,于 Linux 上运行 ThreadSanitizer;在完全没有子模块的情况下构建 Hub;在 Alpine 中使用 busybox ash、busybox awk 和非 GNU date 运行 POSIX shell 生产者;并运行 verify-sdks.sh——因此,每个 SDK 都已证明能在非开发它的机器上实际传递事件。

仍然仅完成编写、尚未验证:Windows 事件日志路径(这里没有 Windows 机器)、iOS 硬件日志跟踪器、Kotlin(这里没有 kotlinc)、iOS 上的 Swift、以真实波特率连接真实硬件测试串口日志跟踪器、CUDA 演示(这里没有 NVIDIA GPU,也没有 nvcc——请将首次构建视为初始调试),针对 TLS 和真实客户端库的 gRPC,以及 OpenGL、D3D 和 WebGPU 代码片段。尚未构建:查看器的“加载会话”功能和指标迷你折线图。

CI 配置位于 .github/workflows/ci.yml;scripts/smoke.sh 是 CI、Docker 镜像和你的终端都会以相同方式运行的唯一冒烟测试。

后续方向

这些功能是有意暂未实现的,背后的考量和清单本身同样重要:

  • 面向 Linux/Ubuntu 的 superlog-sys。 macOS 版本会读取 DiagnosticReports、diskutil activity 和 kern.sleeptime;Linux 对应的来源则是用于崩溃和 OOM 杀进程的 coredumpctl 和 journalctl -k、 用于卷的 udev/udisks 事件,以及用于挂起/恢复的 systemd-logind。主题相同(sys.<host>),事件结构也相同,因此 查看器和指南不必关心发生问题的是哪个操作系统。按照项目的规矩,要等 工作台上有一台 Linux 桌面机可供验证。
  • Grafana / Loki / OTLP 转发器。 显而易见的请求是“集成 Grafana”,而答案是转发器,不是集成。让中心服务充当 Prometheus 目标或 Grafana 数据源,会使它依赖自身并不需要的生态系统,也会模糊上文划定的界限—— 中心服务的职责是成为任何东西都能在十秒内指向的对象。 转发器尊重这条界限:它是在数据流上新增一个订阅者,将数据重新发送到 Loki 或 OTLP 收集器,形态与这里所有的日志跟踪器相同,因此工作台仍是工作台, 长期存储仍保持独立。它只是一个小脚本,也是任何想在 Grafana 中查看 昨日日志和工作台上今日日志的人的合适桥梁。
  • FIX 和市场数据二进制协议。 面向会话层和应用层的 superlog-fix (QuickFIX 和 FIX8 日志,或会话监听):心跳和缺口填补记为 DEBUG, 重发请求或序列重置记为 WARN,日间会话注销记为 ERROR——将这里每个监视器都遵循的 同一套边界纪律,应用到这一种沉默代价最高的协议上。其下的交易所二进制行情流—— Nasdaq ITCH(市场数据)和 OUCH(订单录入),以及许多交易场所的同类协议—— 都采用固定布局的二进制格式,因此按照 log4j 模式实现有界解码器(检查长度, 绝不求值内容),便可将捕获的会话或实时监听转成事件:订单簿新增/取消作为 DEBUG 读数,拒绝和中断作为 WARN/ERROR。按照项目的规矩,必须先有真实 会话可供验证——未经验证的市场协议实现,比没有实现更糟。
  • 查看器的“加载会话”功能和指标迷你趋势图——日志本已支持搜索和回放, 但两个查看器都无法直接打开已保存的会话。
  • 为中心服务的回放环设置字节预算,这项工作应放在 ts-moveables 中,而不是这里; 在此功能落地前,SUPER_LOG_REPLAY_CHUNKS 会改为按数量限制环的大小。请参阅 hub/src/main.cpp 中的注释。
  • 将 zrok 作为经过验证的隧道提供商。 网关已通过 Cloudflare 验证(快速隧道、稳定的命名隧道、DNS 配置、端点工厂)和 ngrok 验证(--tunnel ngrok——已在此工作台上验证实时往返:互联网触发的警报发送到公开的 ngrok URL 后抵达中心服务;免费套餐的 URL 每次重启都会变化,付费域名则保持稳定, API 调用方会发送 ngrok-skip-browser-warning 标头)。--tunnel zrok 会解析 zrok 的 输出,应该可以正常工作,但“应该可以”不符合项目标准:需要在工作台上配置 zrok 账户, 并进行同样的实时验证,之后 README 才会作此声明。在此之前,端点工厂的快速隧道 仅支持 Cloudflare。
  • 将 Windows 作为中心服务和查看器的一等主机平台。Windows 计算机已经可以作为数据源使用,事件日志跟踪器也已编写完成,但尚未经验证。

贡献

这是我为自己的工作台开发的工具,后来发现它很有用,所以分享出来。欢迎提交拉取请求——尤其欢迎新增数据流:如果你桌上的设备会输出日志,而这个工具还无法读取,那正是值得填补的空白。

有两件事能让改动更容易被接受。提交 PR 前先用真实的 Hub 运行测试,并在描述中说明你实际观察到了什么——本项目习惯标明哪些内容经过验证、哪些只是写出来,这也是它值得信赖的原因。还要遵守生产者约定:队列有界、丢弃最旧的数据、统计丢弃数量,而且绝不阻塞你正在观察的程序。

即使没有补丁,描述你希望它读取哪种数据流的问题也很有帮助。

如果你运行的服务会生成日志

诚邀参与。 如果你开发的 SaaS 产品会输出开发者用来调试的日志、事件或指标,那么这里就应该有一个用于读取它的工具,而最适合编写它的人就是你——你了解事件的结构、真正重要的严重级别,以及凌晨三点人们需要查看哪些字段。

这里已有适用于 Cloudflare Workers、Stripe、GitHub Actions、Postgres、nginx、Redis、Kafka、Docker、ROS 以及其他几十种服务的读取器。每个读取器都是一个几百行的独立文件,用于启动你的 CLI 或调用你的 API,并将输出转换为事件。没有需要学习的插件 API,也没有需要实现的接口——读一个读取器,你就能了解完整的模式。

优秀的读取器要做到三件事,而这也是审查的全部要点:

级别应符合你产品的实际含义。 而不是照搬事件的名称。Stripe 争议属于 CRITICAL,因为钱已经损失,还面临截止期限;Cloudflare Worker 超出 CPU 限额则属于错误,即使它完全没有记录任何日志。你最清楚哪些事件属于凌晨三点就必须关注的情况。把这点编码进去,开发者的级别筛选器才能真正发挥作用。

按允许列表脱敏,而不是按屏蔽列表。 这个 Hub 没有身份验证,任何能访问它的人都能读取数据。payment_intent.payment_failed 事件包含电子邮件、姓名、电话号码、账单地址和卡号指纹——这是客户记录,不是日志数据。Stripe 读取器会明确列出允许输出的字段,并丢弃其他所有内容,因为只要新增一个字段,屏蔽列表就会在无人察觉的情况下开始泄露信息。如果你的数据流包含个人信息,也请这样处理。

遵守生产者约定。 队列有界、丢弃最旧的数据、统计丢弃数量,而且绝不阻塞或终止正在观察的程序。可能导致被观察对象宕机的日志记录器,根本不该被安装。

如果这听起来符合你的产品,欢迎提交问题或 PR——也可以直接告诉我们 API 是什么,我们会尝试接入。尚未有人想到的数据流,最值得探索。

发布版本

带标签的发布版本会附带说明,标明哪些内容经过验证、哪些尚未验证——CHANGELOG.md。本项目没有软件包注册表:克隆仓库就是获取方式,所以如果你需要稳定的基础版本,请固定到某个标签。

git clone --recurse-submodules --shallow-submodules --branch v0.1.0 \
  https://github.com/saxonnicholls/super-log ~/dev/super-log

许可证

MIT。每个源文件都包含版权声明和 SPDX-License-Identifier: MIT,因此有人复制单个文件时,许可证也会随文件一并保留——对于由独立单文件工具组成的仓库来说,大多数内容实际上都会通过这种方式得到复用。

Copyright 2026 Saxon Herschel Nicholls。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。