news 2026/9/16 19:36:22

在 Fluent Bit 中编写 Rust + C 混合 WASM 过滤器:filter_rust_clib 示例完整实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在 Fluent Bit 中编写 Rust + C 混合 WASM 过滤器:filter_rust_clib 示例完整实战指南

在 Fluent Bit 中编写 Rust + C 混合 WASM 过滤器:filter_rust_clib 示例完整实战指南

【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit

导读

本指南以 Fluent Bit 官方示例filter_rust_clib为核心,讲解如何在 Fluent Bit 中构建并运行一个"以 Rust 编写核心逻辑、以 C 导出 WASI 入口"的 WASM 过滤器:Rust 侧负责 JSON 解析与字段重组,C 侧负责把 Fluent Bit 传入的参数适配为 Rust 的 C ABI 调用,最终编译为单文件.wasm,通过filter_wasm插件挂载进 Fluent Bit 数据管道。读完本文,你将掌握完整的环境准备、构建命令、配置参数含义,以及从源码层面理解 WASM 过滤器与 Fluent Bit 的调用契约。

示例源码位于 examples/filter_rust_clib/,与本示例配套的纯 C 版本见 examples/filter_wasm_c/,更精简的纯 Rust 版本见 examples/filter_rust/。

示例定位:Rust 写逻辑、C 做桥接的 WASI 过滤器

filter_rust_clib在官方示例中承担一个独特角色:它演示了"Rust 与 C 混编"的 WASM 过滤器形态。README 中明确说明该源码树提供的正是一个"以 Rust 为主编写的、运行在 WASI 模式下的 WASM 程序"(见 README 首段)。

整个示例只有 4 个文件,分工清晰:

文件作用
src/lib.rsRust 侧核心过滤器:解析传入的 JSON 记录、附加时间与 tag 信息后重新输出 JSON
rust_clib_filter.cC 侧桥接函数:把char*风格参数换算为长度后转调 Rust 导出函数
Cargo.tomlRust crate 配置,声明cdylib/staticlib/rlib三种库形态
Makefile一键构建脚本:Rust 编译 → cbindgen 生成头文件 → WASI SDK 的 clang 链接出.wasm

这种"Rust 核心 + C 壳"的结构,使 Rust 开发者可以完全使用自己熟悉的 crate 生态(本例用到了serde_jsonchrono),同时又通过 C 层保持与 Fluent Bit WASI 运行时 ABI 的稳定兼容。

环境准备:编译器、工具链与 WASI SDK

需要安装的工具

工具用途版本要求
Rust / rustc编译 Rust 过滤器README 注明使用 rustc 1.61.0(fe5b13d68 2022-05-18)
rustup管理 Rust 工具链与 target用于添加wasm32-unknown-unknown目标
cbindgen由 Rust crate 自动生成 C 头文件用于声明 Rust 导出的 C 风格函数
WASI SDK提供 WASI 版 clang,负责最终链接生成.wasm示例使用 WASI SDK 14

Ubuntu 下安装 WASI SDK

README 给出了基于wget的安装方式,把 SDK 释放到/opt/wasi-sdk

$ export WASI_VERSION=14 $ export WASI_VERSION_FULL=${WASI_VERSION}.0 $ wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_VERSION}/wasi-sdk-${WASI_VERSION_FULL}-linux.tar.gz $ sudo mkdir -p /opt/wasi-sdk/ $ sudo tar xvf wasi-sdk-${WASI_VERSION_FULL}-linux.tar.gz --strip-components=1 -C /opt/wasi-sdk

--strip-components=1的作用是把压缩包根目录剥掉一层,让bin/clangshare等直接落在/opt/wasi-sdk下,与 Makefile 中WASI_SDK_ROOT ?= /opt/wasi-sdk的默认值保持一致。如果你安装在其他目录,通过WASI_SDK_ROOT=/your/path make build覆盖即可。

