catalingrigoriev285/issueflow
GitHub: catalingrigoriev285/issueflow
一个自托管、框架无关的错误追踪器和轻量级 SIEM,通过统一 HTTP API 收集异常并提供分组、回归检测、仪表板和审计追踪功能。
Stars: 0 | Forks: 0
# IssueFlow
**一个自托管、框架无关的错误追踪器和轻量级 SIEM。** 通过 HTTP API 从*任何*语言或技术栈中收集异常,然后对其进行分流、**记录**和**解决** —— 这是一个更精简的 GlitchTip/Sentry,具备 issue 分组、回归检测、仪表板和审计追踪功能。将你自己的 **Markdown 报告**附加到任何事件中,IssueFlow 会将其原样渲染。内置提供一流的 Laravel 支持。
- **`backend/`** — Express.js + TypeScript REST API。负责管理 MongoDB、JWT 认证、ingestion pipeline、issue 分组/去重、RBAC 以及所有业务逻辑。
- **`frontend/`** — Next.js (App Router) + Tailwind UI。仅通过 HTTP 与后端通信。
- **`laravel-package/`** — 用于你的 Laravel 应用的即拷即用型上报器。
```
Any app ──POST /api/v1/ingest──▶ Express backend ──▶ MongoDB
(Laravel / Node / Python / …) ▲
Next.js UI ────────┘ (httpOnly JWT cookie)
```
**两种 payload 结构,一个 endpoint。** ingestion API 会自动检测你发送的内容:**Sentry 风格的 envelope**(`exception.values[]`,即 Laravel 包发出的格式)*或*扁平的**通用/自定义错误**(`type`, `message`, `file`, `line`, `stacktrace`, `request`, `user`, …)。无论哪种方式,IssueFlow 都会渲染 Markdown 报告 —— 或者显示你随其发送的预渲染 `markdown` 内容。
## 核心亮点
- **多项目** — 每个 Laravel 应用/环境都有自己专属的 ingestion API key。
- **安全认证** — 存储于 httpOnly + SameSite cookie 中的 JWT(由 Express 签发,在 Next.js edge 端验证)、CSRF 双重提交、RBAC(`admin` / `analyst` / `viewer`)、防止 NoSQL 注入的查询、经过 HMAC 哈希处理的 API key、速率限制。
- **SIEM 风格分流** — 确定性的 issue 分组、去重、**回归检测**、状态标记(未解决 / 已解决 / 已忽略)、文档笔记,以及只追加的活动/审计日志。
- **仪表板** — 随时间变化的事件量、严重程度分布、热门 issue。
## 前置条件
- **Node.js 22+**(在 26 上也可运行)
- **MongoDB 6+** 在本地运行(或使用下文提到的 Docker)
## 快速开始(本地,不使用 Docker)
**1. 启动 MongoDB**(任何运行在 `27017` 端口的本地实例)。使用项目本地数据目录的示例:
```
mkdir -p .mongodb-data
mongod --dbpath ./.mongodb-data --port 27017 --bind_ip 127.0.0.1
```
**2. 配置环境变量** — 示例文件已经包含了可用的开发默认值。确保 `JWT_SECRET` 在两个服务中**完全一致**:
```
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.env.local
# 在 backend/.env 和 frontend/.env.local 中设置相同的 JWT_SECRET
```
**3. 后端** — 安装、种子数据填充、运行:
```
cd backend
npm install
npm run seed # creates the super-admin + a demo project and PRINTS an ingestion key (copy it!)
npm run dev # http://localhost:4000
```
**4. 前端** — 安装并运行:
```
cd frontend
npm install
npm run dev # http://localhost:3000
```
**5. 打开** http://localhost:3000 并使用 `ADMIN_EMAIL` / `ADMIN_PASSWORD` 登录
(默认值:`admin@issueflow.local` / `ChangeMe_Str0ng!Pass`)。
## 快速开始(Docker)
```
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.env
# 在两者中都设置相同的 JWT_SECRET,并在 backend/.env 中设置 MONGODB_URI=mongodb://mongo:27017/issueflow
docker compose up --build
docker compose exec backend npm run seed # prints the demo ingestion key
```
UI 运行在 http://localhost:3000,API 运行在 http://localhost:4000。
## 冒烟测试 ingestion API
使用 `npm run seed` 打印出的 key:
```
curl -X POST http://localhost:4000/api/v1/ingest \
-H "Content-Type: application/json" \
-H "X-IssueFlow-Key: iflow_xxxxxxxxxxxx_yyyyyyyy..." \
-d '{"level":"error","transaction":"App\\Http\\Controllers\\OrderController@store",
"exception":{"values":[{"type":"RuntimeException","value":"Undefined array key id",
"stacktrace":{"frames":[{"filename":"app/Http/Controllers/OrderController.php",
"function":"store","lineno":42,"in_app":true}]}}]}}'
```
→ `202 { "issueId": "...", "fingerprint": "...", "status": "unresolved", "isNew": true }`。
再次发送 → 发生次数将会增加,并且不会创建新的 issue。在 UI 中刷新 **Issues** 即可看到它。将其标记为已解决,再发送另一个事件 → 它会翻转为 **regressed**(回归)状态。
## 通用 / 自定义错误(任何语言)
你不需要 Sentry envelope。任何服务都可以将**扁平错误** POST 到同一个 endpoint —— 发送你已有的内容,IssueFlow 会填补剩余部分。除 payload 必须携带错误(`type`/`message`/`stacktrace`)*或* `message` 之外,所有字段都是可选的。
```
curl -X POST http://localhost:4000/api/v1/ingest \
-H "Content-Type: application/json" \
-H "X-IssueFlow-Key: iflow_xxxxxxxxxxxx_yyyyyyyy..." \
-d '{
"service": "checkout-api",
"environment": "production",
"level": "critical",
"type": "App\\Exceptions\\PaymentFailed",
"message": "Card declined",
"code": 402,
"file": "app/Services/Billing.php",
"line": 88,
"previous": "PDOException: server has gone away",
"stacktrace": "#0 app/Services/Billing.php(88): charge()\n#1 app/Http/OrderController.php(31): store()",
"request": { "method": "POST", "url": "https://shop.test/pay", "ip": "9.9.9.9", "user_agent": "curl/8" },
"user": { "Id": 42, "Name": "Ada", "Email": "ada@shop.test" },
"tags": { "region": "eu-west" }
}'
```
**接受的字段**(括号内为宽松的别名):
| 字段 | 备注 |
|---|---|
| `type` (`exception`, `error.type`) | 异常/错误类 → issue 标题和分组 |
| `message` (`error`, `error.message`) | 人类可读的信息 |
| `code`, `file`, `line`, `previous` | 位置与起因 |
| `stacktrace` (`trace`) | 原始**字符串** *或* `{ file, line, function }` 帧数组(从最旧到最新) |
| `level` (`severity`), `environment`, `release` (`version`) | `level` 映射到 `fatal/error/warning/info/debug` |
| `service` (`app`), `server_name`, `transaction` (`culprit`) | 来源身份 |
| `request` | `{ method, url, ip, referer, user_agent, query_string, headers }` |
| `user` | 任何 `label → value` 映射(渲染为表格) |
| `tags` | 对象 `{k:v}` *或* 数组 `[{key,value}]` |
| `context` / `extra`, `contexts`, `fingerprint` | 直接透传;`fingerprint` 会覆盖分组逻辑 |
| `markdown` | **预渲染的** GitHub 风格 Markdown 报告 —— 原样显示 |
### 自带 Markdown
如果你已经格式化了丰富的错误报告(例如,你发布到 Telegram 或 TheHive 的那些报告),将该字符串作为 `markdown` 发送,IssueFlow 会将其**逐字**渲染在 issue 的 **Report**(报告)面板中 —— 表格、代码块、粗体、链接等全部保留。你发送什么布局,就得到什么布局;IssueFlow 不会强加自己的模板。没有携带 `markdown` 字段的事件仅是没有 Report 面板而已 —— 结构化的 **Overview**(概览,包含 stack trace、request、user、tags)依然会显示所有内容。
```
curl -X POST http://localhost:4000/api/v1/ingest \
-H "X-IssueFlow-Key: iflow_..." -H "Content-Type: application/json" \
-d '{ "type": "RuntimeException", "message": "boom",
"markdown": "### 🚨 RuntimeException\n\n| Field | Value |\n|---|---|\n| Env | `production` |" }'
```
## 连接真实的 Laravel 应用
请参阅 [`laravel-package/README.md`](laravel-package/README.md) —— 复制两个文件,设置两个环境变量,注册一个 `reportable()` 钩子。
## 项目结构
```
backend/ Express + TS API (models, ingestion, auth, routes, seed, tests)
frontend/ Next.js App Router UI (design system, shell, dashboard, issues, projects, admin)
laravel-package/ Copy-paste Laravel reporter
shared/contracts/ Canonical DTO types synced into both services
docker-compose.yml + .override.yml
```
## 角色
| 角色 | 权限 |
|---|---|
| **viewer** | 读取 issue、事件、仪表板 |
| **analyst** | + 分流:解决 / 忽略 / 分配 / 评论 / 批量操作 |
| **admin** | + 管理项目、ingestion key 和用户 |
初始化的超级管理员受到保护 —— 其他管理员无法降级或禁用它。
## 测试
```
cd backend && npm test # Vitest + Supertest (grouping, jwt, api-keys, ingest, auth) — 26 tests
```
## 安全说明
- JWT 使用 `jose` 签名(HS256 共享密钥用于开发环境)。**投入生产前:** 切换到 EdDSA/RS256(Express 持有私钥,前端仅获取公开验证密钥),设置 `COOKIE_SECURE=true`,在反向代理后将两个服务部署在同一父域名下,将 rate-limit store 迁移至 Redis,并设置强大的非默认 `ADMIN_PASSWORD` / `JWT_SECRET` / `INGEST_KEY_PEPPER`。
- Ingestion key 以 HMAC-SHA256(secret, pepper) 的形式存储;明文仅显示一次。
- 所有用户提供的查询输入在接触 MongoDB 之前都会被验证为基本类型(无操作符注入)。
- 原始事件带有 TTL (`retentionDays`);issue 聚合数据和审计日志将无限期保留。
标签:Express.js, Laravel, MongoDB, 自动化攻击, 请求拦截, 运维监控, 错误追踪