Ubuntu 22.04 安装 Apache Flagon 指南

在 Ubuntu 22.04 从零安装 Apache Flagon(UserALE.js 2.4.0 + 收集服务器 + Distill 0.1.0)的源码构建与 npm 包双方案实测指南,含 systemd 托管与端到端数据收集验证。

Apache FlagonApache FlagonApacheUbuntuUbuntu 22.04Linux2026/10/16

Ubuntu 22.04 安装 Apache Flagon 指南

目标:在 Ubuntu 22.04 服务器上从零安装 Apache Flagon 的可跑通组件组合——UserALE.js 2.4.0(npm flagon-userale)+ 官方 Example Collector(Node.js 收集服务器)+ Distill 0.1.0(Python 分析端),并以 systemd 托管、用 curl 与 Distill 验证行为日志端到端收集。全部命令与回显均在 Ubuntu 22.04.5 LTS(x86_64)实测通过(2026-10-16 实测)。

一、环境信息

本文实测环境与目录约定如下,所有命令默认在 /root 下执行:

  • 操作系统: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 主线);npm 方案目录:/root/uale-npm
  • 收集服务器端口:8000(HTTP + WebSocket)
  • 日志落盘目录:/root/flagon/products/userale/example/logs/
  • 实测版本:UserALE.js 2.4.0(npm dist-tags latest)、Distill 0.1.0(PyPI)、monorepo master(2026-10-11 提交)

先确认系统版本(实测回显):

cat /etc/os-release | head -3
uname -m
PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
x86_64

💡 说明:Apache Flagon 当前主线是 monorepo(github.com/apache/flagon),历史上独立存在的 distillery 数据收集服务与 stout 仓库已在 GitHub 上消失(实测返回 404),因此本指南的收集端采用官方 example/server 参考实现,前端采集端采用 UserALE.js——这是官方仓库现状下唯一可跑通的最小闭环组合。

二、前置条件

Flagon 最小闭环由「前端采集(UserALE.js)+ 收集端(Node.js)+ 分析端(Python)」组成,安装前需要准备:基础编译与下载工具(curl、ca-certificates、git)、Node.js ≥ 20、pnpm、Python 3.10+ 与 pip、可用的出网通道。逐项安装基础依赖(实测回显,输出节选):

apt-get update
apt-get install -y curl ca-certificates gnupg git python3 python3-pip
Get:18 http://archive.ubuntu.com/ubuntu jammy-backports/universe amd64 Packages [35.6 kB]
Fetched 49.0 MB in 21s (2299 kB/s)
Setting up ca-certificates (20260601~22.04.1) ...
0 added, 0 removed; done.

💡 技术拓展:Node.js 的安装方式不止 NodeSource 仓库一种——Ubuntu 官方仓库版本旧(jammy 为 12.x,无法满足构建要求);nvm 适合开发机多版本并存;官方 tar.xz 二进制包适合内网离线。生产服务器推荐 NodeSource apt 仓库(20/22/24 大版本可选、apt 统一管理、/usr/bin/node 路径固定便于 systemd 引用),本文即采用该方式(见 4.2 节)。

三、版本选型说明

安装前先核实各组件的当前版本,避免用过时资料里的旧组件(如已消失的 distillery)。实测核实 npm 侧 UserALE.js:

npm view flagon-userale dist-tags version
dist-tags = { latest: '2.4.0' }
version = '2.4.0'
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

版本选型对照表:

组件实测最新版获取渠道说明
UserALE.js2.4.0npm flagon-userale前端采集库,版本序列 1.0.0→2.4.0 共 8 版
Example Collectormaster(随 monorepo)git cloneNode.js Express + WebSocket 收集参考实现
Distill0.1.0PyPI flagon-distill分析端仅一个已发布版本,建议源码安装 monorepo 版
monorepomaster 61677c7(2026-10-11)github.com/apache/flagon主线开发仓库

选型结论:生产采集端用 npm 固定 2.4.0 版本;需要最新修复/参与开发则走 monorepo 源码构建;分析端直接源码安装 monorepo 的 products/distill(其依赖比 PyPI 0.1.0 新,实测功能更全)。

