# LView **Repository Path**: sunmingze123/lview ## Basic Information - **Project Name**: LView - **Description**: No description available - **Primary Language**: Python - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-19 - **Last Updated**: 2026-09-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # LView > **L**ight **View** · 一个面向运维场景的轻量级日志查看系统 LView 由前后端两个子项目组成: | 子项目 | 路径 | 技术栈 | 角色 | |---|---|---|---| | **LView-Server** | [`LView-Server/`](./LView-Server) | FastAPI + DuckDB + Watchdog | 目录树、磁盘直读 grep / tail、采集入库、API | | **LView-UI** | [`LView-UI/`](./LView-UI) | React 19 + TypeScript 5 + Vite 8 + Ant Design 6 | 数据源浏览 / 内容检索 / 实时 tail / 全库查询 | ## 架构总览 后端有**两条互不依赖的数据通道**——这是理解 LView 的关键: ``` ┌───────────────────────────────────────────────────────────────┐ │ LView-UI (浏览器 / 静态服务器 / Nginx) │ │ 「数据源」页:内容检索 + 目录卡片 + 文件抽屉(tail -f) │ │ 「查询日志」页:已入库日志多维筛选 + 分页 │ └──────────────────────────┬────────────────────────────────────┘ │ HTTP + WebSocket(CORS / Nginx 反代) ▼ ┌───────────────────────────────────────────────────────────────┐ │ LView-Server (FastAPI, 127.0.0.1:8000) │ │ │ │ ① 磁盘直读通道 —— 不依赖入库,文件在盘上就能查 │ │ GET /logs/grep 多关键词 grep + 行内事件时间过滤 │ │ GET /logs/file/tail 字节窗口读文件(头 / 尾 / 上滚 / 跳行)│ │ GET /tree /tree/stats 目录树与聚合统计 │ │ │ │ ② 采集入库通道 —— 面向「查询日志」页的全库检索 │ │ Watchdog ──→ Parser ──→ DuckDB ──→ /logs/search /facets │ │ WS /logs/tail/{path} 推送建连之后新写入的行 │ └──────────────────────────┬────────────────────────────────────┘ │ 只读本地文件 ▼ WATCH_PATHS 下匹配 LOG_GLOB_PATTERNS 的文件 ``` > 「数据源」页走通道 ①,所以**刚从生产环境下载到本地的历史日志,放进 `WATCH_PATHS` > 就能立刻检索**,不必等采集器入库。「查询日志」页走通道 ②,查的是 DuckDB 里的数据。 ## 核心特性 - 🔍 **内容检索(grep 式)** —— 多关键词 AND + 多词 OR 排除,三种匹配模式(包含 / LIKE / 正则), 按**行内事件时间**而非文件 mtime 过滤时间范围,结果按事件时间倒序 - 📂 **类 SFTP 浏览** —— 每个日志根一张卡片,目录按 mtime 倒序(最近写入的排最前), 自动标记该根下最新的日志文件 - 📜 **抽屉式看文件** —— 打开即从文件头读起,向上滚动无限加载更早内容, grep 命中可一键跳到对应行号并高亮;抽屉内还能对当前文件再做一次内容检索 - ⚡ **实时 tail** —— WebSocket 推送新写入的行,自动重连 + 指数退避,可随时暂停跟随 - 🧩 **多行日志合并** —— 异常堆栈、缩进上下文自动拼回上一条,不再被切成碎片 - 🗃 **全库查询** —— 时间范围 / 级别 / 服务 / 关键词 / 排除词 + 分页,虚拟滚动渲染 - 🪶 **零外部依赖** —— DuckDB 单文件 + Python 单进程,无 Redis / ES / Kafka - 🧹 **自动清理** —— 默认按 2 GB / 30 天滚动,也可手动触发 - 🔐 **HTTP Basic** —— 全 API 鉴权(`AUTH_ENABLED` 开关,默认关闭便于本地开发) - 🌗 **亮 / 暗主题** —— 跟随系统,手动切换后记忆偏好 - 📑 **自动 OpenAPI** —— `http://server:8000/docs` 即用 ## 快速开始 ### 1. 启动后端 ```bash cd LView-Server # Linux / macOS ./run.sh # Windows run.bat ``` `run.sh` / `run.bat` 会用 `uv` 按 `uv.lock` 同步依赖到 `.venv`,然后拉起 uvicorn。 启动后访问: - API 根:`http://127.0.0.1:8000/` - Swagger UI:`http://127.0.0.1:8000/docs` - ReDoc:`http://127.0.0.1:8000/redoc` 默认 `AUTH_ENABLED=false`,本地开发免登录。生产部署务必在 `.env` 里改成 `true` 并设置强密码。 ### 2. 启动前端 ```bash cd LView-UI # 安装依赖(推荐 pnpm) pnpm install # 开发模式(Vite 已把 /api、/docs、/openapi.json 代理到 127.0.0.1:8000) pnpm dev # 生产构建 pnpm build # 产物在 dist/ pnpm preview # 本地预览 ``` 打开 `http://localhost:5173` 即可使用。若后端启用了鉴权,凭据存在 `localStorage.LVIEW_BASIC_AUTH`(`btoa("user:pass")`)。 ### 3. 配置要监控的日志目录 编辑 `LView-Server/.env`: ```env # Linux WATCH_PATHS=["/opt/apps","/var/log/myapp"] # Windows 用正斜杠 # WATCH_PATHS=["D:/logs","C:/Users/me/Downloads/log"] LOG_GLOB_PATTERNS=["**/*.log","**/*.txt","**/*.out"] ``` 要监控微服务 jar 包状态,可在 `.env` 预设 jar 根目录(也可在前端「服务监控」页运行时增删): ```env JAR_PATHS=["/opt/apps","/srv/microservices"] ``` 改完重启后端,前端点右上「重新扫描」即可看到新的日志根。 ## 两个页面怎么用 | 页面 | 适用场景 | 数据来源 | |---|---|---| | **数据源** | 「我把生产日志下载下来了,想翻一翻 / 搜一搜」 | 磁盘直读,不经过 DB | | **查询日志** | 「这台机器上的日志一直在采,我要跨文件按级别/服务查」 | DuckDB 中已入库的数据 | | **服务监控** | 「这台机器上的微服务 jar 还活着吗?谁挂了?」 | psutil 同机扫 jar 进程 | 数据源页的检索栏:关键词多个之间是 **AND**,排除词多个之间是 **OR 排除**; 匹配模式选「正则」时关键词按 Python 正则解释。时间范围按每行解析出的时间戳过滤, **解析不出时间戳的行在设置了时间范围时会被跳过**。 ## API 一览 完整定义见 Swagger UI(`/docs`)。前缀均为 `/api/v1`。 | 方法 | 路径 | 鉴权 | 说明 | |---|---|---|---| | GET | `/` | 否 | 服务自检 | | GET | `/logs/grep` | 是 | 磁盘直读 grep:`q[]` / `exclude[]` / `q_mode` / 时间范围 / `file_type` / `path` | | GET | `/logs/file/tail` | 是 | 字节窗口读文件:`tail` / `before_line` / `start_line` / `bytes` / `max_lines` | | POST | `/logs/search` | 是 | 已入库日志多维检索 + 分页 | | GET | `/logs/facets` | 是 | 服务 / 级别计数 + 数据整体时间范围 | | GET | `/logs/files` \| `/levels` \| `/services` | 是 | 已入库的去重字典 | | WS | `/logs/tail/{file_path:path}` | 三种方式 | 实时 tail(只推建连之后的新行) | | GET | `/tree?path=&depth=` | 是 | 目录树(`depth` ≤ 12,节点预算 5000) | | GET | `/tree/stats?path=` | 是 | 子树目录数 / 文件数 / 总字节 | | GET | `/services/jar/dirs` | 是 | 受监控的 jar 根目录列表(env + runtime 合并) | | POST | `/services/jar/dirs` | 是 | 添加运行时监控目录(必须存在) | | DELETE | `/services/jar/dirs?path=` | 是 | 移除运行时监控目录(env 不可移除) | | GET | `/services/jar/status` | 是 | 扫描所有受监控目录下 *.jar,返回每个 jar 的运行状态 | | GET | `/system/stats` | 是 | DB 体积 / 行数 / 时间范围 | | POST | `/system/cleanup` | 是 | 手动触发保留策略清理 | | GET | `/system/health` | 否 | 健康检查(探针用,不鉴权) | **预算保护**:grep 单次最多扫 5000 个文件、单文件最多读 32 MB、命中上限 `limit`(≤5000); 目录遍历节点预算 5000。任一预算耗尽时响应里 `truncated=true`,前端会提示结果已截断。 ## 目录结构(仓库根) ``` LView/ ├── README.md ← 你正在看 ├── DEPLOY.md ← 生产部署逐步指南(Nginx / systemd / 证书 / 备份) ├── LView-Server/ ← 后端(详见 LView-Server/README.md) │ ├── app/ │ │ ├── api/v1/ # logs.py / tree.py / system.py │ │ ├── core/ │ │ │ ├── collector.py # Watchdog 采集 + 断点续传 + 轮转检测 │ │ │ ├── grep.py # 磁盘直读 grep 引擎 │ │ │ ├── parser.py # 采集链路在用的解析器(JSON / 纯文本) │ │ │ ├── parsing/ # 独立解析层:logback / grok / regex / logfmt / json │ │ │ └── storage.py # DuckDB 单例、检索、清理 │ │ ├── models/ # Pydantic schemas + domain │ │ └── services/cleanup.py │ ├── data/ # DuckDB 文件(运行时创建) │ ├── deploy/lview.service # systemd 单元示例 │ ├── tests/ │ ├── run.sh / run.bat │ └── README.md └── LView-UI/ ← 前端(详见 LView-UI/README.md) ├── src/ │ ├── api/ # axios 实例 + 各业务 API │ ├── components/ # RootCard / GrepSearchBar / GrepResults / │ │ # LogFileDrawer / RealtimeViewer / LogViewer … │ ├── hooks/ # useLogs / useWebSocket │ ├── pages/ # DataSourcePage / QueryPage │ └── theme.tsx # 亮暗主题 + AntD ConfigProvider ├── vite.config.ts └── README.md ``` ## 开发与测试 ```bash # 后端(28 个测试:smoke / grep 20 + parsing 8) cd LView-Server uv sync uv run python -m pytest -q # 前端 cd LView-UI pnpm lint # biome check pnpm format # biome format --write pnpm build # tsc -b && vite build ``` > ⚠️ 后端测试请用 `uv run python -m pytest`。直接 `uv run pytest` 时 > `tests/parsing/test_parsing.py` 会因缺少 `tests/parsing/__init__.py` 而收集失败 > (`ModuleNotFoundError: No module named 'app'`)。 ## 部署 完整步骤见 **[DEPLOY.md](./DEPLOY.md)**。要点: - 后端必须部署在能读到业务日志的机器上,只监听 `127.0.0.1:8000`,**不要直接暴露公网** - 由 Nginx 统一对外:静态文件 + `/api` 反代 + WebSocket `Upgrade` 头 + HTTPS - `LogCollector` 是进程内单例,跨进程不共享 —— 生产固定 `--workers 1` - 前端三种部署姿势: | 姿势 | 说明 | |---|---| | **本地打开** | `pnpm build` 后把 `dist/` 拷到本机,配 `VITE_API_BASE_URL` 指向后端 | | **集中式 Nginx** | `dist/` 放内网 Nginx,所有人访问同一 URL,`/api` 反代到后端(推荐) | | **Docker** | 后端见 `LView-Server/Dockerfile` | ## 安全清单 - [ ] 生产环境 `AUTH_ENABLED=true`,并改掉默认 `admin` / `lview123` - [ ] 后端只监听 `127.0.0.1`,对外走 Nginx + HTTPS(Basic Auth 是明文,必须靠 TLS 保护) - [ ] `CORS_ORIGINS` 收窄为前端实际域名,不要在生产留 `["*"]` - [ ] `WATCH_PATHS` 只放应用日志目录 —— 这些路径下的文件内容会通过 API 可读 - [ ] WebSocket 的匿名兜底(5 秒内不发消息即匿名连接)仅供本地调试,生产环境请靠鉴权与网络隔离堵住 - [ ] Nginx 层加 `allow / deny` 白名单或再叠一层 OAuth - [ ] 日志目录权限最小化(后端进程只需读权限) - [ ] 定期备份 `data/lview.duckdb` ## 已知限制 - **全文检索不是倒排索引**:`/logs/search` 走 DuckDB 的 `contains()` / `LIKE` / `regexp_matches()` 子串匹配 + 天分区裁剪,不是 FTS5 / BM25,也没有相关度评分 - **`app/core/parsing/` 尚未接入采集链路**:多格式解析层(logback / grok / regex / logfmt / json、 多行折叠、关键字抽取、GBK / UTF-8 编码探测)已实现且有单测覆盖,但采集器目前仍走 `app/core/parser.py`。接入后 `/logs/search` 的 level / service 识别率会明显提升 - **service 推断靠目录名**:取日志文件的父目录名,例如 `/apps/order-svc/logs/x.log` → `service="logs"`。 想让 service 等于 `order-svc`,就把日志直接放在 `/apps/order-svc/` 下 - **两条通道的时间口径不同**:grep 命中行的时间戳由 `grep.py` 归一化为 naive UTC; 入库路径在行内解析不到时间戳时会回落到服务器本地时间(`datetime.now()`) - **多 worker 不支持**:实时 tail 依赖进程内订阅,多进程下会丢事件 ## Roadmap - [ ] 把 `app/core/parsing/` 接进采集链路,支持 `.env` 指定日志格式 - [ ] 导出选中日志为 CSV / JSON - [ ] 告警规则(关键字 / 频次阈值 + Webhook) - [ ] 多 worker 进程间广播(Redis / NATS) - [ ] 用户管理 + OIDC,取代单一 Basic 凭据 - [ ] Prometheus `/metrics` ## License MIT