# 状态与日志

## 状态目录在哪

当前 Linux 用户的 `~/.bm2`。`HOME` 不可用时回退到 `/tmp/bm2-<uid>`。每个用户一个守护进程，管理全部已注册项目。

这个路径是固定的，配置无法改变它：bm2 按 Linux 用户划分管理范围，而一个可以被指到别处的守护进程，会心安理得地把同一批应用管上两遍。

## 目录布局

```text
daemon.endpoint                     # 控制通道端点：本机端口 + 共享令牌（目录权限 0700）
bm2d.lock                           # 同一时刻只有一个守护进程；持有者退出即释放，SIGKILL 也不例外
bm2d.pid                            # 守护进程 PID
bm2.events.jsonl                    # CLI 连接与重试事件
bm2d.log                            # 守护进程 stderr 与运行时诊断
bm2d.events.jsonl                   # 守护进程与监督事件
<name>/project.json                 # 一个项目的登记信息
<name>/<name>-<id>.json             # 持久化的实例状态
<name>/logs/<name>-<id>.out.log     # 应用 stdout
<name>/logs/<name>-<id>.error.log   # 应用 stderr
<name>/logs/<name>-<id>.crash.log   # 异常退出诊断
```

目录以 `0700` 创建，因为里面放着授权控制守护进程的令牌。

## 两类日志，严格分开

`<name>/logs/` 下的三份日志属于**应用**，由应用自己写入。顶层的三份属于 **bm2**，记录管理侧做了什么。分开的好处很直接：对应用日志 `tail -f` 读到的全是应用输出，而管理侧的历史小到可以长期保留。

出问题时这个划分尤其重要：应用崩了，它写自己的 `.error.log` 与 `.crash.log`。而「要不要重启、为什么」记在 `bm2d.events.jsonl` 里。

## 轮转

单个文件超过 `[logs] max_size_mb` 即轮转一次，`[logs] keep` 决定保留几代归档。默认的 `keep = 0` 保留所有代数，所以磁盘占用的唯一上界就是你设的那个。

轮转后的文件在同一目录里，带数字后缀：`api-0.out.log.1` 是最新的一份归档，`.2` 更旧，依次类推。

守护进程自身的日志不属于任何项目，因此不受任何项目的 `[logs]` 支配，统一用内置策略。

两种轮转方式都用上了，用哪一种取决于文件被谁握着。应用的 stdout 与 stderr 由应用自己持有 fd，所以先把内容拷出来再原地截断——改名会让应用继续往旧 inode 写，而新文件永远是空的。bm2 自己写的日志走改名，更省也更不丢东西。

## 怎么看日志

实时跟随某个实例。

```bash
tail -f ~/.bm2/api/logs/api-0.out.log
```

一次看某个项目的全部标准输出。

```bash
tail -f ~/.bm2/api/logs/*.out.log
```

只看错误。

```bash
tail -50 ~/.bm2/api/logs/api-0.error.log
```

看崩溃原因。

```bash
cat ~/.bm2/api/logs/api-0.crash.log
```

`bm2 dev` 会把当前实例的日志直接跟到终端，调试时不必再开第二个窗口。

## 该留多少

按项目量估：假设一个实例每秒写 `2 KB` 日志，一天约 `170 MB`。

`max_size_mb = 100` 配 `keep = 7` 意味着单实例最多占约 `800 MB`，七个归档加当前文件。

只想留近期历史就把 `keep` 设小，比如 `3`。

`keep = 0` 表示归档代数不限，磁盘只受写入量和 `max_size_mb` 影响。

想给磁盘一个明确上界，就给 `keep` 一个具体数字。

上面那组参数的总占用可以这样估：`max_size_mb × (keep + 1)`，再乘以实例数。

## 清理

日志属于项目，随项目走。

`bm2 remove <name>` 保留日志，`bm2 remove <name> -f` 连状态目录一起删。

只想清空内容不清目录，可以直接清空文件。

```bash
: > ~/.bm2/api/logs/api-0.out.log
```

直接删文件也可以，应用的下一次写入会重新创建它，代价是那一行可能落在新文件里、也可能落在旧 inode 上，取决于轮转的实现路径。最稳的方式还是让轮转去处理。

## 实例状态文件的用途

`<name>/<name>-<id>.json` 记录守护进程把一个实例重新接上所需的东西：pid、启动时间、重启次数、最后一次退出的原因。

它的用途只有一个——守护进程重启之后，新守护进程收养仍在运行的实例，而不是把它们再启动一份。它不记录日志、不记录环境变量值、不记录配置内容。

## 事件日志

两个 `*.events.jsonl` 每行一个 JSON 对象。它们只记录管理元数据：时间戳、事件名、适用时的应用/实例/PID，以及运维原因。

它们**不**包含环境变量值、协议载荷、应用输出，也不包含通知凭据。

```bash
tail -f ~/.bm2/bm2d.events.jsonl      # 盯着守护进程
jq -c . ~/.bm2/bm2d.events.jsonl      # 按字段过滤
```

崩溃原因与状态异常的定位见[排障与常见问题](/guide/troubleshooting)。