四、方案 A:源码构建安装(官方主线,推荐)

4.1 方案说明

从官方 monorepo 克隆源码,用 pnpm 构建 UserALE workspace(同时产出前端 IIFE 包与浏览器扩展),直接以仓库自带 Example Collector 起收集服务。适合需要完整能力(扩展、示例、最新修复)的部署,也是官方 Quick Start 路径。

4.2 安装 Node.js 20 与 pnpm

NodeSource 脚本方式接入 Node.js 20 仓库并安装(实测回显):

curl -fsSL https://deb.nodesource.com/setup_20.x -o /tmp/nodesource_setup.sh
bash /tmp/nodesource_setup.sh
apt-get install -y nodejs
npm install -g pnpm@9
2026-10-16 04:00:47 - You can use N|solid Runtime as a node.js alternative
2026-10-16 04:00:48 - To install N|solid Runtime, run: apt install nsolid -y
Setting up nodejs (20.20.2-1nodesource1) ...
npm notice To update run: npm install -g npm@12.2.0

验证版本(四条命令逐条执行,实测回显合并展示):

node -v
npm -v
pnpm -v
git --version
v20.20.2
10.8.2
9.15.9
git version 2.34.1

💡 技术拓展:JavaScript 包管理器主流三种——npm(Node 自带,扁平 node_modules,适合简单单包项目)、yarn(经典/berry 两代,锁文件成熟)、pnpm(内容寻址全局存储 + 硬链接,磁盘占用小、严格依赖隔离)。Flagon monorepo 是 pnpm workspace(根 package.json 定义多个 package),因此本文采用 pnpm,pnpm -r run build 可一次构建全部子包。

4.3 获取源码

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)

实测说明:--depth 5 浅克隆仅取最近提交,节省时间;git log 确认拿到的 master 最新提交为 2026-10-11,主线活跃。

4.4 构建 UserALE workspace

cd /root/flagon/products/userale
pnpm install
pnpm build
Done in 2m 8.1s using pnpm v9.15.9
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

验证要点:构建同时产出浏览器端 IIFE 包 packages/flagon-userale/build/main.global.js(实测 71872 字节)与浏览器扩展的 chrome-mv3-prod 目录(packages/flagon-userale-ext/build/ 下 manifest.json 存在)。

4.5 启动收集服务器

Example Collector 位于 example/server/server.js,依赖已在 pnpm install 时随 workspace 装好。生产用 nohup 后台启动并记录 PID(本指南第八、九章的端到端验证即基于该方式,实测回显):

cd /root/flagon/products/userale
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

4.6 验证

页面与构建产物可达性验证(实测回显):

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

再核对示例页面中 UserALE 的埋点配置已正确内嵌(实测回显):

grep -oE 'data-(url|user|version|tool|threshold|interval)="[^"]*"' /tmp/index_out.html
data-url="http://localhost:8000/"
data-user="example-user"
data-version="2.3.0"
data-tool="Apache UserALE Example"
data-threshold="1"
data-interval="1000"

验证要点:示例页面默认把日志发到 data-url 指向的收集端,data-threshold="1" 表示每 1 条日志即触发一次发送、data-interval="1000" 表示每 1 秒检查一次发送——用真实浏览器打开 http://<服务器IP>:8000/ 交互后,日志会持续写入 example/logs/ 目录。

五、方案 B:npm 包安装(轻量集成)

5.1 方案说明

不克隆 monorepo,直接用 npm 安装发布的 flagon-userale 包,把包内 IIFE 产物部署到任意静态页面。适合只需要「给现有站点加埋点」的场景:不需要 pnpm 与源码构建,部署物只有几十 KB。

5.2 npm 安装 flagon-userale

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

💡 说明:npm init -y 会生成默认 package.json(name 取目录名 uale-npm,license 为 ISC 等,实测内容为标准模板)。

核对包内构建产物与版本(实测回显,输出节选):

