引言
BEAM 虚拟机运行着数十万甚至数百万个轻量进程,每个进程都有自己的堆、邮箱和状态。监控这样一个「进程海洋」需要不同于传统系统的工具:不是看 CPU 和内存的宏观曲线,而是看 reductions、消息队列长度、ETS 表大小、GC 频率等 BEAM 特有指标。Elixir 的 Telemetry 库提供了标准化的指标发射机制,配合 Logger 的结构化输出和 OpenTelemetry 的分布式追踪,构成了 Erlang/Elixir 系统的完整可观测栈。本文从日志到指标、从诊断工具到告警集成,给 BEAM 系统的观测一张地图。
前置:/elixir-otp-supervision-tasks/(OTP 监督树)、/erlang-concurrency-actors/(并发与容错)。
目录
- 1. Logger 日志系统配置与处理器
- 2. 结构化日志与 JSON 输出
- 3. Telemetry 事件与指标体系
- 4. 指标类型计数器直方图与最后值
- 5. Prometheus 与 Grafana 集成
- 6. OpenTelemetry 分布式链路追踪
- 7. Observer 与 CLI 诊断工具
- 8. BEAM VM 内部指标解析
- 9. 日志聚合与告警体系
- 10. 速查表与一句话记忆
- 延伸阅读
1. Logger 日志系统:配置与处理器
1.1 Elixir Logger 配置
# config/config.exs
config :logger,
level: :info,
backends: [:console]
config :logger, :console,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id, :user_id]
1.2 日志级别
| 级别 | 用途 |
|---|---|
| :debug | 开发调试 |
| :info | 正常运行信息 |
| :warning | 需要注意但非错误 |
| :error | 错误需处理 |
1.3 自定义后端
# 写入文件
config :logger,
backends: [{LoggerFileBackend, :error_log}]
config :logger, :error_log,
path: "/var/log/app/error.log",
level: :error
# 或使用 structured logging backend(如 logger_json)
记忆 Elixir Logger 分 debug/info/warning/error 四级,config 配置 backends(console/file/自定义)和 metadata;生产环境推荐 logger_json backend 输出结构化 JSON。
2. 结构化日志与 JSON 输出
2.1 为什么结构化
非结构化:"User 42 logged in from 192.168.1.1 at 10:00"
结构化:{"@timestamp":"2024-01-15T10:00:00Z","event":"login","user_id":42,"ip":"192.168.1.1"}
# JSON 可被 Elasticsearch/Loki 索引和查询
2.2 logger_json 配置
# mix.exs: {:logger_json, "~> 5.0"}
config :logger_json, :backend,
metadata: :all,
formatter: LoggerJSON.Formatters.BasicLogger
config :logger,
backends: [LoggerJSON.Backend]
2.3 日志最佳实践
# 用 metadata 而非字符串拼接
Logger.info("User login", user_id: user.id, ip: conn.remote_ip)
# 统一 request_id 贯穿请求
Logger.metadata(request_id: request_id)
# 错误日志带异常和堆栈
Logger.error("Payment failed", error: inspect(e), stacktrace: __STACKTRACE__)
记忆 结构化日志 = logger_json backend + metadata 传键值对(不用字符串拼接);metadata 设 request_id 贯穿请求链路;错误日志带 inspect(e) 和 STACKTRACE。
3. Telemetry 事件与指标体系
3.1 Telemetry 核心概念
Telemetry 是「标准化事件发射库」
不是监控工具本身,而是数据 producer
第三方库(Ecto/Phoenix/Redix)内置 Telemetry 事件
应用代码 attach 处理器来消费事件 → 生成指标
3.2 基本用法
# 定义事件(通常在库代码中)
:telemetry.execute([:web, :request], %{duration: 230}, %{path: "/users"})
# 附加处理器(应用代码中订阅)
:telemetry.attach(
"web-request-handler",
[:web, :request],
&handle_event/4,
nil
)
def handle_event([:web, :request], measurements, metadata, _config) do
# measurements = %{duration: 230}
# metadata = %{path: "/users"}
Statsd histogram("web.request.duration", measurements.duration,
tags: ["path:#{metadata.path}"])
end
3.3 常用内置事件
[:phoenix, :endpoint, :stop] # Phoenix 请求完成
e[:phoenix, :router_dispatch, :stop] # 路由分派完成
e[:phoenix, :socket_connected] # WebSocket 连接
e[:ecto, :query] # Ecto 查询
e[:vm_memory, :total] # VM 内存
记忆 Telemetry = 事件发射标准库(execute 发射、attach 订阅处理);不是监控工具是数据桥梁;Phoenix/Ecto 内置大量事件;处理器收到 measurements(数值)和 metadata(标签)后转指标。
4. 指标类型:计数器、直方图与最后值
4.1 Telemetry.Metrics 定义
# 定义指标规格
metrics = [
counter("web.request.count"),
sum("web.request.body_bytes", unit: :byte),
last_value("vm.memory.total", unit: :byte),
summary("web.request.duration",
unit: {:native, :millisecond},
tags: [:path, :method]
),
distribution("web.request.duration",
buckets: [10, 50, 100, 200, 500],
unit: {:native, :millisecond}
)
]
4.2 指标类型对比
| 类型 | 描述 | 用例 |
|---|---|---|
| counter | 只增计数 | 请求数、错误数 |
| sum | 累加值 | 传输字节数 |
| last_value | 最新值 | 内存使用量 |
| summary | 采样统计 | 响应时间分位 |
| distribution | 分桶分布 | P50/P95/P99 |
记忆 五种指标——counter(只增计数)、sum(累加)、last_value(最新状态)、summary(分位采样)、distribution(分桶直方图);Telemetry.Metrics 定义规格,Reporter(如 StatsD/Prometheus)负责导出。
5. Prometheus 与 Grafana 集成
5.1 Prometheus 导出
# mix.exs: {:prometheus_ex, "~> 3.0"}, {:prometheus_plugs, "~> 1.0"}
# 定义 metrics
defmodule MyApp.Metrics do
use Prometheus.Metric
defsetup do
Counter.declare(name: :http_requests_total, labels: [:method, :path])
Histogram.declare(name: :http_request_duration_seconds, labels: [:method])
end
end
# 更新
def handle_request(conn) do
Counter.inc(name: :http_requests_total, labels: [conn.method, conn.path_info])
# ...
end
5.2 /metrics 端点
# Phoenix 路由
get "/metrics", Prometheus.PlugsExporter
# Prometheus scrape 配置
scrape_configs:
- job_name: 'myapp'
static_configs:
- targets: ['localhost:4000']
5.3 Grafana 看板
# 导入 BEAM VM 看板模板
# 关键面板:
# - 请求 QPS / 错误率
# - P50/P95/P99 响应时间
# - BEAM 进程数 / reductions 每秒
# - 内存使用(atom / binary / ets / process)
# - ETS 表大小
记忆 Prometheus 集成——prometheus_ex 定义 Counter/Histogram、PlugsExporter 暴露 /metrics、Prometheus scrape 拉取、Grafana 看板展示;BEAM 特有面板看 reductions/进程/内存/ETS。
6. OpenTelemetry 分布式链路追踪
6.1 核心概念
Trace:一次请求的完整链路
Span:链路中的一个操作(如 DB 查询)
SpanContext:Trace ID + Span ID + Flags(跨进程传递)
Baggage:跨 Span 传递的键值对
6.2 Elixir 集成
# mix.exs: {:opentelemetry, "~> 1.0"}, {:opentelemetry_exporter, "~> 1.0"}
# 初始化 Trace
require OpenTelemetry.Tracer
OpenTelemetry.Tracer.with_span "process_order" do
OpenTelemetry.Tracer.set_attribute("order.id", order_id)
OpenTelemetry.Tracer.with_span "db_query" do
Repo.get(Order, order_id)
end
end
6.3 Phoenix 自动追踪
# mix.exs: {:opentelemetry_phoenix, "~> 1.0"}
# 自动追踪 HTTP 请求、DB 查询、外部调用
# 导出到 Jaeger/Tempo/Datadog
记忆 OpenTelemetry 在 Elixir 中用 with_span 嵌套追踪;opentelemetry_phoenix 自动埋点;Span 包含 attribute(标签)和 event(时间点);导出到 Jaeger/Tempo 查看链路。
7. Observer 与 CLI 诊断工具
7.1 observer_cli(终端版 Observer)
$ mix deps.get observer_cli
$ iex -S mix
iex> :observer_cli.start()
# 实时显示:
# - 进程列表(内存/ reductions /消息队列)
# - ETS 表
# - 端口 / 调度器负载
# - Mnesia 表
7.2 recon
# 内存分析
:recon.proc_count(:memory, 10) # 内存占用最高的 10 个进程
:recon.proc_count(:message_queue_len, 10) # 消息队列最长的进程
# 节点状态
:recon.node_stats(1000, 10) # 每秒采样,共 10 次
7.3 vmstats
# 定期输出 VM 统计到 StatsD
# 进程数 / reductions / atom / binary / ets / port
# 适合长期趋势监控
记忆 诊断工具——observer_cli 终端实时监控(进程/ETS/调度器)、recon 内存和消息队列排查、vmstats 长期趋势输出;生产环境首选 CLI 工具(observer GUI 需 wx)。
8. BEAM VM 内部指标解析
8.1 核心指标
| 指标 | 含义 | 健康阈值 |
|---|---|---|
| reductions | 函数调用次数(调度单位) | 平稳增长 |
| process_count | 进程数 | < 系统上限 |
| message_queue_len | 消息队列长度 | < 1000 |
| memory.total | 总内存 | < 物理内存 80% |
| memory.binary | 堆外 binary 内存 | 不泄漏增长 |
| memory.ets | ETS 表内存 | 不泄漏增长 |
| gc.count | GC 次数 | 不激增 |
8.2 获取方式
# 进程信息
Process.info(pid, [:message_queue_len, :memory, :reductions])
# VM 内存
:erlang.memory() # => [total: ..., processes: ..., binary: ..., ets: ...]
# 统计
:erlang.statistics(:reductions)
:erlang.system_info(:process_count)
记忆 BEAM 核心指标——reductions(调度单位)、process_count(进程数)、message_queue_len(消息队列 >1000 危险)、memory 分 total/processes/binary/ets(binary/ets 泄漏增长要排查)、gc.count(激增说明内存抖动)。
9. 日志聚合与告警体系
9.1 日志流架构
App (JSON 日志) → Filebeat/Fluent Bit → Logstash → Elasticsearch → Kibana
↓
Loki (轻量替代)
# BEAM 应用日志通过 stdout/stderr → 容器收集器 → 聚合存储
9.2 告警规则
# Prometheus Alertmanager
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
for: 5m
annotations:
summary: "错误率 > 5%"
- alert: MemoryLeaking
expr: rate(vm_memory_total_bytes[10m]) > 1000000
for: 10m
annotations:
summary: "内存持续泄漏"
- alert: MessageQueueBlocked
expr: max_over_time(process_message_queue_length[5m]) > 10000
annotations:
summary: "进程消息队列阻塞"
记忆 日志聚合——App 输出 JSON → Filebeat → ES/Loki → Kibana/Grafana;告警用 Prometheus 规则——错误率>5%、内存持续泄漏(rate>阈值)、消息队列阻塞(>10000);BEAM 特有告警:进程数接近上限、binary 内存泄漏。
10. 速查表与一句话记忆
| 概念 | 一句话 |
|---|---|
| Logger | Elixir 日志系统,分四级 |
| logger_json | 结构化 JSON 日志 |
| Telemetry | 标准化事件发射 |
| execute | 发射事件 |
| attach | 订阅处理 |
| counter | 只增计数 |
| histogram | 时间分布 |
| last_value | 最新状态 |
| Prometheus | /metrics 端点 |
| OpenTelemetry | with_span 追踪 |
| observer_cli | 终端实时监控 |
| recon | 内存/队列排查 |
| reductions | BEAM 调度单位 |
| message_queue | 消息队列阻塞指标 |
一句话记忆:Elixir 可观测性三支柱——Logger(结构化 JSON 日志 + metadata/request_id)、Telemetry(execute 发射 + attach 消费转指标,Phoenix/Ecto 内置大量事件)、OpenTelemetry(with_span 嵌套追踪);指标五种类型——counter/sum/last_value/summary/distribution,Prometheus 拉取 /metrics、Grafana 看 BEAM 特有面板;诊断工具 observer_cli 实时看进程/ETS/调度器、recon 排查内存和消息队列;BEAM 核心指标 reductions(调度)、message_queue_len(>1000 危险)、memory binary/ets(泄漏排查);日志走 JSON → Filebeat → Loki/ES,告警用 Prometheus 规则(错误率/内存泄漏/队列阻塞)——「百万进程的世界,reductions 和消息队列长度是生命线」。
延伸阅读
- /elixir-otp-supervision-tasks/ — OTP 监督树
- /erlang-concurrency-actors/ — 并发与容错
- DevOps 专题 — 监控告警体系
- Telemetry 官方文档
- OpenTelemetry Elixir
- observer_cli GitHub
- recon 文档
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。