一个 Go 编写的 CLI 工具,让你一眼看清 localhost 上所有在监听的服务——进程名、Docker 容器名、Compose 项目、资源占用、可点击 URL,全部一目了然。
安装
# macOS / Linux(推荐)
brew install raskrebs/sonar/sonar
# 安装脚本
curl -sfL https://raw.githubusercontent.com/raskrebs/sonar/main/scripts/install.sh | bash
# Go
go install github.com/raskrebs/sonar@latest
# Windows PowerShell
irm https://raw.githubusercontent.com/raskrebs/sonar/main/scripts/install.ps1 | iex
macOS 用户用 Homebrew 安装还会自动装一个菜单栏 Tray App,实时显示端口状态。
核心功能
一条命令看清全貌
sonar list
PORT PROCESS CONTAINER IMAGE CPORT URL
1780 proxy (traefik:3.0) my-app-proxy-1 traefik:3.0 80 http://localhost:1780
3000 next-server (v16.1.6) http://localhost:3000
5432 db (postgres:17) my-app-db-1 postgres:17 5432 http://localhost:5432
6873 frontend (frontend:latest) my-app-frontend-1 frontend:latest 5173 http://localhost:6873
9700 backend (backend:latest) my-app-backend-1 backend:latest 8000 http://localhost:9700
5 ports (4 docker, 1 user)
这一条命令替代了 lsof、docker ps、netstat 三个命令的联合使用。Docker 容器自动关联容器名和镜像名,Compose 项目自动分组。
默认隐藏桌面应用和系统服务(Figma、Discord、Spotify、AirPlay 等),只显示开发相关的端口。加 -a 显示全部。
带资源统计
sonar list --stats
加上 --stats 后显示每个服务的 CPU、内存、状态和运行时间。Docker 容器通过 Docker Engine API 获取精确的容器级指标。
自定义列
sonar list -c port,process,cpu,mem,uptime,state
可选列:port、process、pid、type、url、cpu、mem、threads、uptime、state、connections、health、latency、container、image、containerport、compose、project、user、bind、ip。
端口详情
sonar info 3000
显示端口 3000 的全部信息:完整命令行、用户、绑定地址、CPU/内存/线程数、运行时间、健康检查结果,以及 Docker 详细信息(如果适用)。
按端口杀进程
sonar kill 3000 # SIGTERM
sonar kill 3000 -f # SIGKILL
Docker 容器用 docker stop 停止(优雅关闭),而不是直接发信号。
批量操作:
sonar kill-all --filter docker # 停止所有 Docker 容器
sonar kill-all --project my-app # 停止某个 Compose 项目
sonar kill-all --filter user -y # 停止所有用户进程(跳过确认)
查看日志
sonar logs 3000
Docker 容器自动跑 docker logs -f。原生进程通过 lsof 发现日志文件并 tail。如果找不到日志文件,回退到 macOS 的 log stream 或 Linux 的 /proc/<pid>/fd。
连接到服务
sonar attach 3000 # Docker 容器自动 shell 进去
sonar attach 3000 --shell bash # 指定 shell
如果不是 Docker 容器,则尝试 TCP 连接。
实时监控
sonar watch # 每 2 秒刷新,显示变化
sonar watch --stats # 实时资源监控(类似 docker stats)
sonar watch -i 500ms # 更快的刷新间隔
sonar watch --notify # 端口上下线时发送桌面通知
--notify 特别有用——后台跑着 sonar watch --notify,当某个服务挂掉或新服务启动时,桌面弹出通知。
依赖关系图
sonar graph
显示服务之间的连接关系——比如后端连接了 PostgreSQL,前端连接了后端。支持 JSON 和 Graphviz DOT 格式输出:
sonar graph --dot > deps.dot
dot -Tpng deps.dot -o deps.png
这个功能帮你理解微服务之间的实际调用关系,不是代码里声明的依赖,而是运行时真正的连接。
Profile:项目端口快照
# 保存当前端口状态
sonar profile create my-app
# 查看已保存的 profile
sonar profile list
# 检查哪些端口在跑
sonar up my-app
# 一键停止所有
sonar down my-app
Profile 让你可以保存和恢复项目的端口状态。比如你的项目需要 PostgreSQL(5432) + Redis(6379) + 后端(8080) + 前端(3000),创建一个 profile 后,sonar up my-app 一键检查哪些服务还没启动,sonar down my-app 一键全部关闭。
等待端口就绪
# 等待 TCP 连接可用
sonar wait 5432
# 等待多个端口
sonar wait 5432 3000 6379
# 等待 HTTP 200(不是 TCP open)
sonar wait 5432 --http
# 检查特定端点
sonar wait 5432 --http=/health
# 超时
sonar wait 5432 --timeout 30s
--http 是关键区别:有些服务(比如 Spring Boot)TCP 端口开放了但应用还没初始化完。--http 等待真正的 HTTP 200-399 响应,确保服务真的准备好了。
CI/CD 脚本中的典型用法:
docker compose up -d
sonar wait 5432 3000 --timeout 60s && npm run migrate && npm run test
比 sleep 10 靠谱得多——端口就绪了立即继续,不用浪费等待时间。
端口映射
sonar map 6873 3002
把 6873 端口的服务映射到 3002 端口。轻量级的端口转发。
查找空闲端口
sonar next # 从 3000 开始找第一个空闲端口
sonar next 8000 # 从 8000 开始
sonar next 3000-3100 # 在范围内找
sonar next -n 3 # 找 3 个连续空闲端口
写脚本时特别有用——不用硬编码端口号了。
远程扫描
sonar list --host user@server
sonar watch --host user@server
通过 SSH 扫描远程机器的端口状态。排查线上问题时不用登上去跑 netstat。
健康检查
sonar list --health
对每个 HTTP 服务发请求检查健康状态。结合 --stats 一起用,可以看到延迟和状态码。
macOS Tray App
如果你用 Homebrew 安装,会自动装一个菜单栏应用:
sonar tray
在菜单栏实时显示端口数量和状态。点击可以展开查看所有端口。比在终端里跑 watch 更方便——不需要保持一个终端窗口。
Tray App 是一个原生 Swift 二进制,不是 Electron。50KB 大小。
跨平台
| 平台 | 实现方式 |
|---|---|
| macOS | lsof |
| Linux | ss |
| Windows | netstat |
三个平台使用不同的系统工具获取端口信息,Go 的跨平台编译让一个代码库支持三个系统。
Go 实现的技术亮点
极致的三依赖
sonar 的 go.mod 里只有 3 个依赖:
github.com/spf13/cobra v1.10.2 # CLI 框架
gopkg.in/yaml.v3 v3.0.1 # YAML 解析(profile 存储)
github.com/spf13/pflag v1.0.9 # 参数解析(cobra 的依赖)
没有 TUI 框架、没有 HTTP 客户端、没有 Docker SDK。所有核心功能都用标准库和系统命令实现:
- 端口发现:直接调用
lsof/ss/netstat,解析输出 - Docker 信息:通过 Docker Engine API 的 Unix Socket 直接发 HTTP 请求(
net/http+net/url) - 日志获取:
lsof发现日志文件 +os/exec调用tail - 端口映射:标准库
net包的 TCP 代理 - 健康检查:
net/http发请求
这种"标准库优先"的做法在 Go 工具类项目中非常典型。依赖越少,编译越快,维护成本越低。
系统命令适配层
跨平台的端口发现通过系统命令适配实现:
// 伪代码
func discoverPorts() ([]Port, error) {
switch runtime.GOOS {
case "darwin":
return parseLsof(exec.Command("lsof", "-iTCP", "-sTCP:LISTEN", "-n", "-P"))
case "linux":
return parseSs(exec.Command("ss", "-tlnp"))
case "windows":
return parseNetstat(exec.Command("netstat", "-ano"))
}
}
每个平台解析不同命令的输出格式。不依赖 /proc 或系统调用,保持实现的简单性。
Docker Engine API 直连
sonar 不使用 Docker SDK(docker/docker 或 docker/client),而是通过 Docker Engine API 的 Unix Socket 直接通信:
// 通过 Unix Socket 连接 Docker Engine API
// 实际实现需要自定义 http.Transport 并设置 Dial 指向 /var/run/docker.sock
// 这里用伪代码展示调用逻辑:
func dockerInspect(containerID string) (*ContainerInfo, error) {
// 伪代码 - 展示 Docker Engine API 的调用方式
// resp, err := http.Get("http://unix/v1.43/containers/" + containerID + "/json")
// Docker API 通过 Unix Socket 暴露在 /var/run/docker.sock
}
这避免了引入 Docker SDK 的庞大依赖链(Docker SDK 本身有几十个传递依赖)。
Profile 存储
Profile 用 YAML 文件存储在本地:
# ~/.config/sonar/profiles/my-app.yaml
name:my-app
ports:
-5432
-6379
-8080
-3000
用 gopkg.in/yaml.v3 解析,简单可靠。
和传统工具对比
| 功能 | sonar | lsof + docker ps | netstat | docker compose |
|---|---|---|---|---|
| 端口列表 | ✅ 含进程名 | 需组合命令 | 仅数字 | 仅容器 |
| Docker 关联 | ✅ 自动 | 需手动关联 | ❌ | ✅ |
| 资源统计 | ✅ --stats | 需 top/htop | ❌ | docker stats |
| 按端口杀进程 | ✅ | 需先查 PID | ❌ | 需容器名 |
| 查看日志 | ✅ | 需手动找 | ❌ | docker logs |
| 依赖关系图 | ✅ | ❌ | ❌ | ❌ |
| 等待就绪 | ✅ HTTP 检测 | ❌ | ❌ | healthcheck |
| Profile 管理 | ✅ | ❌ | ❌ | ❌ |
| 找空闲端口 | ✅ | ❌ | ❌ | ❌ |
| 远程扫描 | ✅ SSH | 需 SSH 登录 | 需登录 | ❌ |
| 桌面通知 | ✅ | ❌ | ❌ | ❌ |
| Tray App | ✅ macOS | ❌ | ❌ | ❌ |
| 跨平台 | macOS/Linux/Win | macOS/Linux | 全平台 | 全平台 |
sonar 的核心价值是把开发者在 localhost 上需要频繁做的端口相关操作,统一到一个命令。它不是替代 lsof 或 docker,而是在它们之上提供一个更人性化的抽象层。
实战场景
场景 1:开发环境启动脚本
#!/bin/bash
# dev.sh — 一键启动开发环境
# 启动依赖服务
docker compose up -d
# 等待所有服务就绪
sonar wait 5432 --http=/health --timeout 30s
sonar wait 6379 --timeout 10s
sonar wait 9000 --http --timeout 30s
# 运行数据库迁移
npm run migrate
# 启动开发服务器(自动选端口)
PORT=$(sonar next 3000)
PORT=$PORT npm run dev
echo"开发服务器启动在 http://localhost:$PORT"
场景 2:下班前清理
# 停止某个项目的所有服务
sonar down my-project
# 或者停止所有 Docker 开发容器
sonar kill-all --filter docker -y
场景 3:排查"端口冲突"
# 谁占了 3000 端口?
sonar info 3000
# 是个忘了关的 Docker 容器,直接杀掉
sonar kill 3000
场景 4:监控微服务
# 实时监控所有服务,有变化弹桌面通知
sonar watch --notify --stats
去开个会回来,桌面通知会告诉你哪些服务挂了、哪些新服务启动了。
场景 5:CI/CD 服务就绪等待
# 等待数据库和后端都就绪再跑测试
docker compose up -d
sonar wait 5432 8080 --http --timeout 60s && go test ./...
总结
sonar 解决的问题很小——"谁占了我的端口"。但这个"很小"的问题每个开发者每周都会遇到几次。把 lsof + docker ps + netstat + kill + tail 的组合操作统一成 sonar list、sonar kill、sonar logs,省下来的时间积少成多。