ls -la node_modules/flagon-userale/build/ | grep -vE '\.map|\.d\.ts'
total 72
drwxr-xr-x 3 root root  4096 Oct 16 04:13 .
drwxr-xr-x 8 root root  4096 Oct 16 04:13 ..
drwxr-xr-x 3 root root  4096 Oct 16 04:13 UserALEWebExtension
-rw-r--r-- 1 root root 44729 Oct 16 04:13 userale-2.4.0.js
-rw-r--r-- 1 root root 15276 Oct 16 04:13 userale-2.4.0.min.js

5.3 部署埋点页面

把包内 IIFE 产物复制到站点目录,并在页面 <script> 标签中传入配置(data-url 指向收集端,参数逐项含义见《Apache Flagon 配置参数详解》):

mkdir -p /var/www/uale-site && cp node_modules/flagon-userale/build/userale-2.4.0.js /var/www/uale-site/
cat > /var/www/uale-site/index.html <<'EOF'
<!DOCTYPE html>
<html>
<head>
  <title>Flagon npm Deploy Demo</title>
  <script src="userale-2.4.0.js"
          data-url="http://localhost:8000/"
          data-user="demo-user"
          data-version="2.4.0"
          data-tool="npm-demo-app"
          data-threshold="1"
          data-interval="1000"></script>
</head>
<body>
  <button id="test_button">Click Me!</button>
</body>
</html>
EOF
ls -la /var/www/uale-site/
total 56
drwxr-xr-x 2 root root  4096 Oct 16 05:12 .
drwxr-xr-x 3 root root  4096 Oct 16 05:12 ..
-rw-r--r-- 1 root root   392 Oct 16 05:12 index.html
-rw-r--r-- 1 root root 44729 Oct 16 05:12 userale-2.4.0.js

💡 说明:方案 B 的收集端复用方案 A 的 Example Collector(Node.js 服务常驻方式见第七章 systemd 托管)。页面文件可由任意静态服务器(Nginx 等)托管,本文为聚焦 Flagon 本身不再展开。部署后用真实浏览器打开页面任意点击,日志即按 http-json 格式 POST 到收集端(与方案 A 相同的数据流)。

六、方案对比与选择建议

维度方案 A:源码构建方案 B:npm 包
产物版本master 最新(61677c7,2026-10-11)发布版 2.4.0
前置依赖Node 20 + pnpm 9 + git + 全 workspace 依赖仅 npm(Node 自带)
构建耗时pnpm install 2m8s + build 秒级npm install 1s
附带能力浏览器扩展构建、示例页、OTel demo、Distill 源码仅 IIFE 产物 + 扩展随包文件
适用场景评估全量能力、参与开发、需要最新修复现有站点快速埋点、版本固定

选择建议:生产埋点优先方案 B(版本固定、体积小、依赖面窄);需要浏览器扩展、Distill 源码或跟随主线修复时用方案 A。两方案的收集端与分析端完全一致,可并行共存(方案 A 目录提供收集器,方案 B 目录提供页面产物)。

七、systemd 服务托管

收集服务器需要开机自启与崩溃自动拉起,用 systemd 托管(⚠️ 本章命令需要带 systemd 的 Linux 真机环境执行;单元文件与验证回显均为 systemd 环境实测)。写入单元文件:

cat > /etc/systemd/system/flagon-collector.service <<'EOF'
[Unit]
Description=Apache Flagon UserALE Example Collector
After=network.target

[Service]
Type=simple
WorkingDirectory=/root/flagon/products/userale
ExecStart=/usr/bin/node example/server/server.js
Environment=NODE_ENV=production
Environment=PORT=8000
Restart=on-failure
RestartSec=3

[Install]
WantedBy=multi-user.target
EOF

逐行加载、设自启、启动(daemon-reload 全局执行一次,enable/start 针对同一单元逐条操作;实测回显合并展示):

systemctl daemon-reload
systemctl enable flagon-collector
systemctl start flagon-collector
sleep 3
systemctl is-active flagon-collector
Created symlink /etc/systemd/system/multi-user.target.wants/flagon-collector.service → /etc/systemd/system/flagon-collector.service.
active