构建步骤:从 Rust 源码到单文件 wasm

第一步:添加 wasm 编译目标

Rust 默认不包含 WebAssembly 后端,需要用 rustup 显式添加:

$ rustup target add wasm32-unknown-unknown

第二步:安装 cbindgen

cbindgen用于从 Rust 侧导出符号自动生成 C 头文件filter_rust_clib.h

$ cargo install --force cbindgen

第三步:执行 make build

$ make build

构建完成后,当前目录会生成最终产物:

$ ls *.wasm rust_clib_filter.wasm

Makefile 背后的三步流水线

从 Makefile 可以看到build目标实际串联了两个子目标:

rustclib: cargo build --target wasm32-unknown-unknown --release build: wasm rustclib wasm: rustclib rust_clib_filter.h ${WASI_SDK_ROOT}/bin/clang -O3 -nostdlib \ -z stack-size=${STACK_SIZE} -Wl,--initial-memory=${INITIAL_MEMORY_SIZE} \ -o rust_clib_filter.wasm rust_clib_filter.c \ -L ./target/wasm32-unknown-unknown/release -lfilter_rust_clib \ -Wl,--export=__heap_base -Wl,--export=__data_end -Wl,--export=rust_clib_filter \ -Wl,--no-entry -Wl,--strip-all -Wl,--allow-undefined rust_clib_filter.h: cbindgen --crate filter_rust_clib --output filter_rust_clib.h --lang c
  1. rustclibcargo build --release --target wasm32-unknown-unknown把 src/lib.rs 编译为target/wasm32-unknown-unknown/release/libfilter_rust_clib.a(静态库形态由 Cargo.toml 中的crate-type = ["cdylib", "staticlib", "rlib"]决定)。
  2. rust_clib_filter.hcbindgen --crate filter_rust_clib --output filter_rust_clib.h --lang c根据 Rust 源码中#[no_mangle] pub extern "C"的导出函数生成 C 声明。
  3. wasm:用 WASI SDK 的clang -O3 -nostdlib编译 rust_clib_filter.c,再-L ... -lfilter_rust_clib链接 Rust 静态库。注意-Wl,--export=rust_clib_filter显式导出过滤器入口,-Wl,--no-entry表示无 main 入口、-Wl,--allow-undefined容忍未定义符号(由 WASI 运行时补齐)、-Wl,--strip-all缩减产物体积,并设置了STACK_SIZE(默认 8192)与INITIAL_MEMORY_SIZE(默认 65536)两个内存参数。

源码剖析:Rust 过滤器与 C 桥接层

Rust 侧:rust_filter导出函数

核心逻辑位于 src/lib.rs 的rust_filter

#[no_mangle] pub extern "C" fn rust_filter(tag: *const c_char, tag_len: u32, time_sec: u32, time_nsec: u32, record: *const c_char, record_len: u32) -> *const u8 { let slice_tag: &[u8] = unsafe { slice::from_raw_parts(tag as *const u8, tag_len as usize) }; let slice_record: &[u8] = unsafe { slice::from_raw_parts(record as *const u8, record_len as usize) }; // ... let v: Value = serde_json::from_slice(slice_record).unwrap(); let dt = Utc.timestamp(time_sec as i64, time_nsec); let time = dt.format("%Y-%m-%dT%H:%M:%S.%9f %z").to_string(); let message = json!({ "message": v["message"], "time": format!("{}", time), "tag": vtag, "original": v.to_string(), "lang": "Rust", }); let buf: String = message.to_string(); buf.as_ptr() }

