Apache Flagon 常用 CLI 命令与工具详解
Apache Flagon 常用 CLI 命令详解:git/pnpm 构建、node 收集服务器运行与优雅停机、curl 上报验证、systemd 托管、npm/pip 包管理与 Distill 分析脚本,逐参数实测说明。
Apache Flagon 常用 CLI 命令与工具详解
目标:把日常运维 Apache Flagon(UserALE.js + Example Collector + Distill)用到的命令逐条讲透——每条命令给出参数含义与实测回显,覆盖「构建、运行、验证、托管、分析、排障」全周期。全部命令在 Ubuntu 22.04.5 LTS(x86_64)实测通过(2026-10-16 实测)。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- Node.js:v20.20.2(npm 10.8.2)、pnpm:9.15.9
- Python:3.10.12(pip 22.0.2)、Git:2.34.1
- 源码目录:
/root/flagon(monorepo master,61677c7,2026-10-11 提交) - UserALE.js:npm
flagon-userale2.4.0;Distill:flagon-distill0.1.0 - 收集服务器:
example/server/server.js,默认端口 8000
Flagon 日常操作没有单一 flagon 命令,而是由一组工具协作:构建用 pnpm、运行收集端用 node、验证用 curl、常驻用 systemctl、分析用 python(Distill 库)、排障用 pgrep/ps/tail。命令地图如下:
| 阶段 | 命令 | 作用 |
|---|---|---|
| 获取/构建 | git / pnpm | 拉取 monorepo、安装依赖、构建 UserALE |
| 运行 | node | 启动 Example Collector(HTTP + WebSocket) |
| 验证 | curl / node | HTTP 上报模拟、WebSocket 客户端、页面可达性 |
| 托管 | systemctl / journalctl | 常驻、自启、日志 |
| 包管理 | npm / pip3 | 发布版安装与版本核实 |
| 分析 | python3 | Distill 会话摘要/挫败感检测/热力图 |
| 排障 | pgrep / pkill / ps / tail | 进程定位、优雅停机、日志追踪 |
二、前置条件
确认工具链就绪(实测回显,命令逐条执行):
git --version
pnpm -v
node -v
python3 --version
git version 2.34.1
9.15.9
v20.20.2
Python 3.10.12
ls /root/flagon/products/userale/example/server/server.js
/root/flagon/products/userale/example/server/server.js
💡 技术拓展:pnpm 与 npm 的命令行结构同源(
pnpm install≈npm install),但 workspace 项目必须用 pnpm——monorepo 根package.json声明了pnpm-workspace.yaml的多包结构,npm 会把子包依赖拍平装错位置。判断一个仓库该用什么工具,先看根目录有没有pnpm-workspace.yaml(Flagon 实测有)。
三、源码获取与构建命令
3.1 git clone
cd /root
git clone --depth 5 https://github.com/apache/flagon.git flagon
cd /root/flagon
git log -1 --format='%H %ad %s' --date=iso
Cloning into 'flagon'...
61677c77727a5f9158923c42034a59b6d576f476 2026-10-11 21:26:58 -0700 fix(site): improve homepage hero formatting, add deployment stack pills, and replace raw backticks in HTML (#201)
clone --depth 5:浅克隆只取最近 5 个提交(monorepo 含 site 构建产物历史,评估部署用浅克隆足够);log -1 --format='%H %ad %s' --date=iso:最新 1 条提交的完整哈希、作者日期(ISO 8601 带时区)、主题。
3.2 pnpm install / pnpm build
cd /root/flagon/products/userale
pnpm install
Done in 2m 8.1s using pnpm v9.15.9
install:按 workspace 锁文件安装全部子包依赖(UserALE.js、浏览器扩展、example server 的 express/ws/body-parser 都在这一步装好)。
pnpm build
packages/flagon-userale build: [USERALE] ESM ⚡️ Build success in 21ms
packages/flagon-userale build: Done
packages/flagon-userale-ext build: 🟣 Plasmo v0.90.3
packages/flagon-userale-ext build: 🟢 DONE | Finished in 981ms!
packages/flagon-userale-ext build: Done
build:workspace 递归脚本(等价pnpm -r run build),产出浏览器端 IIFE 包(packages/flagon-userale/build/main.global.js,实测 71872 字节)与浏览器扩展 chrome-mv3 产物。
3.3 pnpm run:脚本清单与常用子命令
pnpm run
Lifecycle scripts:
test
playwright test -c ./test/playwright.config.ts && pnpm -r test
pretest
npx playwright install
Commands available via "pnpm run":
format
prettier --ignore-path .gitignore --write 'packages/**/{src,test}/**/*.{ts,js,tsx,jsx}'
format:check
prettier --ignore-path .gitignore --check 'packages/**/{src,test}/**/*.{ts,js,tsx,jsx}'
lint
eslint 'packages/**/src/**/*.{ts,js,tsx,jsx}' --fix
lint:check
eslint 'packages/**/src/**/*.{ts,js,tsx,jsx}'
build
pnpm -r run build
example:run
node example/server/server.js
example:watch
nodemon -w ./example/server example/server/server.js
逐个解读(pnpm run <name> 执行对应脚本):
| 脚本 | 作用 | 实测建议 |
|---|---|---|
test | Playwright 端到端测试(pretest 先装浏览器内核) | 首次跑会下载 Chromium,耗时较长 |
format / format:check | Prettier 写入/检查格式 | 提 PR 前跑 format:check |
lint / lint:check | ESLint 检查(lint 带 --fix 自动修复) | CI 用 lint:check |
build | 递归构建全部子包 | 部署必跑 |
example:run | 等价 node example/server/server.js 前台启动 | 本地演示 |
example:watch | nodemon 监听 server 目录改动自动重启 | 二次开发收集端时用 |
四、收集服务器运行命令
4.1 node 启动与优雅停机
前台启动(实测回显):
cd /root/flagon/products/userale
node example/server/server.js
UserAle HTTP + WebSocket server running on port 8000
node:Node.js 运行时(无--experimental等参数需求,依赖 CommonJS);example/server/server.js:收集端脚本,支持三种入参格式(UserALE 平铺 JSON、OTLP resourceLogs、WS 消息)。
生产后台启动并记录 PID(实测回显):
nohup node example/server/server.js > /root/server.log 2>&1 &
echo "server pid: $!"
sleep 3
cat /root/server.log
server pid: 5314
UserAle HTTP + WebSocket server running on port 8000
nohup ... &:后台常驻,输出重定向到/root/server.log;2>&1:stderr 并入 stdout;echo "server pid: $!":$!是最后一个后台进程的 PID,记下来用于精确停机。
优雅停机(实测回显,kill -INT 发 SIGINT 触发日志文件收尾):
kill -INT 5314
sleep 6
tail -3 /root/server.log
Shutting down...
HTTP server closed.
WebSocket server closed.
kill -INT:给指定 PID 发送 SIGINT(等价前台 Ctrl+C),收集器会补写日志文件结尾]再退出;sleep 6:实测留足写流 flush 时间(见 FAQ Q1)。
改端口启动(PORT 环境变量,实测回显):
PORT=9000 nohup node example/server/server.js > /root/server_9000.log 2>&1 &
sleep 3
cat /root/server_9000.log
curl -s -o /dev/null -w 'GET / -> HTTP %{http_code}\n' --noproxy '*' http://localhost:9000/
UserAle HTTP + WebSocket server running on port 9000
GET / -> HTTP 200
4.2 WebSocket 上报(node ws 客户端)
收集端在 /ws 提供 WebSocket 通道,用包内自带的 ws 库做客户端实测上报:
cd /root/flagon/products/userale
nohup node example/server/server.js > /root/server_ws.log 2>&1 &
sleep 3
node -e "
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:8000/ws');
ws.on('open', () => {
ws.send(JSON.stringify([{logType:'raw',clientTime:1760032000000,userAction:true,type:'click',action:'click',target:'BUTTON#ws-btn',user:'demo-user',sessionId:'sess-ws-9',tool:'Demo App',version:'2.4.0',pageUrl:'http://localhost:8000/',details:{}}]));
setTimeout(()=>{ ws.close(); process.exit(0); }, 500);
});
ws.on('error', (e)=>{ console.error('WS error:', e.message); process.exit(1); });
"
node -e "<script>":-e直接执行内联脚本,免落盘;new WebSocket('ws://localhost:8000/ws'):连接收集端 WS 端点;ws.send(JSON.stringify([...])):与 HTTP 相同的 UserALE 批量 JSON 数组;setTimeout(..., 500):发送后 500ms 主动关闭退出。
验证服务端收到(实测回显):
grep -c 'WebSocket client' /root/server_ws.log
2
grep -c:统计匹配行数,实测出现 2 次WebSocket client日志(连接建立与断开各一条),说明 WS 链路双向工作正常。
五、HTTP 验证命令(curl)
5.1 可达性验证
curl -s -o /tmp/index_out.html -w 'HTTP %{http_code} bytes=%{size_download}\n' http://localhost:8000/
curl -s -o /dev/null -w 'HTTP %{http_code} bytes=%{size_download}\n' http://localhost:8000/build/main.global.js
HTTP 200 bytes=3447
HTTP 200 bytes=71872
-s:静默(不显示进度条);-o <file>:响应体落盘(/dev/null即丢弃);-w '<format>':%{http_code}状态码、%{size_download}响应字节数——验证「页面 + IIFE 产物」两个关键资源。
5.2 模拟日志上报
curl -s -o /dev/null -w 'HTTP %{http_code}\n' -X POST http://localhost:8000/v1/logs \
-H 'Content-Type: application/json' \
-d '[{"logType":"raw","clientTime":1760030001000,"microTime":1760030001000.35,"userAction":true,"type":"click","action":"click","target":"BUTTON#submit","path":[{"position":0,"tag":"BODY","id":null,"classes":[]}],"user":"demo-user","sessionId":"sess-run-3","tool":"Demo App","version":"2.4.0","pageUrl":"http://localhost:8000/","pageTitle":"Demo Page","location":{"x":512,"y":300},"scrnRes":"1920x1080","details":{}}]'
HTTP 200
-X POST:显式 POST(-d存在时可省略);-H 'Content-Type: application/json':收集端按 JSON 解析(bodyParser.json 中间件);-d '<json>':请求体,单元素数组即一批;多个连续 POST 模拟同一 session 的点击序列(rage click 场景见 7.3)。
追踪服务端落盘(实测回显):
LOG=$(ls -t /root/flagon/products/userale/example/logs/logs_*.json | head -1)
echo "$LOG"
python3 -c "import json; d=json.load(open('$LOG')); print('records:', len(d))"
/root/flagon/products/userale/example/logs/logs_1792123964892.json
records: 8
ls -t ... | head -1:按修改时间取最新日志文件(收集端按进程启动时间戳命名,logs_<ts>.json);json.load+len(d):确认全部日志已落盘可解析。
六、systemd 服务管理命令
单元文件 /etc/systemd/system/flagon-collector.service(完整内容与部署过程见《Ubuntu 22.04 安装 Apache Flagon 指南》第七章)。日常管理命令逐条如下:
systemctl daemon-reload
- 修改 unit 文件后必须执行,重新加载单元定义(无输出即成功)。
systemctl enable flagon-collector
Created symlink /etc/systemd/system/multi-user.target.wants/flagon-collector.service → /etc/systemd/system/flagon-collector.service.
enable:注册开机自启(创建 multi-user.target.wants 软链接);- 对应
disable移除(实测输出Removed /etc/systemd/system/multi-user.target.wants/flagon-collector.service.)。
systemctl start flagon-collector
sleep 3
systemctl is-active flagon-collector
active
start:启动(等价前台node但由 systemd 托管);is-active:仅输出状态字符串(active/inactive/failed),适合脚本判断。
systemctl status flagon-collector --no-pager -l | head -8
● flagon-collector.service - Apache Flagon UserALE Example Collector
Loaded: loaded (/etc/systemd/system/flagon-collector.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2026-10-16 04:18:45 UTC; 3s ago
Main PID: 3947 (node)
Tasks: 11 (limit: 7196)
Memory: 15.5M
status:完整状态(Loaded/Active/Main PID/Memory 等);--no-pager -l:不进分页器、不截断长行(脚本环境必加)。
journalctl -u flagon-collector --no-pager -n 3
Oct 16 04:19:24 aa6f16b2a761 node[3993]: HTTP server closed.
Oct 16 04:19:24 aa6f16b2a761 node[3993]: WebSocket server closed.
Oct 16 04:19:24 aa6f16b2a761 systemd[1]: flagon-collector.service: Deactivated successfully.
journalctl -u <unit>:过滤指定单元日志;-n 3:只看最近 3 行;追实时日志用-f(如journalctl -u flagon-collector -f)。
systemctl restart flagon-collector
systemctl stop flagon-collector
restart:等效 stop + start(实测 restart 后is-active为active);stop:发送 SIGTERM,实测收集器优雅收尾(日志文件完整、Deactivated successfully)。
七、包管理与分析命令
7.1 npm 包管理
npm view flagon-userale dist-tags version
dist-tags = { latest: '2.4.0' }
version = '2.4.0'
view <pkg> <field>:查询 registry 元数据(不安装),用于版本核实;- 常用字段:
versions(全版本列表,实测 1.0.0→2.4.0 共 8 版)、dependencies(实测输出为空,零运行时依赖)。
mkdir -p /root/uale-npm && cd /root/uale-npm
npm init -y
npm install flagon-userale@2.4.0
Wrote to /root/uale-npm/package.json:
added 1 package, and audited 2 packages in 1s
found 0 vulnerabilities
init -y:跳过问答生成默认 package.json;install <pkg>@<version>:精确安装 2.4.0;产物在node_modules/flagon-userale/build/(userale-2.4.0.js44729 字节 /.min.js15276 字节)。
7.2 pip 包管理
pip3 index versions flagon-distill
WARNING: pip index is currently an experimental command. It may be removed/changed in a future release without prior warning.
flagon-distill (0.1.0)
Available versions: 0.1.0
INSTALLED: 0.1.0
LATEST: 0.1.0
pip3 install /root/flagon/products/distill
pip3 install 'numpy<2'
python3 -c "import distill; print('distill import OK')"
pip3 show flagon-distill | head -3
Successfully built flagon-distill
Successfully installed numpy-1.26.4
distill import OK
Name: flagon-distill
Version: 0.1.0
Summary: An analytics framework for front-end UserALE data, enabling modeling, sequence analysis, and customization in Python.
pip3 index versions <pkg>:核实线上可用版本(实验性命令,带 WARNING 属正常);pip3 install /path/to/pkg:从本地源码目录安装(monorepoproducts/distill,实测比 PyPI 0.1.0 依赖新);pip3 install 'numpy<2':修复 pandas 1.5.3 与 numpy 2.x 二进制不兼容(实测装出 1.26.4);show <pkg>:查看已装版本与元信息。
7.3 Distill 分析脚本
先看 Distill 提供的全部能力(顶层导出,实测回显):
python3 -c "import distill; print([n for n in dir(distill) if not n.startswith('_')])"
['FeatureDefinition', 'Log', 'Segment', 'Segment_Type', 'SegmentationError', 'Segments', 'Session', 'Sessions', 'analytics', 'click_heatmap_data', 'click_rate', 'core', 'correlate_by_trace', 'createDiGraph', 'create_segment', 'detect_dead_clicks', 'detect_deadspace', 'detect_dom_thrash', 'detect_rage_clicks', 'difference', 'epoch_to_datetime', 'export_segments', 'extract_logs', 'extract_replay_envelopes', 'find_meta_values', 'frustration_score', 'funnel', 'generate_collapsing_window_segments', 'generate_fixed_time_segments', 'generate_segments', 'getUUID', 'intersection', 'label_features', 'normalize_userale_dict', 'pairwiseSeq', 'pairwiseStag', 'plot_click_heatmap', 'process', 'sankey', 'schemas', 'scroll_depth_summary', 'segmentation', 'session_summary', 'sessions', 'setup', 'transition_matrix', 'union', 'utils', 'write_segment']
dir()过滤下划线开头:拿到 45 个导出名称——除本文用到的核心函数外,还有funnel(漏斗)、sankey(桑基图)、detect_deadspace(死区检测)、detect_dom_thrash(DOM 抖动)、generate_segments系列(会话切分)等,可按需扩展。
标准分析脚本(保存为 /root/analyze.py 后执行,实测回显):
cat > /root/analyze.py <<'EOF'
import json, glob
import distill
from distill.schemas.userale import extract_logs
f = sorted(glob.glob('/root/flagon/products/userale/example/logs/logs_*.json'))[-1]
payload = json.load(open(f))
print('== source file:', f.split('/')[-1], '| raw records:', len(payload))
docs = [d.model_dump(by_alias=True) if hasattr(d, 'model_dump') else d for d in extract_logs(payload)]
print('== extract_logs ->', len(docs), 'logs; keys:', sorted(docs[0].keys()))
summary = distill.session_summary(docs)
print('== session_summary:', json.dumps(summary, default=str))
fric = distill.frustration_score(docs)
short = dict(fric); short['deadClicks'] = f"<{len(fric['deadClicks'])} items>"
print('== frustration_score:', json.dumps(short, default=str))
rage = distill.detect_rage_clicks(docs)
print('== detect_rage_clicks:', json.dumps(rage, default=str))
EOF
python3 /root/analyze.py
== source file: logs_1792123964892.json | raw records: 8
== extract_logs -> 8 logs; keys: ['action', 'clientTime', 'details', 'location', 'logType', 'microTime', 'pageTitle', 'pageUrl', 'path', 'scrnRes', 'sessionId', 'target', 'tool', 'type', 'user', 'userAction', 'version']
== session_summary: {"total_logs": 8, "raw_logs": 8, "interval_logs": 0, "custom_logs": 0, "mutation_logs": 0, "replay_logs": 0, "click_count": 8, "rage_click_count": 0, "duration_seconds": 8.0, "click_rate": 1.0, "unique_targets": 5, "unique_pages": 1, "page_load_time_ms": null, "trace_ids": []}
== frustration_score: {"frustrationScore": 65.0, "severity": "high", "rageClickCount": 1, "deadClickCount": 8, "domThrashBurstCount": 0, "errorCount": 0, "rageClicks": [{"target": "BUTTON#broken", "pageUrl": "http://localhost:8000/", "sessionId": "sess-run-3", "timestamp": 1760030006300, "clickCount": 3, "targetRect": null, "traceId": null, "source": "inferred"}], "deadClicks": "<8 items>"}
== detect_rage_clicks: [{"target": "BUTTON#broken", "pageUrl": "http://localhost:8000/", "sessionId": "sess-run-3", "timestamp": 1760030006300, "clickCount": 3, "targetRect": null, "traceId": null, "source": "inferred"}]
extract_logs(payload):多格式解析校验(UserALE 平铺 JSON / OTLP 混合均可);session_summary(docs):会话摘要(14 项指标);frustration_score(docs):挫败感综合分 65.0(severity=high),rage/dead click 明细内嵌;detect_rage_clicks(docs):Rage Click 检测,参数window_ms=1000, threshold=3, radius_px=30.0可调(实测收紧window_ms=500, threshold=4仍检出 1 组,放宽window_ms=3000, threshold=2检出 2 组)。
生成点击热力图(实测回显):
python3 - <<'EOF'
import json
import distill
from distill.schemas.userale import extract_logs
docs = [d.model_dump(by_alias=True) if hasattr(d,'model_dump') else d for d in extract_logs(json.load(open('/root/flagon/products/userale/example/logs/logs_1792123964892.json')))]
fig = distill.plot_click_heatmap(docs, nbinsx=20, nbinsy=20)
fig.write_html('/root/heatmap.html', include_plotlyjs=False)
print('figure traces:', len(fig.data), '| layout title:', fig.layout.title.text)
EOF
ls -la /root/heatmap.html
figure traces: 1 | layout title: None
-rw-r--r-- 1 root root 7957 Oct 16 04:14 /root/heatmap.html
plot_click_heatmap(docs, *, nbinsx=20, nbinsy=20, normalize_viewport=False):keyword-only 参数,bins 数决定网格粒度,normalize_viewport=True按视口归一化坐标;fig.write_html('/root/heatmap.html', include_plotlyjs=False):落盘 HTML(include_plotlyjs=False减小文件,生产页面引入公共 plotly.js 即可)。
八、排障命令
pgrep -af 'server\.js'
5527 node example/server/server.js
pgrep -af:-a显示完整命令行、-f匹配整条命令行(不加-f只匹配进程名node);- 模式
'server\.js':转义点号精确匹配,避免误中serverXjs之类。
pkill -f 'server\.js'
sleep 2
ps -ef | grep [n]ode
no node process left
pkill -f:按完整命令行杀进程(发 SIGTERM);ps -ef | grep [n]ode:[n]ode技巧避免 grep 自身出现在结果;无输出说明清理干净。
tail -22 /root/server.log
tail -n:看服务端控制台最近输出(每次 HTTP POST 会打印收到的日志对象,实测可见HTTP POST: [...]);持续跟踪用tail -f。
curl -s -o /dev/null -w 'GET / -> HTTP %{http_code}\n' --noproxy '*' http://localhost:8000/
--noproxy '*':服务器配了http_proxy环境变量时必须加,否则本机请求也被发给代理(实测返回 502 的根因)。
九、注意事项
- 优雅停机优先:无论
kill -INT还是systemctl stop,收集器都会补写日志文件结尾];kill -9会留下不可解析的半截文件; - 不要用
pkill -f 'node server.js':匹配不到长路径启动的实例(node example/server/server.js),实测踩坑,用server\.js模式或按 PID 精确操作; - 日志文件按进程启动时间滚动:重启服务即产生新
logs_<ts>.json,分析脚本用sorted(glob(...))[-1]取最新时注意多实例并存的情况; - npm 方案与源码方案产物不同名:npm 包内是
userale-2.4.0.js(IIFE),源码构建是main.global.js,页面引用时以实际文件为准; - Python 分析端零 CLI:Distill 全部能力都是 Python API,写脚本封装即可,不要找
distill命令。
十、常见问题(FAQ)
Q1:停机后立即解析日志文件报 JSONDecodeError?
写流 flush 未完成。实测等待 6 秒后文件必然完整;应急处理补一个 ]:
python3 -c "import json; json.load(open('$LOG'))" 2>/dev/null || echo "]" >> "$LOG"
Q2:pip3 install flagon-distill 后 import distill 报 numpy.dtype size changed?
PyPI 0.1.0 锁定 pandas<2.0(1.5.3)与最新 numpy 2.x 二进制冲突。实测修复:pip3 install 'numpy<2'(装出 1.26.4 后导入正常),或直接源码安装 monorepo 版。
Q3:WS 客户端报 WS error: Unexpected server response: 404?
路径写错。收集端 WS 端点是 /ws(源码 93 行 app.get("/ws", ...)),不是根路径;且 WS 与 HTTP 共用同一端口(httpServer.listen(port))。
Q4:pnpm run test 首次执行卡在下载浏览器?
pretest 生命周期脚本执行 npx playwright install 下载 Chromium 内核(体积大)。走代理或仅验证收集端时可跳过测试,直接用 4.1 的 node 启动验证。
十一、参考链接
- monorepo 仓库:https://github.com/apache/flagon
- UserALE.js npm 包:https://www.npmjs.com/package/flagon-userale
- Distill PyPI 包:https://pypi.org/project/flagon-distill/
- 安装部署全流程见《Ubuntu 22.04 安装 Apache Flagon 指南》;参数逐项说明见《Apache Flagon 配置参数详解》;组件全景与选型对照见《Apache Flagon 介绍与选型指南》。