服务状态核查(实测回显,输出节选):

systemctl status flagon-collector --no-pager -l | head -8
journalctl -u flagon-collector --no-pager -n 2
● 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)
     Memory: 15.5M
Oct 16 04:18:45 aa6f16b2a761 node[3947]: UserAle HTTP + WebSocket server running on port 8000

重启与停止行为验证(实测回显,命令逐条执行):

systemctl restart flagon-collector
sleep 3
systemctl is-active flagon-collector
active
systemctl stop flagon-collector
sleep 2
systemctl is-active flagon-collector
journalctl -u flagon-collector --no-pager -n 3
inactive
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.

配置逐项说明:WorkingDirectory 必须是 products/userale(server.js 以相对路径引用 workspace 依赖与静态目录);ExecStart 用绝对路径 /usr/bin/node(NodeSource 安装的固定位置);PORT=8000 覆盖默认监听端口;Restart=on-failure + RestartSec=3 表示异常退出 3 秒后拉起。实测要点:systemd stop 发送 SIGTERM,收集服务器会在退出前把日志文件补写完整(第九章 FAQ 的「缺右括号」问题在 SIGTERM 收尾下未复现)。

八、端到端数据收集验证

本节把「采集 → 收集 → 分析」整条链路用可重复的命令验证一遍:先装分析端,再模拟日志上报,最后用 Distill 检出 rage click。

8.1 安装 Distill 分析端

先尝试 PyPI 发布版(实测回显):

pip3 install flagon-distill
Successfully installed flagon-distill-0.1.0 networkx-2.8.8 numpy-2.2.6 packaging-26.3 pandas-1.5.3 plotly-5.24.1 python-dateutil-2.9.0.post0 pytz-2026.5 six-1.17.0 tenacity-9.2.1

⚠️ 实测发现 PyPI 0.1.0 依赖 pandas 1.5.3 与 numpy 2.2.6 存在二进制不兼容(import distill 报 ValueError: numpy.dtype size changed,处理见第九章 FAQ)。为保证分析函数可用,实测采用 monorepo 源码安装(会重装新版依赖集):

pip3 uninstall -y flagon-distill
cd /root/flagon/products/distill
pip3 install .
pip3 install 'numpy<2'
Uninstalling flagon-distill-0.1.0:
  Successfully uninstalled flagon-distill-0.1.0
Successfully built flagon-distill
Successfully installed annotated-types-0.8.0 contourpy-1.3.2 cycler-0.12.1 dateparser-1.4.3 flagon-distill-0.1.0 fonttools-4.65.0 kiwisolver-1.5.1 matplotlib-3.10.9 pillow-12.3.0 pksuid-1.1.2 pybase62-0.4.3 pydantic-2.14.1 pydantic-core-2.50.1 pyparsing-3.3.3 regex-2026.9.29 scipy-1.15.3 typing-extensions-4.16.0 typing-inspection-0.4.4 tzlocal-5.4.4
Successfully installed numpy-1.26.4

💡 说明:pip 全局安装会输出 root 用户告警(WARNING: Running pip as the 'root' user...),属预期行为;生产建议在 venv 中执行,本指南为演示简洁直接全局安装。

验证导入(实测回显):

python3 -c "import distill; print('distill import OK')"
pip3 show flagon-distill | head -3
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.

8.2 模拟行为日志上报

收集服务器运行中(第七章已托管或 4.5 已启动)时,用 curl 按标准 UserALE 平铺 JSON 协议逐条 POST。先上报一条正常点击(实测回显):

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