几个关键点:

  • #[no_mangle] pub extern "C"保证符号名不被修饰,且按 C ABI 传参,这是 WASI 运行时能够定位并调用它的前提;
  • 函数签名携带tagtag_lentime_sectime_nsecrecordrecord_len六个参数,与 Fluent Bit 传入记录的构成(tag、时间戳、msgpack/JSON 载荷)一一对应;
  • 内部用serde_json把记录解析为Value,再用chrono把秒/纳秒时间戳格式化为%Y-%m-%dT%H:%M:%S.%9f %z
  • 输出一个重组后的 JSON 对象:保留原message字段、追加格式化time、来源tag、完整original原文与标记"lang": "Rust"
  • 返回buf.as_ptr()——这里必须理解 WASM 线性内存模型:Rust 侧分配字符串后,返回的只是 WASM 堆上的指针,Fluent Bit 侧会依据返回指针在共享线性内存中读取结果,因此返回指针前字符串必须仍存活(buf在该函数返回后由运行时读取,示例以简化方式演示了这一契约)。

C 侧:rust_clib_filter桥接

rust_clib_filter.c 提供了一个薄封装,把 "指针 + 长度" 的调用换算成 C 字符串再转交 Rust:

#include "filter_rust_clib.h" char* rust_clib_filter(char* tag, int len, uint32_t sec, uint32_t nsec, char* record, int record_len) { return (char *)rust_filter(tag, strlen(tag), sec, nsec, record, strlen(record)); }

它包含filter_rust_clib.h——正是构建流程中由 cbindgen 自动生成的头文件,从而保证 C 侧对 Rust 函数签名的声明与 Rust 定义严格一致。这个 C 层同时也是-Wl,--export=rust_clib_filter导出到.wasm的最终入口,对应 Fluent Bit 配置中的Function_Name

在 Fluent Bit 中运行:WASI 集成验证

构建出rust_clib_filter.wasm后,按 README 提供的方式编写如下 Fluent Bit 配置进行验证:

[SERVICE] Flush 1 Daemon Off Log_Level info HTTP_Server Off HTTP_Listen 0.0.0.0 HTTP_Port 2020 [INPUT] Name dummy Tag dummy.local [FILTER] Name wasm match dummy.* WASM_Path /path/to/rust_clib_filter.wasm Function_Name rust_clib_filter accessible_paths .,/path/to/fluent-bit [OUTPUT] Name stdout Match *

配置链路为:dummy输入插件按Flush 1每秒生成一条带dummy.localtag 的测试记录 →wasm过滤器匹配dummy.*并把记录送入rust_clib_filter.wasmstdout输出插件打印处理结果。启动后可在终端看到每条记录都被重写为包含messagetimetagoriginallang: "Rust"字段的 JSON,从而验证 WASI 集成与 Rust 过滤器确实生效。

关键配置参数详解

下表结合 filter_wasm.c 的config_map(第 454 行起)逐项说明本示例涉及的参数:

参数示例取值含义与默认值
WASM_Path/path/to/rust_clib_filter.wasm待执行的.wasm文件路径,必填;插件在cb_wasm_pre_run阶段会用access(path, R_OK)校验可读性
Function_Namerust_clib_filter.wasm中要调用的导出函数名,必填,对应 C 入口rust_clib_filter
accessible_paths.,/path/to/fluent-bitWASM 程序可访问的目录列表,逗号分隔;默认值是当前工作目录.
event_formatjson(默认)/msgpack传给 WASM 程序的事件编码格式;本示例按默认 JSON 路径工作,cb_wasm_filter中通过flb_msgpack_to_json_str将 msgpack 记录转为 JSON 再调用 WASM
wasm_heap_size可选,默认8192WASM 运行时堆大小(KB),仅当配置值大于默认值时才覆盖
wasm_stack_size可选,默认8192WASM 运行时栈大小(KB),覆盖逻辑同上

从源码看调用链路

