Skip to content

监控与日志

EcoCtrl 提供多层次的运行状态监控和结构化日志输出,帮助运维团队及时发现和处理异常。

SSE 实时连接监控

服务端使用 Server-Sent Events (SSE) 向已认证的客户端推送实时数据。

心跳机制

每个 SSE 连接以 30 秒间隔发送心跳信号:

javascript
// packages/server/src/routes/events.ts
const HEARTBEAT_INTERVAL_MS = 30000; // 30 秒

const heartbeat = setInterval(() => {
  reply.raw.write(":ping\n\n"); // SSE 心跳注释
}, HEARTBEAT_INTERVAL_MS);

心跳是标准的 SSE 注释行(以 : 开头),客户端可以通过监听 onopen 和检测心跳超时判断连接状态。

连接生命周期

阶段触发时机日志事件
连接建立客户端 GET /api/events?token=<sse-token>SSE connection <id> opened for user <userId>
首次 ping连接建立后立即客户端收到 {"type":"ping","payload":{"message":"connected"}}
定期心跳每 30 秒:ping\n\n
连接关闭客户端断开或心跳写入失败SSE connection <id> closed

监控 SSE 连接状态

bash
# 查看当前连接的 SSE 连接数(通过日志统计)
docker compose logs server | grep "SSE connection" | grep "opened" | wc -l

# 监控连接断开异常
docker compose logs server --tail 100 | grep "SSE connection.*closed"

SSE 连接令牌

SSE 使用短寿命 JWT(30 秒过期)进行认证:

  • 客户端通过 POST /api/events/token 获取令牌
  • 令牌仅限 SSE 连接使用(purpose: "sse"
  • 连接建立后再无额外认证开销

照明控制 Mock Fallback 告警

照明控制模块在 IoT 服务不可用时会自动降级到 Mock 模式——使用进程内内存存储替代真实的 BACnet 设备点状态。

触发条件

typescript
// packages/server/src/routes/lighting.ts
try {
  const raw = await readPointValues(pointIds);
  return parseIotResponse(raw);
} catch (err) {
  console.warn("[lighting] IoT read failed, using mock fallback:", (err as Error).message);
  // 返回 mock 值
}

告警信号

当控制台输出以下日志时,表示 IoT 服务异常:

[warn] [lighting] IoT read failed, using mock fallback: <error message>
[warn] [lighting] IoT write failed, using mock fallback: <error message>

应对措施

步骤操作
1检查 IoT 网关服务状态(ping <BASE_URL>
2检查 BASE_URL / APP_ID 环境变量配置
3确认 IoT 令牌缓存未过期
4查看 docker compose logs server | grep iot 获取详细错误

pg-boss 任务队列监控

后台任务通过 pg-boss 管理,所有任务在 PostgreSQL 的 pgboss.* 表中持久化。

检查队列深度

sql
-- 连接到数据库后查询
SELECT name, state, count(*)
FROM pgboss.job
GROUP BY name, state
ORDER BY name, state;

队列状态说明

状态说明需关注
created已创建等待处理正常
active正在处理中正常
completed已完成正常
failed已失败(超过重试次数)
expired超时未完成
cancelled已取消

监控队列积压

bash
# 直接通过 docker 查询
docker compose exec postgres psql -U ecoctrl -d ecoctrl -c "
  SELECT state, count(*) as count
  FROM pgboss.job
  WHERE name = 'workflow.execute'
  GROUP BY state;
"

大量 created 任务堆积或 failed 任务持续出现时,需要检查 Worker 进程和队列配置。

pino JSON 日志

服务端使用 pino 日志框架,所有日志以 JSON on stdout 格式输出,可直接对接各种日志聚合系统。

日志级别

env
# .env.local 配置
LOG_LEVEL=info           # debug | info | warn | error
LOG_DESTINATION=stdout   # stdout | file | both
LOG_DIR=./logs           # 文件模式时的日志目录
LOG_PRETTY=true          # 仅开发模式有效

日志格式示例

json
{"level":30,"time":1704067200000,"pid":1,"hostname":"abc123","name":"sse","msg":"SSE connection abc-123 opened for user 42"}
{"level":40,"time":1704067260000,"pid":1,"hostname":"abc123","name":"lighting","msg":"[lighting] IoT read failed, using mock fallback: connect ECONNREFUSED"}
{"level":50,"time":1704067320000,"pid":1,"hostname":"abc123","name":"queue","msg":"[pg-boss] connection error","err":{"message":"..."}}
Level 值级别说明
30INFO常规运行状态
40WARN可恢复的异常(如 Mock Fallback)
50ERROR需要人工关注的问题

日志聚合方案

标准 JSON-on-stdout 格式可与以下系统无缝对接:

方案接入方式
Loki使用 Promtail 或 Docker 日志驱动采集
ELK使用 Filebeat 或 Logstash 采集容器 stdout
Datadog使用 Datadog Agent 自动采集 Docker 日志
Papertrail配置 Docker 日志驱动 --log-driver syslog
自建直接 docker compose logs -f 定向到文件或管道

日志轮转

LOG_DESTINATION=file 时:

env
LOG_ROTATE_INTERVAL=1d    # 日志轮转间隔(1d / 7d / 1h)
LOG_MAX_DAYS=30           # 日志保留天数

日志文件位于容器 ./logs 目录,通过 Docker 卷映射到宿主机 docker/ecoctrl/server_logs/

健康检查端点

端点方法说明
/healthGET服务存活检查(返回 200 OK)
/api/configs/verify-smtpPOSTSMTP 连接验证(详见 SMTP 配置)

健康检查由 Docker Compose 的 healthcheck 自动调用:

yaml
healthcheck:
  test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3000/health > /dev/null || exit 1"]
  interval: 10s
  timeout: 5s
  retries: 3
  start_period: 30s

基于 MIT 协议发布