可观测性指南
本章介绍 Thurgio 的三类可观测入口如何配合使用:Prometheus 指标(GET /metrics)、订阅健康度聚合(GET /app/read/health)与结构化日志。
概述
| 入口 | 路径 | 认证 | 用途 | 适合告警 |
|---|---|---|---|---|
| 指标 | GET /metrics | 无需令牌(公开端点,见 API 参考) | 机器可读的时序数据:请求、构建管线、缓存、熔断器、渲染线程池、内存 | 是 |
| 健康度 | GET /app/read/health | 需 Read/Admin 令牌 | 面向订阅的业务健康度:按 provider/snippet 汇总熔断器状态与最后成功时间 | 否(适合 Dashboard/人工巡检) |
| 日志 | stdout/stderr(journald) | — | 请求追踪、错误链、事件总线与熔断器状态切换 | 否(适合排障) |
三者在 部署指南 的监控检查清单中均有对应项。
Prometheus 接入
认证说明
GET /metrics 归类为公开端点,与 GET /health、GET /ready 一致,无需 ?token= 或 Authorization: Bearer 头。反向代理层应对其做网络级访问控制,禁止公网直接暴露。详见 部署指南 的 Nginx/Caddy 示例——allow 10.0.0.0/8 / deny all 或 remote_ip 匹配。
scrape_config 示例
以下配置可直接放入 prometheus.yml 的 scrape_configs:
scrape_configs:
- job_name: "thurgio"
metrics_path: /metrics
scrape_interval: 15s
scrape_timeout: 10s
static_configs:
- targets: ["127.0.0.1:10100"]
labels:
instance: "thurgio-prod-01"
若 Prometheus 与 Thurgio 不在同一内网,经反向代理抓取时改用域名并按需添加 TLS 配置:
scrape_configs:
- job_name: "thurgio"
metrics_path: /metrics
scheme: https
static_configs:
- targets: ["thurgio.example.com"]
# 若反向代理对 /metrics 做了 IP 白名单,Prometheus 需部署在白名单网段内
验证抓取是否生效:
curl -fsS http://127.0.0.1:10100/metrics | head -n 40
# 应返回 Prometheus 文本暴露格式,首行为 # HELP http_requests_total
GET /metrics 的响应为纯文本 Prometheus 暴露格式,非 JSON;每次请求实时计算 process_memory_usage_mb(VmRSS)与熔断器快照并追加渲染线程池块(见 crates/thurgio-server/src/routes/public.rs:metrics 与 crates/thurgio-server/src/metrics.rs:render/render_renderer_metrics)。
OTLP 可选链路
thurgio-infra/src/metrics.rs 定义的 MetricsRecorder 在启用 otlp feature 时经 OtelMetrics 写入全局 OpenTelemetry Meter Provider(OTEL_EXPORTER_OTLP_ENDPOINT,默认 http://localhost:4317,gRPC)。MetricsCollector 对未映射的指标名会转发至 fallback recorder(set_fallback),未启用 otlp 时直接丢弃。因此下表标注为 OTLP 的指标仅在 cargo run -p thurgio-cli --features otlp 或对应 release 构建下可见;Prometheus 标注的指标无论是否启用 OTLP 均出现在 /metrics。
指标参考
名称与类型以源码为准。来源文件:
crates/thurgio-infra/src/metrics.rs(names模块,规范名称单一来源)crates/thurgio-server/src/metrics.rs(MetricsCollector::render与render_renderer_metrics的 Prometheus 渲染)
1. HTTP 请求(MetricsCollector 直接渲染)
| 名称 | 类型 | 含义 |
|---|---|---|
http_requests_total | counter | 累计 HTTP 请求数(record_request 每次 +1) |
http_errors_total | counter | 累计 status >= 400 的请求数 |
active_connections | gauge | 当前在途请求数(RequestTimer::new +1 / finish -1) |
http_request_duration_ms | gauge | 平均请求耗时(毫秒,request_duration_sum / count) |
http_request_duration_histogram | histogram | 请求耗时直方图;桶边界 [1, 5, 10, 50, 100, 500, 1000, 5000, 10000] 毫秒,另含 +Inf 桶、_sum 与 _count |
| `http_status_codes{category=“1xx | 2xx | 3xx |
http_rate_limited_total | counter | 因并发或速率限制被拒绝并返回 429 的请求数(RATELIMIT = http_rate_limited_total) |
2. 构建与更新管线
| 名称 | 类型 | 含义 | 暴露 |
|---|---|---|---|
config_updates_total | counter | provider/snippet 更新总次数(record_config_update) | Prometheus |
config_updates_failed_total | counter | 更新失败次数(CONFIG_UPDATES_FAILED,经 MetricsRecorder 映射) | Prometheus |
config_renders_total | counter | 模板渲染总次数(record_config_render,含 provider 主输出、具名输出与 snippet) | Prometheus |
config_builds_total | counter | 完整构建(build)总次数 | Prometheus |
build.completed | counter | build 管线成功完成计数(BUILD_COMPLETED) | Prometheus |
build.update.failure | counter | build 更新阶段失败计数(BUILD_UPDATE_FAILURE) | Prometheus |
build.provider.success{name="<provider>"} | counter | 单个 provider 拉取成功计数(ITEM_PROVIDER.success,label name) | Prometheus |
build.provider.failure{name="<provider>"} | counter | 单个 provider 拉取失败计数(ITEM_PROVIDER.failure) | Prometheus |
build.snippet.success{name="<snippet>"} | counter | 单个 snippet 拉取成功计数(ITEM_SNIPPET.success) | Prometheus |
build.snippet.failure{name="<snippet>"} | counter | 单个 snippet 拉取失败计数(ITEM_SNIPPET.failure) | Prometheus |
renderer.reload | counter | 渲染器重载次数(RENDERER_RELOAD,dev 模式或 GET /app/admin/reload 触发) | Prometheus |
config_reload_failed_total | counter | 配置热重载重解析失败次数(CONFIG_RELOAD_FAILED,失败时保留旧配置,不发事件) | Prometheus |
proxy_count | gauge | 当前代理总数(PROXY_COUNT,gauge_set,可增可减) | Prometheus |
build.duration | histogram | 完整 build 耗时(秒,BUILD_DURATION) | OTLP |
| `build.update.duration{status=“success | failure“}` | histogram | build 更新阶段耗时(秒,label status) |
build.render.duration | histogram | build 渲染阶段耗时(秒,BUILD_RENDER_DURATION) | OTLP |
build.update.success | counter | build 更新阶段成功计数(BUILD_UPDATE_SUCCESS) | OTLP |
autobuild.success | counter | AutoBuildHandler 自动重建成功计数 | OTLP |
autobuild.failure | counter | AutoBuildHandler 自动重建失败计数 | OTLP |
proxy_parse_failed_total{name="<provider>"} | counter | 单 provider 解析阶段丢弃的坏条目数(label name) | OTLP |
ruleset_fetch_failed_total{name="<ruleset>"} | counter | 远程规则集拉取失败次数(回退旧文本,不传播错误;label name) | OTLP |
3. 缓存
| 名称 | 类型 | 含义 | 暴露 |
|---|---|---|---|
cache_hits_total | counter | TTL 缓存命中次数(CACHE_HIT,label `kind=provider | snippet |
cache_misses_total | counter | TTL 缓存未命中次数(CACHE_MISS,实际发起抓取时 +1) | Prometheus |
4. 熔断器(Circuit Breaker)
| 名称 | 类型 | 含义 |
|---|---|---|
circuit_breaker_state{name="<provider|snippet>"} | gauge | 熔断器状态:0 = Closed(正常)、1 = Open(拒绝抓取)、2 = HalfOpen(试探) |
circuit_breaker_fail_count{name="<provider|snippet>"} | counter | 熔断器累计失败次数(CircuitBreakerSnapshot::failure_count) |
快照来源为 CircuitBreakerPool::snapshots(),与 GET /app/read/health 的 failure_count 同源;键格式 circuit_breaker:{identity} 由 thurgio-infra::circuit_breaker::SNAPSHOT_KEY_PREFIX 单一拥有。
5. 资源与内存
| 名称 | 类型 | 含义 | 暴露 |
|---|---|---|---|
process_memory_usage_mb | gauge | 当前 RSS 内存(MB,get_memory_usage 读取 /proc/self/status 的 VmRSS;非 Linux 置 0) | Prometheus |
process_memory_baseline_mb | gauge | 空闲驱逐后的引擎基线 RSS(MB,PROCESS_MEMORY_BASELINE) | Prometheus |
data_cache_memory_mb | gauge | 空闲驱逐释放的数据缓存内存(MB,DATA_CACHE_MEMORY,rss_before - rss_after) | Prometheus |
6. 渲染线程池(render_renderer_metrics)
由 AppState::renderer().pool_metrics() 在 /metrics 末尾追加,HELP/TYPE/value 三行一组:
| 名称 | 类型 | 含义 |
|---|---|---|
renderer_active_threads | gauge | 当前活跃渲染线程数 |
renderer_backlog_smoothed | gauge | EMA 平滑后的待渲染积压深度(浮点,保留两位小数) |
renderer_threads_spawned_total | counter | 累计创建的渲染线程数 |
renderer_threads_exited_total | counter | 累计退出的渲染线程数 |
renderer_slots_total | gauge | 渲染线程池总槽位数 |
池参数由环境变量 THURGIO_RENDERER_MIN_THREADS / THURGIO_RENDERER_MAX_THREADS 调整(默认 min 1 / max 64),见 部署指南。
告警建议
下列规则为 Prometheus groups YAML 片段,可直接加入 prometheus.yml 的 rule_files 或 Alertmanager 规则文件。阈值按需调整。
groups:
- name: thurgio
interval: 30s
rules:
# 1. 熔断器 Open 超过 10 分钟
- alert: ThurgioBreakerOpen
expr: circuit_breaker_state == 1
for: 10m
labels:
severity: warning
annotations:
summary: "订阅 {{ $labels.name }} 熔断器持续 Open"
description: "该 provider/snippet 已连续 10 分钟拒绝抓取,检查上游可用性与网络。"
# 2. 任意订阅拉取失败率升高(5 分钟内失败 > 0)
- alert: ThurgioProviderFailure
expr: increase(build.provider.failure[5m]) > 0
labels:
severity: warning
annotations:
summary: "provider {{ $labels.name }} 拉取失败"
description: "5 分钟内出现拉取失败,对照日志 trace_id 排查上游与解析错误。"
# 3. 渲染线程池积压过高
- alert: ThurgioRendererBacklogHigh
expr: renderer_backlog_smoothed > 20
for: 5m
labels:
severity: warning
annotations:
summary: "渲染线程池积压过高"
description: "平滑积压 {{ $value }} 超过 20,考虑调大 THURGIO_RENDERER_MAX_THREADS 或降低并发渲染压力。"
# 4. 限流误伤(正常流量下不应出现 429)
- alert: ThurgioRateLimited
expr: increase(http_rate_limited_total[5m]) > 0
labels:
severity: warning
annotations:
summary: "请求被限流"
description: "5 分钟内出现 429,检查是否误伤正常客户端,必要时调整 THURGIO_RATE_LIMIT_* 或反向代理限流配置。"
# 5. 配置热重载持续失败
- alert: ThurgioConfigReloadFailed
expr: increase(config_reload_failed_total[10m]) > 0
labels:
severity: critical
annotations:
summary: "配置热重载失败"
description: "10 分钟内出现重解析失败,旧配置仍生效但新配置未生效,检查 YAML 语法与模板存在性。"
可选补充(按需启用):
# 6. 内存基线异常增长(按实例规格调整阈值)
- alert: ThurgioMemoryHigh
expr: process_memory_usage_mb > 800
for: 10m
labels:
severity: warning
annotations:
summary: "内存占用过高"
description: "RSS {{ $value }} MB 超过 800 MB,检查是否存在订阅膨胀或渲染泄漏。"
与 GET /app/read/health 的分工
| 维度 | GET /metrics | GET /app/read/health |
|---|---|---|
| 定位 | 面向监控系统的时序指标 | 面向人的订阅健康度聚合 |
| 数据 | 计数器/直方图/gauge,带时间序列 | 当前快照:overall(healthy/degraded/unhealthy)、summary{total,closed,open,half_open} 与 items[] 明细 |
| 熔断器 | circuit_breaker_state(数值 0/1/2)与 circuit_breaker_fail_count | breaker(closed/open/half-open)、failure_count、last_failure_at(Unix 秒,null 表示从未失败) |
| 时间 | 仅计数,不含时间戳 | last_update_at(RFC 3339,取 DB time 列;未抓取过为 null;epoch 哨兵已过滤) |
| 排序 | 按 Prometheus 文本顺序 | provider 在前(配置声明顺序)、snippet 在后 |
| 触发 | 不触发更新 | 不触发更新(仅读熔断器池与 DB) |
| 认证 | 公开 | 需 Read/Admin 令牌(见 API 参考) |
使用建议:
- 告警走
/metrics(Prometheus 规则见上节)。 - 巡检/排障走
/app/read/health:overall == degraded时检查items中breaker == open的条目,结合failure_count与last_failure_at判断故障时长;last_update_at == null表示该订阅从未成功抓取。 - Dashboard 可同时消费两者:时序图用
/metrics,订阅列表用/app/read/health(与内置 UI 的数据源一致,见docs/src/dashboard.md)。
日志
Thurgio 经 tracing + tracing-subscriber 输出日志到 stdout/stderr,由 systemd 的 journald 或容器日志驱动收集。日志级别由 RUST_LOG 控制,格式由 RUST_LOG_FORMAT 控制(见 部署指南 与 thurgio-infra::init_logger)。
| 环境变量 | 默认 | 说明 |
|---|---|---|
RUST_LOG | info | EnvFilter 语法,如 info,thurgio_app_update=debug;just 默认 info,opentelemetry=off |
RUST_LOG_FORMAT | 文本 | 设为 json 输出结构化日志,便于 jq 过滤 |
RUST_LOG_ANSI | 自动检测(TTY 时启用) | 是否输出 ANSI 颜色,同时影响 CLI 彩色输出 |
常用排障命令:
# 跟随日志
journalctl -u thurgio -f
# 仅看熔断器状态切换
journalctl -u thurgio | grep "circuit breaker"
# 仅看失败请求(结构化日志)
RUST_LOG_FORMAT=json thurgio serve | jq 'select(.fields.trace_id != null)'
# 提升更新管线的日志级别
RUST_LOG="thurgio_app_update=debug,thurgio_request=debug" thurgio serve
关键日志信号:
- 熔断器:
circuit breaker opened after N failures、circuit breaker reopened after half-open failure、circuit breaker open for <name>。 - 配置热重载失败:
config_reload_failed_total递增时伴随error!日志,旧配置保持生效。 - 事件总线:
domain event: provider '<name>' update failed等(thurgio-app-build/src/event_handlers.rs)。