Apache Flagon 常用 CLI 命令与工具详解

Apache Flagon 常用 CLI 命令详解:git/pnpm 构建、node 收集服务器运行与优雅停机、curl 上报验证、systemd 托管、npm/pip 包管理与 Distill 分析脚本,逐参数实测说明。

Apache FlagonApache FlagonPythonUbuntuUbuntu 22.04Linux2026/10/16

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-userale 2.4.0;Distill:flagon-distill 0.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 / nodeHTTP 上报模拟、WebSocket 客户端、页面可达性
托管systemctl / journalctl常驻、自启、日志
包管理npm / pip3发布版安装与版本核实
分析python3Distill 会话摘要/挫败感检测/热力图
排障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> 执行对应脚本):

脚本作用实测建议
testPlaywright 端到端测试(pretest 先装浏览器内核)首次跑会下载 Chromium,耗时较长
format / format:checkPrettier 写入/检查格式提 PR 前跑 format:check
lint / lint:checkESLint 检查(lint 带 --fix 自动修复)CI 用 lint:check
build递归构建全部子包部署必跑
example:run等价 node example/server/server.js 前台启动本地演示
example:watchnodemon 监听 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.js 44729 字节 / .min.js 15276 字节)。

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:从本地源码目录安装(monorepo products/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 的根因)。

九、注意事项

  1. 优雅停机优先:无论 kill -INT 还是 systemctl stop,收集器都会补写日志文件结尾 ];kill -9 会留下不可解析的半截文件;
  2. 不要用 pkill -f 'node server.js':匹配不到长路径启动的实例(node example/server/server.js),实测踩坑,用 server\.js 模式或按 PID 精确操作;
  3. 日志文件按进程启动时间滚动:重启服务即产生新 logs_<ts>.json,分析脚本用 sorted(glob(...))[-1] 取最新时注意多实例并存的情况;
  4. npm 方案与源码方案产物不同名:npm 包内是 userale-2.4.0.js(IIFE),源码构建是 main.global.js,页面引用时以实际文件为准;
  5. 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 启动验证。

十一、参考链接