再上报同一目标 4 次快速点击(制造 rage click 行为模式;下为其中两条,其余两条仅 clientTime 依次为 1760030006150/1760030006300,实测逐条返回 200):

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":1760030006000,"microTime":1760030006000.35,"userAction":true,"type":"click","action":"click","target":"BUTTON#broken","user":"demo-user","sessionId":"sess-run-3","tool":"Demo App","version":"2.4.0","pageUrl":"http://localhost:8000/","pageTitle":"Demo Page","location":{"x":320,"y":420},"scrnRes":"1920x1080","details":{}}]'
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":1760030006450,"microTime":1760030006450.35,"userAction":true,"type":"click","action":"click","target":"BUTTON#broken","user":"demo-user","sessionId":"sess-run-3","tool":"Demo App","version":"2.4.0","pageUrl":"http://localhost:8000/","pageTitle":"Demo Page","location":{"x":321,"y":420},"scrnRes":"1920x1080","details":{}}]'
HTTP 200
HTTP 200

收集服务器控制台同步打印了收到的日志(实测回显,输出节选):

tail -22 /root/server.log
UserAle HTTP + WebSocket server running on port 8000
HTTP POST: [
  {
    logType: 'raw',
    clientTime: 1760030006450,
    microTime: 1760030006450.35,
    userAction: true,
    type: 'click',
    action: 'click',
    target: 'BUTTON#broken',
    path: [ [Object] ],
    user: 'demo-user',
    sessionId: 'sess-run-3',
    tool: 'Demo App',
    version: '2.4.0',
    location: { x: 321, y: 420 },
    details: {}
  }
]

8.3 日志落盘验证

收集服务器把日志追加写入启动时间戳命名的 JSON 文件(实测回显):

LOG=$(ls -t /root/flagon/products/userale/example/logs/logs_*.json | head -1)
echo "$LOG"
ls -la "$LOG"
/root/flagon/products/userale/example/logs/logs_1792123964892.json
-rw-r--r-- 1 root root 3295 Oct 16 04:12 /root/flagon/products/userale/example/logs/logs_1792123964892.json

优雅停机后再解析(SIGINT/SIGTERM 会触发收集器补写结尾括号,实测回显):

kill -INT 5314
sleep 6
tail -3 /root/server.log
python3 -c "import json; d=json.load(open('$LOG')); print('records:', len(d))"
Shutting down...
HTTP server closed.
WebSocket server closed.
records: 8

验证要点:文件为合法 JSON 数组且包含全部上报日志;sleep 6 是给写流收尾留出的时间(过短可能读到未补全文件,见第九章 FAQ)。

8.4 Distill 行为分析验证

把生成的日志交给 Distill 做「解析 → 会话摘要 → 挫败感检测 → 图谱」全流程分析(脚本实测回显):

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))

tm = distill.transition_matrix(docs, drop_recursions=True)
print('== transition_matrix:')
print(tm)
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"}]
== transition_matrix:
               BODY  BUTTON#broken  BUTTON#submit  DIV#hero  INPUT#search
BODY            0.0            0.0            0.0       0.0           0.0
BUTTON#broken   1.0            0.0            0.0       0.0           0.0
BUTTON#submit   0.0            0.0            0.0       0.0           1.0
DIV#hero        0.0            1.0            0.0       0.0           0.0
INPUT#search    0.0            0.0            0.0       1.0           0.0

验证要点解读:

  1. extract_logs 把 8 条原始日志全部校验归一化,17 个标准字段齐全;
  2. session_summary 正确统计出 8 次点击、8 秒会话时长、5 个唯一目标、1 个页面;
  3. frustration_score 给出 65.0 分(severity=high),detect_rage_clicks 精确检出 BUTTON#broken 上 1 秒内 3 次连续点击(默认阈值 threshold=3、窗口 window_ms=1000),source: inferred 表示由原始点击流推断而非前端显式上报;
  4. transition_matrix 输出目标间一阶转移概率矩阵,可直接喂给漏斗/桑基图可视化。

至此端到端链路全部验证通过。日常使用中,把 8.2 节的 curl 替换为真实浏览器流量(页面埋点自动上报)即可持续收集。

九、常见问题(FAQ)

Q1:node example/server/server.js 报 EADDRINUSE: address already in use :::8000?

端口已被占用(实测多为上一个实例未退干净)。实测错误现象:

