CnOps 智能运维与可观测社区
首页开源项目实践文章视频课程常见问答开发者工具STAROps 专题
导航菜单
首页开源项目实践文章视频课程常见问答开发者工具STAROps 专题

CnOps 智能运维与可观测社区

愿景

CnOps 智能运维与可观测社区是一个以"智能运维与可观测"为核心的开放、包容、分享的技术社区,旨在聚集运维专家、开发者和爱好者,共同探讨、学习和分享可观测最佳实践与最新技术,与众多技术社区合作互动,共同探讨交叉领域的技术挑战,推动可观测领域的创新与进步。

内容社区

  • 实践文章
  • 视频课程
  • 开源项目
  • 常见问答
  • 开发者工具

友情链接

  • Prometheus
  • Grafana Lab
  • OpenTelemetry
  • LoongCollector

关注我们

阿里云云原生公众号阿里云云原生
阿里云可观测公众号阿里云可观测

Copyright © 2026 CnOps 社区. All rights reserved.

首页/开源项目/Prometheus/编写 exporter
文档目录
客户端库编写客户端库推送指标Exporter 和集成编写 exporter暴露格式UTF-8 指标和标签名称转义方案抓取协议内容协商

Prometheus

编写 exporter

如果你在检测自己的代码,应该遵循使用 Prometheus 客户端库检测代码的一般规则。当从另一个监控或检测系统获取指标时,情况往往不那么黑白分明。 本文档包含编写 exporter 或自定义 collector 时应考虑的事项。所涵盖的理论对于进行直接检测的人也会很有参考价值。 如果你正在编写 exporter 并且对此处的内容有任何不清楚的地方,请通过 IRC(libera 上的...

如果你在检测自己的代码,应该遵循使用 Prometheus 客户端库检测代码的一般规则。当从另一个监控或检测系统获取指标时,情况往往不那么黑白分明。

本文档包含编写 exporter 或自定义 collector 时应考虑的事项。所涵盖的理论对于进行直接检测的人也会很有参考价值。

如果你正在编写 exporter 并且对此处的内容有任何不清楚的地方,请通过 IRC(libera 上的 #prometheus 频道)或邮件列表联系我们。

可维护性和纯度

编写 exporter 时你需要做出的主要决定是,你愿意投入多少工作来从中获得完美的指标。

如果相关系统只有少量且很少变化的指标,那么追求完美是一个容易的选择,HAProxy exporter 就是一个很好的例子。

另一方面,如果你试图在系统有数百个指标且随新版本频繁变化的情况下追求完美,那你就为自己承担了大量的持续工作。MySQL exporter 就处于这个范围的另一端。

Node exporter 是两者的混合体,复杂度因模块而异。例如 mdadm collector 手动解析文件并暴露专门为该 collector 创建的指标,所以我们不妨把指标做对。对于 meminfo collector,结果在不同内核版本间有所不同,所以我们只做了足够的转换来创建有效的指标。

配置

在处理应用程序时,你应该力求 exporter 除了告诉它应用程序在哪里之外不需要用户进行自定义配置。你可能还需要提供过滤某些指标的能力,如果它们在大型部署上可能太细粒度且开销太大,例如 HAProxy exporter 允许过滤每服务器的统计信息。同样,可能存在默认禁用的高开销指标。

在处理其他监控系统、框架和协议时,你通常需要提供额外的配置或自定义来生成适合 Prometheus 的指标。在最好的情况下,一个监控系统与 Prometheus 有足够相似的数据模型,你可以自动确定如何转换指标。Cloudwatch、SNMP 和 collectd 就是这种情况。最多,我们需要让用户选择他们想要提取哪些指标。

在其他情况下,来自系统的指标完全是非标准的,取决于系统的使用方式和底层应用程序。在这种情况下,用户必须告诉我们如何转换指标。JMX exporter 是这方面最典型的例子,Graphite 和 StatsD exporter 也需要配置来提取标签。

确保 exporter 开箱即用不需要配置,如果需要转换则提供一组示例配置,这是推荐的做法。

YAML 是 Prometheus 的标准配置格式,所有配置应默认使用 YAML。

指标

命名

遵循指标命名最佳实践。

通常,指标名称应该让熟悉 Prometheus 但不熟悉特定系统的人能够对指标的含义做出合理猜测。名为 http_requests_total 的指标并不是特别有用——这些请求是在进入时测量的、在某个过滤器中测量的还是在到达用户代码时测量的?而 requests_total 更糟糕,什么类型的请求?

使用直接检测时,给定的指标应该存在于恰好一个文件中。同样,在 exporter 和 collector 中,一个指标应该恰好适用于一个子系统并相应命名。

指标名称永远不应该通过程序化方式生成,除非是在编写自定义 collector 或 exporter 时。

应用程序的指标名称通常应该以 exporter 名称作为前缀,例如 haproxy_up。

指标必须使用基本单位(例如秒、字节),将它们转换为更易读的单位留给图形化工具。无论你最终使用什么单位,指标名称中的单位必须与实际使用的单位匹配。同样,暴露比率而不是百分比。更好的做法是,为比率的两个组成部分各指定一个计数器。

指标名称中不应包含导出时使用的标签,例如 by_type,因为如果标签被聚合掉就没有意义了。

一个例外是当你通过多个指标以不同标签导出相同数据时,在这种情况下,这通常是区分它们的最合理方式。对于直接检测,这种情况应该只在导出带有所有标签的单个指标会导致过高基数时才出现。

Prometheus 指标和标签名称使用 snake_case 编写。将 camelCase 转换为 snake_case 是可取的,尽管自动转换并不总能对 myTCPExample 或 isNaN 之类的名称产生好的结果,所以有时最好保持原样。

暴露的指标不应包含冒号,这些是为用户定义的记录规则在聚合时使用而保留的。

指标名称中只有 [a-zA-Z0-9:_] 是有效的。

_sum、_count、_bucket 和 _total 后缀由 Summary、Histogram 和 Counter 使用。除非你正在生成其中一种,否则避免使用这些后缀。

_total 是 Counter 的命名约定,如果你使用的是 COUNTER 类型,应该使用它。

process_ 和 scrape_ 前缀是保留的。如果它们遵循匹配的语义,可以在这些前缀上添加你自己的前缀。例如,Prometheus 有 scrape_duration_seconds 表示抓取花了多长时间,同样拥有一个以 exporter 为中心的指标也是好的实践,例如 jmx_scrape_duration_seconds,说明特定 exporter 完成其工作花了多长时间。对于能够访问 PID 的进程统计信息,Go 和 Python 都提供了能为你处理这些的 collector。HAProxy exporter 就是一个很好的例子。

当你有成功请求计数和失败请求计数时,暴露这些的最佳方式是一个指标用于总请求数,另一个指标用于失败请求数。这样可以轻松计算失败率。不要使用一个带有 failed 或 success 标签的指标。同样,对于缓存的命中或未命中,最好有一个指标用于总数,另一个用于命中数。

考虑使用监控的人对指标名称进行代码或网络搜索的可能性。如果名称非常成熟且不太可能在习惯使用这些名称的人群之外使用,例如 SNMP 和网络工程师,那么保持原样可能是个好主意。这个逻辑并不适用于所有 exporter,例如 MySQL exporter 的指标可能被各种各样的人使用,而不仅仅是 DBA。一个包含原始名称的 HELP 字符串可以提供与使用原始名称几乎相同的好处。

标签

阅读关于标签的一般建议。

避免使用 type 作为标签名称,它太通用且通常没有意义。你还应该尽可能避免使用可能与目标标签冲突的名称,例如 region、zone、cluster、availability_zone、az、datacenter、dc、owner、customer、stage、service、environment 和 env。但是,如果这就是应用程序对某些资源的称呼,最好不要因为重命名而造成混淆。

避免仅仅因为它们共享前缀就将东西放入一个指标中。除非你确定某个东西作为一个指标是有意义的,否则多个指标更安全。

标签 le 对 Histogram 有特殊含义,quantile 对 Summary 有特殊含义。一般应避免使用这些标签。

读/写和发送/接收最好作为单独的指标,而不是作为标签。这通常是因为你一次只关心其中一个,这样使用起来更容易。

经验法则是一个指标在求和或求平均时应该有意义。还有另一种 exporter 中出现的情况,即数据本质上是表格式的,否则用户需要对指标名称进行正则表达式匹配才能使用。考虑主板上的电压传感器,虽然对它们进行数学运算没有意义,但将它们放在一个指标中比每个传感器一个指标更合理。一个指标中的所有值应该(几乎)总是具有相同的单位,例如考虑如果风扇速度混在电压中,而你无法自动将它们分开。

不要这样做:

my_metric{label="a"} 1
my_metric{label="b"} 6
my_metric{label="total"} 7

或者这样:

my_metric{label="a"} 1
my_metric{label="b"} 6
my_metric{} 7

前者对你的指标做 sum() 的人会产生问题,后者既会破坏 sum 又很难使用。一些客户端库,例如 Go,会主动阻止你在自定义 collector 中做后者,所有客户端库都应该阻止你在直接检测中做后者。永远不要做这两种情况中的任何一种,而是依赖 Prometheus 聚合。

如果你的监控暴露了这样的总计,请去掉总计。如果出于某种原因必须保留它,例如总计包含了未单独计数的项目,请使用不同的指标名称。

检测标签应该是最少的,每个额外的标签都是用户编写 PromQL 时需要额外考虑的。因此,避免拥有即使移除也不会影响时间序列唯一性的检测标签。围绕指标的附加信息可以通过 info 指标添加,有关如何处理版本号的示例见下文。

但是,在某些情况下,几乎所有指标的用户都会需要附加信息。如果是这样,添加一个非唯一标签而不是 info 指标是正确的解决方案。例如 mysqld_exporter 的 mysqld_perf_schema_events_statements_total 的 digest 标签是完整查询模式的哈希值,足以保证唯一性。但是,没有人类可读的 digest_text 标签它就没什么用处,而该标签对于长查询只包含查询模式的开头部分,因此不是唯一的。所以我们最终既有 digest_text 标签供人阅读,又有 digest 标签保证唯一性。

目标标签,而非静态抓取标签

如果你发现自己想要将相同的标签应用于所有指标,请停下来。

这种情况通常有两种情况。

第一种是某些标签放在指标上会很有用,例如软件的版本号。相反,请使用
https://www.robustperception.io/how-to-have-labels-for-machine-roles/ 中描述的方法。

第二种情况是标签实际上是目标标签。这些是来自你的基础设施设置而不是应用程序本身的东西,如 region、集群名称等。应用程序不应该说明它在你的标签分类体系中的位置,这应该由运行 Prometheus 服务器的人来配置,监控同一应用程序的不同人可能会给它不同的名称。

因此,这些标签属于 Prometheus 抓取配置中,通过你使用的任何服务发现机制来实现。在这里应用机器角色的概念也是可以的,因为这对于至少一些抓取它的人来说可能是有用的信息。

类型

你应该尝试将你的指标类型与 Prometheus 类型匹配。这通常意味着 Counter 和 Gauge。Summary 的 _count 和 _sum 也相对常见,偶尔你会看到分位数。Histogram 较少见,如果你遇到一个,请记住暴露格式暴露的是累积值。

通常,指标的类型不会很明显,特别是当你自动处理一组指标时。一般来说,UNTYPED 是一个安全的默认值。

Counter 不能减少,所以如果你有一个来自另一个检测系统的可以递减的 Counter 类型,例如 Dropwizard metrics,那它就不是 Counter,而是 Gauge。UNTYPED 可能是最好的类型选择,因为如果它被用作 Counter,GAUGE 会产生误导。

帮助字符串

当你转换指标时,让用户能够追溯到原始指标以及应用了什么规则导致了该转换是很有用的。将 collector 或 exporter 的名称、应用的规则 ID 以及原始指标的名称和详情放入帮助字符串中,将极大地帮助用户。

Prometheus 不允许一个指标有不同的帮助字符串。如果你将多个其他指标合成一个指标,请选择其中一个放入帮助字符串。

作为例子,SNMP exporter 使用 OID,JMX exporter 放入一个示例 mBean 名称。HAProxy exporter 有手写的字符串。Node exporter 也有各种各样的示例。

丢弃不太有用的统计数据

某些检测系统会暴露 1 分钟、5 分钟、15 分钟速率,以及自应用程序启动以来的平均速率(在 Dropwizard metrics 中称为 mean),此外还有最小值、最大值和标准差。

这些都应该被丢弃,因为它们不太有用且会增加混乱。Prometheus 可以自己计算速率,而且通常更准确,因为暴露的平均值通常是指数衰减的。你不知道最小值或最大值是在什么时间段内计算的,标准差在统计学上没有用处,如果你需要计算它,你可以暴露平方和、_sum 和 _count。

分位数也有相关问题,你可以选择丢弃它们或将它们放入 Summary 中。

点分字符串

许多监控系统没有标签,而是使用类似 my.class.path.mymetric.labelvalue1.labelvalue2.labelvalue3 的方式。

Graphite 和 StatsD exporter 共享一种用小型配置语言来转换这些的方法。其他 exporter 应该实现相同的方法。该转换目前仅在 Go 中实现,如果能被提取到单独的库中将会更好。

Collector

在为你的 exporter 实现 collector 时,你永远不应该使用通常的直接检测方式然后在每次抓取时更新指标。

而是应该每次都创建新的指标。在 Go 中,这是通过在你的 Collect() 方法中使用 MustNewConstMetric 来完成的。对于 Python,请参阅 https://github.com/prometheus/client_python#custom-collectors;对于 Java,在你的 collect 方法中生成一个 List<MetricFamilySamples>,参见 StandardExports.java 作为示例。

这样做的原因有两个。首先,两次抓取可能同时发生,直接检测使用的实际上是文件级全局变量,所以你会遇到竞态条件。其次,如果一个标签值消失了,它仍然会被导出。

通过直接检测来检测 exporter 本身是可以的,例如 exporter 在所有抓取中传输的总字节数或执行的调用次数。对于像 Blackbox exporter 和 SNMP exporter 这样不与单个目标绑定的 exporter,这些应该只在普通的 /metrics 调用中暴露,而不是在对特定目标的抓取中暴露。

关于抓取本身的指标

有时你想导出关于抓取本身的指标,比如它花了多长时间或你处理了多少条记录。

这些应该作为 Gauge 暴露,因为它们是关于一个事件(即抓取)的,指标名称以 exporter 名称为前缀,例如 jmx_scrape_duration_seconds。通常 _exporter 会被省略,如果 exporter 也可以仅作为 collector 使用,那么一定要省略它。

应避免其他抓取"元"指标。例如,抓取次数的计数器或抓取持续时间的直方图。让 exporter 跟踪这些指标会与 Prometheus 自身自动生成的指标重复。这会增加每个 exporter 实例的存储成本。

机器和进程指标

许多系统(例如 Elasticsearch)会暴露机器指标,如 CPU、内存和文件系统信息。由于 Node exporter 在 Prometheus 生态系统中提供了这些功能,此类指标应该被丢弃。

在 Java 世界中,许多检测框架会暴露进程级和 JVM 级的统计信息,如 CPU 和 GC。Java 客户端和 JMX exporter 已经通过 DefaultExports.java 以首选形式包含了这些,所以这些也应该被丢弃。

其他语言和框架也是类似的情况。

部署

每个 exporter 应该监控恰好一个应用程序实例,最好紧挨着它在同一台机器上运行。这意味着对于你运行的每个 HAProxy,你运行一个 haproxy_exporter 进程。对于每台有 Mesos worker 的机器,你在上面运行 Mesos exporter,如果一台机器同时有两者,还需要另一个用于 master。

这背后的理论是,对于直接检测你就会这样做,我们在其他布局中尝试尽可能接近这一点。这意味着所有服务发现都在 Prometheus 中完成,而不是在 exporter 中。这还有一个好处,即 Prometheus 拥有所需的目标信息,允许用户使用 Blackbox exporter 探测你的服务。

有两个例外:

第一个是在你监控的应用程序旁边运行完全没有意义的情况。SNMP、Blackbox 和 IPMI exporter 是主要的例子。IPMI 和 SNMP exporter 因为设备通常是无法在上面运行代码的黑盒(尽管如果能在上面运行 Node exporter 那会更好),Blackbox exporter 则是因为你监控的是像 DNS 名称这样的东西,也没有什么可以运行的。在这种情况下,Prometheus 仍然应该进行服务发现,并将要抓取的目标传递过去。参见 Blackbox 和 SNMP exporter 的示例。

请注意,目前只有 Go、Python 和 Java 客户端库支持编写这种类型的 exporter。

第二个例外是当你从系统的随机实例中提取一些统计信息且不在乎与哪个实例通信时。考虑一组 MySQL 副本,你想对数据运行一些业务查询然后导出。让 exporter 使用你通常的负载均衡方法与一个副本通信是最合理的方法。

当你监控具有主选举的系统时,这不适用,在那种情况下你应该单独监控每个实例,并在 Prometheus 中处理"主节点"问题。这是因为不总是恰好有一个主节点,而且在 Prometheus 脚下更改目标会导致奇怪的行为。

调度

只应在 Prometheus 抓取时从应用程序拉取指标,exporter 不应基于自己的定时器执行抓取。也就是说,所有抓取应该是同步的。

因此,你不应该对你暴露的指标设置时间戳,让 Prometheus 来处理。如果你认为需要时间戳,那你可能需要的是 Pushgateway。

如果获取某个指标特别昂贵,即需要超过一分钟,可以接受对其进行缓存。这应该在 HELP 字符串中注明。

Prometheus 的默认抓取超时时间为 10 秒。如果你的 exporter 预计会超过这个时间,你应该在用户文档中明确说明。

推送

某些应用程序和监控系统只推送指标,例如 StatsD、Graphite 和 collectd。

这里有两个考虑因素。

首先,什么时候使指标过期?Collectd 和与 Graphite 通信的东西都会定期导出,当它们停止时我们希望停止暴露指标。Collectd 包含过期时间所以我们使用它,Graphite 没有,所以它是 exporter 上的一个标志。

StatsD 有些不同,因为它处理的是事件而不是指标。最佳模型是在每个应用程序旁边运行一个 exporter,并在应用程序重启时重启它们以清除状态。

其次,这类系统往往允许用户发送增量或原始计数器。你应该尽可能依赖原始计数器,因为这是 Prometheus 的一般模型。

对于服务级别指标,例如服务级别批处理作业,你应该让 exporter 推送到 Pushgateway 并在事件后退出,而不是自己处理状态。对于实例级别的批处理指标,目前还没有明确的模式。选项包括滥用 Node exporter 的 textfile collector、依赖内存状态(如果你不需要在重启后持久化,这可能是最好的)或实现类似 textfile collector 的功能。

失败的抓取

当你与之通信的应用程序不响应或有其他问题时,目前有两种处理失败抓取的模式。

第一种是返回 5xx 错误。

第二种是有一个 myexporter_up 变量,例如 haproxy_up,其值为 0 或 1,取决于抓取是否成功。

后者在即使抓取失败仍能获得一些有用指标的情况下更好,例如 HAProxy exporter 提供进程统计信息。前者对用户来说处理起来稍微容易一些,因为 up 按通常方式工作,尽管你无法区分 exporter 宕机和应用程序宕机。

着陆页

如果用户访问 http://yourexporter/ 时有一个简单的 HTML 页面,包含 exporter 的名称和到 /metrics 页面的链接,那会更好。

端口号

用户可能在同一台机器上运行许多 exporter 和 Prometheus 组件,因此为了简化,每个都有一个唯一的端口号。

https://github.com/prometheus/prometheus/wiki/Default-port-allocations 是我们跟踪端口分配的地方,它是可以公开编辑的。

在开发 exporter 时,请随意获取下一个空闲端口号,最好在公开宣布之前获取。如果你还没准备好发布,放上你的用户名和 WIP 就可以了。

这是一个注册表,旨在让我们的用户生活更轻松,不是开发特定 exporter 的承诺。对于内部应用程序的 exporter,我们建议使用默认端口分配范围之外的端口。

发布

一旦你准备好向世界宣布你的 exporter,请发邮件到邮件列表并提交 PR 将其添加到可用 exporter 列表,方法是编辑这个 GitHub 仓库文件。

文章大纲