Telegraf outputs.exec 输出插件实战:把指标流式写入外部命令 stdin 的配置与源码级原理
【免费下载链接】telegrafAgent for collecting, processing, aggregating, and writing metrics, logs, and other arbitrary data.项目地址: https://gitcode.com/GitHub_Trending/te/telegraf
本文基于 Telegraf 仓库中 outputs.exec 插件文档 展开,完整覆盖该插件的全部配置项与默认值,并结合 exec.go 的序列化与进程执行流程、internal/exec_unix.go 的超时终止机制,以及 exec_test.go 的测试断言,系统讲解如何将指标通过stdin投递给外部可执行程序:如何配置command、environment、timeout、use_batch_format等参数,批量模式与单指标模式的执行差异,命令超时时进程如何被强制终止,以及stderr输出如何进入 Telegraf 日志。
一、插件定位:每次写入都启动一个全新进程
outputs.exec是 Telegraf 的可执行文件输出插件(executable output plugin):它把指标写入一个外部应用程序的stdin。与常驻进程型输出不同,每一次 write 都会执行一次该命令,从而创建一个新进程;指标会以 支持的输出数据格式 之一传递给命令。官方文档同时给出了一条性能提示:如果追求更高性能,应考虑持续运行的 execd 插件(进程只启动一次,指标在其生命周期内持续通过stdin传入)。
关键行为约定(来自插件文档):
- 可执行文件与各个参数必须以列表形式定义(
command是一个字符串数组,而不是单个 shell 字符串); - 命令输出到
stderr的内容会被记录进 Telegraf 日志; - 指标格式由
data_format指定,可选格式见 输出数据格式文档(InfluxDB Line Protocol、CSV、JSON、Graphite、Prometheus、MessagePack 等 14 种标准序列化器)。
从源码结构看,插件通过 plugins/outputs/all/exec.go 的空白导入完成注册,因此默认构建中开箱可用。
二、完整配置说明(含默认值)
官方示例配置完整继承自 sample.conf,并补齐了 exec.go 中init()注册的默认值:
# Send metrics to command as input over stdin [[outputs.exec]] ## Command to ingest metrics via stdin. ## 注意:可执行文件本身和每个参数都必须是列表中的独立字符串 command = ["tee", "-a", "/dev/null"] ## Environment variables ## 以 "key=value" 键值对数组形式传递的环境变量 ## 例如 "KEY=value", "USERNAME=John Doe", ## "LD_LIBRARY_PATH=/opt/custom/lib64:/usr/local/libs" # environment = [] ## 命令完成执行的超时时间(源码默认值为 5s) # timeout = "5s" ## 命令是逐条指标执行一次,还是按指标批次执行一次。 ## 该值为 true 时,序列化器也会以批量模式运行(源码默认值为 true) # use_batch_format = true ## Data format to output. ## 每种数据格式都有自己独有的配置选项,详见数据格式文档 # data_format = "influx"2.1 command:命令及其参数列表
command是必填项。源码中它被声明为Command []string(exec.go),运行时通过exec.Command(command[0], command[1:]...)解释:第一个元素是可执行文件路径或名字,其余元素是独立参数。这意味着不要写成command = ["tee -a file"]这种需要 shell 解析的字符串;参数分隔由 Go 的os/exec直接完成,不经过 shell。
2.2 environment:附加环境变量
environment是key=value字符串数组。从 CommandRunner.Run 的实现看,配置的环境变量会被追加到当前进程环境变量之后:
cmd := exec.Command(command[0], command[1:]...) if len(environments) > 0 { cmd.Env = append(os.Environ(), environments...) }因此子进程继承 Telegraf 进程的全部环境变量,environment中同名键排在后面。是否设置该数组都会创建子进程,只是未配置时不显式赋值cmd.Env。
2.3 timeout:命令超时与进程终止策略
timeout的默认值是5s(Timeout: config.Duration(time.Second * 5),见 exec.go)。超时的实际处理由通用的 internal.RunTimeout 完成:先c.Start()启动命令,再交给平台相关的WaitTimeout等待。
在非 Windows 平台上(internal/exec_unix.go),超时后的终止流程分两级:
- 超时触发后立即向进程组与进程发送
SIGTERM,给命令一次“优雅退出”的机会; - 若仍不退出,再等待
KillGrace(源码常量定义为 5 秒)后发送SIGKILL强杀。
还有一个细节:如果进程在被终止后返回的错误是“正常退出”(err == nil),WaitTimeout会将其视为成功——这允许子进程在收到SIGTERM后做干净的收尾工作。只有确实被终止信号打断时,才返回超时错误。
2.4 use_batch_format:批量模式 vs 单指标模式
这是该插件对性能影响最大的开关,源码默认值为true。Write 方法中两种路径的分支逻辑:
- 批量模式(
use_batch_format = true):调用serializer.SerializeBatch(metrics)把整批指标一次性序列化,然后只执行一次命令,把整个缓冲区写入其stdin。若序列化结果为空(buffer.Len() <= 0),直接返回nil,连命令都不启动; - 非批量模式(
use_batch_format = false):对批次中的每条指标逐一调用serializer.Serialize(metric),每条指标启动一次命令;所有命令的错误通过errors.Join合并后返回。
测试用例精确验证了这两种行为(exec_test.go):TestExternalOutputBatch写入 2 条指标后断言runner.runs == []int{2}(执行一次、一次处理 2 条);TestExternalOutputNoBatch断言runner.runs == []int{1, 1}(执行两次、每次 1 条)。
选型建议:批量模式显著减少进程创建次数,是绝大多数场景的合理默认;只有在下游程序要求“一条指标一次调用”(例如每条指标触发一次独立处理)时才关闭它。若下游是常驻程序,则应改用 execd 输出插件,进程只启动一次。
2.5 data_format:指标序列化格式
data_format决定指标写入stdin之前的编码方式。示例配置默认使用"influx"(InfluxDB Line Protocol),完整可选项及各自独有配置参数见 docs/DATA_FORMATS_OUTPUT.md。任何带data_format配置项的输出插件都可以参考该文档;例如输出 CSV 时配置data_format = "csv",下游命令就可以按列解析指标。
2.6 全局配置选项
作为输出插件,outputs.exec还继承 Telegraf 插件体系的全局配置能力,如指标/标签/字段的过滤修改、插件别名、插件执行顺序等,详见 CONFIGURATION.md 的 Plugins 章节。
三、源码级执行链路:一次 Write 的完整过程
3.1 Runner 抽象与真实执行
插件把“执行命令”抽象为Runner接口(Run(timeout, command, environments, buffer) error),生产实现是CommandRunner,测试中则替换为MockRunner(用 influx 流式解析器 统计收到的指标数),这使得批处理行为可以在不启动真实进程的前提下被断言验证。
真实执行的核心步骤(CommandRunner.Run):
exec.Command构建命令,按需合并环境变量;cmd.Stdin = buffer—— 序列化后的指标字节流成为命令的标准输入;cmd.Stderr指向内存缓冲区,捕获命令的标准错误输出;- 交给 internal.RunTimeout 启动并带超时等待(终止策略见 2.3 节)。
3.2 错误信息的分级呈现
命令失败时,错误信息按以下优先级组装并返回给输出管线(exec.go):
| 失败类型 | 返回错误信息 |
|---|---|
| 超时被终止 | "%q timed out and was killed" |
| 有退出码 | "%q exited %d with %w"(internal.ExitStatus提取状态码) |
| 其他 | "%q failed with %w" |
stderr的处理比文档描述更精细。文档层面写的是“stderr 会被记录进日志”,而源码中它先经过 truncate 处理:先按maxStderrBytes(源码常量 512 字节)截断,再裁到第一个换行符为止,若发生截断则追加...。随后按日志级别输出:
- 当前日志级别低于
debug时:以Errorf记录截断后的 stderr(一行、至多 512 字节); - 日志级别为
debug时:以Debugf记录完整stderr。
也就是说,日常排查时能在错误日志中直接看到命令失败的第一行原因;需要全量诊断输出时把 Telegraf 日志调到 debug 即可。truncate的行为有专门的单元测试覆盖(TestTruncate:超长输出截到 512 字节加...,多行输出只保留首行)。
另外在 Windows 上(removeWindowsCarriageReturns),记录日志前会剥离\r,避免 CRLF 造成日志行错乱。
3.3 生命周期方法
Connect与Close均为空实现(exec.go)——插件无持久连接或资源需要管理,这也印证了它“无状态、随写随启动进程”的设计。Init仅负责把执行器初始化为CommandRunner。
四、实战配置示例
基于插件的语义(stdin 接收指标文本),以下是几种典型用法,均为在telegraf.conf中追加[[outputs.exec]]段即可生效的配置:
# 1) 把 Line Protocol 追加落盘(插件示例同款用法) [[outputs.exec]] command = ["tee", "-a", "/var/log/metrics.ln"] use_batch_format = true # 2) 用 awk 只透传特定测量名 [[outputs.exec]] command = ["awk", "/^cpu /"] data_format = "influx" # 3) 以 JSON 行输出给消费脚本,并通过 environment 传递接收端信息 [[outputs.exec]] command = ["my-metric-consumer"] data_format = "json" environment = ["SINK_HOST=localhost", "SINK_PORT=9192"] timeout = "10s"使用注意:
command[0]若为相对名,按 Telegraf 进程的PATH查找;子进程继承 Telegraf 的系统环境;- 命令不读取
stdout(源码中Stdout未接管),下游程序应把结果写到文件、网络或stderr; - 由于每次 write 都 fork 新进程,高频指标批次下
timeout不宜过小,否则可能出现“timed out and was killed”;反过来超时过长会让慢命令拖住输出缓冲区,需结合输出插件的buffer_*配置权衡。
五、outputs.exec 与 outputs.execd 的取舍
仓库中两个插件的文档形成明确对照:
- exec:每次 write 创建新进程;无
restart_delay;use_batch_format默认开启;适合“把一批指标当作一次性输入喂给工具命令”的场景(管道、转换、落盘); - execd:命令只执行一次并作为常驻守护进程,指标在其生命周期内通过
stdin持续传入;提供restart_delay(进程意外退出后的重启延迟,示例默认10s)等守护参数;适合长生命周期的接收端程序。
文档明确建议:“为了更好的性能,考虑持续运行的 execd”。
六、小结
outputs.exec是 Telegraf 中把指标“投递给外部程序”最直接的通道:command列表 +stdin数据流 +data_format决定内容,use_batch_format决定进程创建频率,timeout通过SIGTERM → SIGKILL两级终止保护输出链路不被慢命令拖死,stderr则按日志级别被完整或截断地写入 Telegraf 日志。源码层面的关键实现集中在 plugins/outputs/exec/exec.go(序列化与错误处理)、internal/exec.go 与 internal/exec_unix.go(超时终止),行为断言见 plugins/outputs/exec/exec_test.go,可据此进一步深入。
【免费下载链接】telegrafAgent for collecting, processing, aggregating, and writing metrics, logs, and other arbitrary data.项目地址: https://gitcode.com/GitHub_Trending/te/telegraf
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考