Uncaught exception: Error: listen EADDRINUSE: address already in use :::8000
    at Server.setupListenHandle [as _listen2] (node:net:1908:16)
    at listenInCluster (node:net:1965:12)

定位与解决:先精确找出占用进程再终止(实测输出):

pgrep -af 'server\.js'
pkill -f 'server\.js'
sleep 2
ps -ef | grep [n]ode
5527 node example/server/server.js
no node process left

注意:pkill -f 'node server.js' 匹配不到以长路径启动的进程(node example/server/server.js 不含该子串),要么用 pgrep -af 先核对再按 PID kill,要么用匹配性更强的模式如 server\.js。

Q2:停机后 json.load 日志文件报 Expecting ',' delimiter?

收集器运行期间日志文件始终处于打开状态、缺少结尾 ],只有优雅停机(SIGINT/SIGTERM)才会补写。若停机等待过短,进程可能在写流 flush 完成前退出。实测现象与处理:

python3 -c "import json; json.load(open('$LOG'))"
echo "]" >> "$LOG"
python3 -c "import json; print('records:', len(json.load(open('$LOG'))))"
json.decoder.JSONDecodeError: Expecting ',' delimiter: line 9 column 148 (char 1365)
records: 8

根治办法:停机后等待足够时间再解析(实测 sleep 6 后文件必然完整),或对文件做结尾括号检查后再消费。

Q3:import distill 报 ValueError: numpy.dtype size changed, may indicate binary incompatibility?

PyPI 的 flagon-distill 0.1.0 锁定 pandas<2.0(实测装出 1.5.3),与最新 numpy 2.x 二进制不兼容。实测修复(任选其一):

pip3 install 'numpy<2'
Successfully installed numpy-1.26.4

降级 numpy 后实测导入恢复正常(pandas 1.5.3 与 numpy 1.26.4 兼容);或按 8.1 节改用 monorepo 源码安装(新版依赖集已避开该组合)。若源码安装后 plot_click_heatmap 渲染报 pandas 相关 ImportError,同样先 pip3 install 'numpy<2' 统一版本即可。

Q4:本机 curl 访问收集服务器返回 502?

服务器设置了 http_proxy 环境变量时,curl 会把 localhost 请求也发给代理。实测现象与解决:

curl -s -o /dev/null -w 'GET / -> HTTP %{http_code}\n' http://localhost:8000/
curl -s --noproxy '*' -o /dev/null -w 'GET / -> HTTP %{http_code}\n' http://localhost:8000/
GET / -> HTTP 502
GET / -> HTTP 200

或对当前会话取消代理变量:unset http_proxy https_proxy。

Q5:pnpm install 缓慢或超时?

国内网络访问 npm registry 慢时,走 HTTP 代理或换 registry 镜像(实测走代理约 2 分钟完成):

export http_proxy=http://192.168.1.62:7897 https_proxy=http://192.168.1.62:7897
pnpm install
Done in 2m 8.1s using pnpm v9.15.9

十、卸载与清理

逐项清理(按部署方式选择执行,实测回显合并展示):

systemctl stop flagon-collector
systemctl disable flagon-collector
rm -f /etc/systemd/system/flagon-collector.service
systemctl daemon-reload
Removed /etc/systemd/system/multi-user.target.wants/flagon-collector.service.

删除源码与数据目录(实测路径):

rm -rf /root/flagon /root/flagon-repo /root/uale-npm /var/www/uale-site
rm -f /root/server.log /root/analyze.py

卸载 Python 分析端与全局工具:

pip3 uninstall -y flagon-distill
npm uninstall -g pnpm
apt-get purge -y nodejs && apt-get autoremove -y
Successfully uninstalled flagon-distill-0.1.0
removed 1 package in 161ms
After this operation, 196 MB disk space will be freed.
(Reading database ... 22663 files and directories currently installed.)
Removing nodejs (20.20.2-1nodesource1) ...
0 upgraded, 0 newly installed, 0 to remove and 3 not upgraded.

清理验证:

node -v
bash: node: command not found

十一、参考链接