在 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.rs | Rust 侧核心过滤器:解析传入的 JSON 记录、附加时间与 tag 信息后重新输出 JSON |
| rust_clib_filter.c | C 侧桥接函数:把char*风格参数换算为长度后转调 Rust 导出函数 |
| Cargo.toml | Rust crate 配置,声明cdylib/staticlib/rlib三种库形态 |
| Makefile | 一键构建脚本:Rust 编译 → cbindgen 生成头文件 → WASI SDK 的 clang 链接出.wasm |
这种"Rust 核心 + C 壳"的结构,使 Rust 开发者可以完全使用自己熟悉的 crate 生态(本例用到了serde_json与chrono),同时又通过 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/clang、share等直接落在/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.wasmMakefile 背后的三步流水线
从 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 crustclib:cargo 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"]决定)。rust_clib_filter.h:cbindgen --crate filter_rust_clib --output filter_rust_clib.h --lang c根据 Rust 源码中#[no_mangle] pub extern "C"的导出函数生成 C 声明。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 运行时能够定位并调用它的前提;- 函数签名携带
tag、tag_len、time_sec、time_nsec、record、record_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.wasm→stdout输出插件打印处理结果。启动后可在终端看到每条记录都被重写为包含message、time、tag、original、lang: "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_Name | rust_clib_filter | .wasm中要调用的导出函数名,必填,对应 C 入口rust_clib_filter |
accessible_paths | .,/path/to/fluent-bit | WASM 程序可访问的目录列表,逗号分隔;默认值是当前工作目录. |
event_format | json(默认)/msgpack | 传给 WASM 程序的事件编码格式;本示例按默认 JSON 路径工作,cb_wasm_filter中通过flb_msgpack_to_json_str将 msgpack 记录转为 JSON 再调用 WASM |
wasm_heap_size | 可选,默认8192 | WASM 运行时堆大小(KB),仅当配置值大于默认值时才覆盖 |
wasm_stack_size | 可选,默认8192 | WASM 运行时栈大小(KB),覆盖逻辑同上 |
从源码看调用链路
filter_wasm.c 揭示了过滤器与 WASM 程序的实际交互流程:
- 初始化:
cb_wasm_init读取配置,通过flb_wasm_instantiate把.wasm实例化一次并持久保存在过滤器上下文中,后续每条记录复用同一实例; - 逐记录处理:
cb_wasm_filter先用flb_log_event_decoder解码输入事件;在 JSON 模式下,将事件 body 经flb_msgpack_to_json_str编码为 JSON 字符串,连同tag、时间戳一起交给flb_wasm_call_function_format_json执行 WASM 函数; - 结果回填: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_json、chrono等成熟库实现记录改写,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),仅供参考