filter_wasm.c 揭示了过滤器与 WASM 程序的实际交互流程:

  1. 初始化cb_wasm_init读取配置,通过flb_wasm_instantiate.wasm实例化一次并持久保存在过滤器上下文中,后续每条记录复用同一实例;
  2. 逐记录处理cb_wasm_filter先用flb_log_event_decoder解码输入事件;在 JSON 模式下,将事件 body 经flb_msgpack_to_json_str编码为 JSON 字符串,连同tag、时间戳一起交给flb_wasm_call_function_format_json执行 WASM 函数;
  3. 结果回填:WASM 返回的 JSON 字符串再经flb_pack_json转回 msgpack,通过flb_log_event_encoder_set_body_from_raw_msgpack写回事件体;若返回值为空或 JSON 非法,则跳过该记录(FLB_FILTER_NOTOUCH)。

这套"msgpack 解码 → JSON 入 WASM → JSON 出 WASM → msgpack 回填"的契约,正是filter_rust_clib中 Rust 函数接收字符串、返回字符串的原因——在默认event_format json下,Rust 过滤器无需感知 msgpack,专注处理 JSON 即可。

常见问题与排查建议

  • cannot access wasm program报错WASM_Path指向的文件不可读,检查路径与权限;插件在cb_wasm_pre_run中执行access(R_OK)校验(见 filter_wasm.c 第 334-339 行)。
  • failed to instantiate wasm program.wasm文件缺失导出符号或格式不兼容,确认make build成功且Function_Name-Wl,--export导出的名称一致。
  • 记录被跳过(无输出):WASM 返回空字符串或非法 JSON 时插件会跳过记录,可用Log_Level debug观察filter_wasm的调试日志定位。
  • 清理构建产物make clean会删除*.wasm*.h并执行cargo clean

小结

filter_rust_clib完整演示了一条"Rust 核心逻辑 + C ABI 桥接 + WASI SDK 链接 + filter_wasm 插件挂载"的过滤器开发路径:Rust 侧借助serde_jsonchrono等成熟库实现记录改写,C 侧保证与 Fluent Bit 调用契约的稳定对接,最终以单文件.wasm交付、由accessible_paths控制文件系统访问边界。在此基础上,你可以参考 examples/filter_wasm_c/ 对比纯 C 实现,或参考 examples/filter_rust/ 与 examples/filter_rust_msgpack/ 探索更纯粹的 Rust 写法与 msgpack 模式,从而根据团队技术栈选择最合适的 WASM 过滤器开发方式。

【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 19:35:18

MathType几何符号输入全攻略:LaTeX命令、面板与快捷键

在公式排版这个圈子里混久了,你会发现一个挺有意思的现象:真正让人卡住的往往不是积分、矩阵这类"重"公式,而是角度、平行、垂直、全等、相似这些看起来再简单不过的几何符号。我见过不少老师,能把三重积分敲得飞快&…

作者头像 李华
网站建设 2026/9/16 19:34:53

TypeScript技能契约设计:Nx monorepo中的可插拔能力建模

1. 项目概述:一个被严重低估的“技能容器”设计范式 “agent-skills”这四个字乍看像某个开源库的包名,或是某篇技术文档里的小节标题,但如果你在Nx monorepo里反复看到它出现在 libs/agent-skills 路径下,又在TypeScript类型定…

作者头像 李华
网站建设 2026/9/16 19:33:48

Linux性能分析利器perf:从perf stat到火焰图与动态追踪

聊Linux性能分析,绕不开perf。它是Linux内核自带的性能剖析工具,从CPU热点定位、缓存失效分析,到内核函数动态插桩、火焰图生成,几乎覆盖了日常性能排查的所有主流场景。简单说,perf就是一套“内核级探针采样器数据分析…

作者头像 李华
网站建设 2026/9/16 19:32:51

Sonoma下CocoaPods安装失败?用rbenv管理Ruby环境一劳永逸

1. Sonoma下安装CocoaPods为什么总是翻车1.1 系统自带Ruby的那个"坑"2024年把Mac升级到Sonoma之后,很多iOS开发者做的第一件事就是打开终端,敲下那句看了无数遍的命令:gem install cocoapods然后下一秒就被红色报错糊了一脸&#x…

作者头像 李华