Fluent Bit 中的 CTraces:C 语言分布式追踪上下文的构建、OpenTelemetry 编解码与实战示例
2026/9/16 22:23:54 网站建设 项目流程

Fluent Bit 中的 CTraces:C 语言分布式追踪上下文的构建、OpenTelemetry 编解码与实战示例

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

CTraces 是内嵌于 Fluent Bit 仓库的lib/ctraces子库,一个体积极小的 C 语言库,用于创建和维护 Traces(分布式追踪)上下文,并提供与 OpenTelemetry 等格式互操作的编码/解码工具。本文以 lib/ctraces/README.md 为主线,完整覆盖其构建方式、API 使用路径与示例代码,并结合仓库源码剖析其数据模型(Resource → Scope → Span 层级)、OTel 编解码接口,以及 Fluent Bit 内部实际调用它的位置,读完后可理解 Fluent Bit 支持 OpenTelemetry 数据的核心链路。

项目定位:Fluent Bit 的核心追踪库

README 开篇即给出定位:

The CTraces project is a tiny library to create and maintain Traces contexts and provide utilities for data manipulation, including encoding/decoding for compatibility with OpenTelemetry and other formats.

即:CTraces 提供两类能力——

  1. 追踪上下文管理:在 C 程序中以结构化方式组织 Resource、Scope、Span、Event、Link 等追踪实体;
  2. 多格式编解码:支持将上下文编码为可读文本、MsgPack、OpenTelemetry(protobuf)字节流,并能反向解码 OTel protobuf 字节流回上下文。

README 明确指出该库是 Fluent Bit 的核心库("This project is a core library for Fluent Bit")。这一说法在仓库中可以得到印证:Fluent Bit 主程序的 OpenTelemetry 处理模块 src/opentelemetry/flb_opentelemetry_traces.c 直接调用ctr_decode_opentelemetry_create等 CTraces API,把收到的 OTLP 追踪数据解析为 CTraces 上下文,再经由编码器输出。换句话说,in_opentelemetry/out_opentelemetry等插件背后的追踪数据模型,正是由 CTraces 承载的。

构建:从源码编译 CTraces

README 给出的构建流程如下(克隆的是 CTraces 独立仓库;在本 Fluent Bit 仓库中,该库已以子目录 lib/ctraces 的形式存在,可跳过克隆直接编译):

# 1. 克隆仓库(本仓库中已包含于 lib/ctraces,可省略) git clone https://github.com/calyptia/ctraces # 2. 进入目录并初始化子模块 cd ctraces git submodule update --init --recursive --remote # 3. 编译 cd build/ cmake -DCTR_DEV=on ../ make

关于CTR_DEV标志,README 特别说明:

CTR_DEV flag enables debugging mode, examples and the unit tests

即只有开启CTR_DEV=on时,构建系统才会一并编译调试模式、examples/目录下的示例程序以及tests/目录下的单元测试。查看 lib/ctraces/tests/CMakeLists.txt 与 lib/ctraces/examples/CMakeLists.txt 可确认测试与示例正是挂在开发构建目标下的:单元测试 tests/basic.c、tests/span.c、tests/decoding.c 基于 tests/lib/acutest 测试框架构成。

依赖方面,从源码结构看,CTraces 依赖两个基础库,均在 Fluent Bit 仓库内以独立子目录形式维护:

  • CFL(lib/cfl):提供cfl_arraycfl_kvlistcfl_listcfl_sds_t(字符串/缓冲)等数据结构,CTraces 的 ID、属性、缓冲区全部基于 CFL 构建;
  • mpack(lib/ctraces/lib/mpack):MsgPack 编解码,供ctr_encode_msgpack/ctr_decode_msgpack使用。

上下文与数据模型:从源码看 OTLP 层级

主头文件 lib/ctraces/include/ctraces/ctraces.h 汇总了全部 API,其中上下文结构体揭示了整体组织方式:

struct ctrace { uint64_t last_span_id; /* 已分配的最大 span id,每新建 span 递增 */ struct cfl_list resource_spans; /* resource 链表:创建 resource 后挂入,span 仅持引用 */ struct cfl_list span_list; /* 全局 span 链表:供调用方线性遍历所有 span */ int log_level; /* 日志级别 */ void (*log_cb)(void *, int, const char *, int, const char *); /* 日志回调 */ }; struct ctrace *ctr_create(struct ctrace_opts *opts); void ctr_destroy(struct ctrace *ctx);

从源码结构看,一个struct ctrace上下文采用与 OpenTelemetry 协议(OTLP)一致的三层嵌套结构:

  1. Resource Spanctr_resource_span_create):描述产生追踪数据的资源(如服务、主机),可设置schema_urldropped_attr_count等字段;
  2. Scope Spanctr_scope_span_create):隶属于某个 Resource Span,代表一个"测量作用域",通过 lib/ctraces/include/ctraces/ctr_scope.h 中的ctr_instrumentation_scope_create(name, version, dropped_attr_count, attr)挂载一个可选的 Instrumentation Scope(如示例中的"ctrace", "a.b.c");
  3. Span:隶属于 Scope Span,是追踪的最小单元。

每个 Span 的字段在 lib/ctraces/include/ctraces/ctr_span.h 中完整定义,与 OTel 的trace.proto对齐(头文件注释中直接引用了该 proto 的来源):

struct ctrace_span { struct ctrace_id *trace_id; /* 追踪 ID */ struct ctrace_id *span_id; /* 本 span 的唯一 ID */ struct ctrace_id *parent_span_id; /* 父 span;NULL 表示根 span */ cfl_sds_t trace_state; /* trace state */ int32_t flags; cfl_sds_t name; int kind; /* span kind */ uint64_t start_time_unix_nano; uint64_t end_time_unix_nano; struct ctrace_attributes *attr; uint32_t dropped_attr_count; struct cfl_list events; /* 事件列表 */ struct cfl_list links; /* link 列表 */ cfl_sds_t schema_url; struct ctrace_span_status status; /* 状态码 */ ... };

两个值得注意的设计点:

  • parent_span_id == NULL即根 span:创建子 span 时把父 span 传入ctr_span_create(ctx, scope_span, name, parent)即可建立父子关系;
  • span_list全局链表:每个 span 同时挂在所属 Scope Span 的链表和struct ctrace->span_list上,调用方无需层层嵌套遍历,即可线性访问全部 span。

Span 种类与状态码在ctr_span.h中以常量定义,与 OTel 规范一一对应:

Span Kind常量
未指定CTRACE_SPAN_UNSPECIFIED0
内部调用CTRACE_SPAN_INTERNAL1
服务端CTRACE_SPAN_SERVER2
客户端CTRACE_SPAN_CLIENT3
生产者CTRACE_SPAN_PRODUCER4
消费者CTRACE_SPAN_CONSUMER5

状态码:CTRACE_SPAN_STATUS_CODE_UNSET(0)、CTRACE_SPAN_STATUS_CODE_OK(1)、CTRACE_SPAN_STATUS_CODE_ERROR(2),通过ctr_span_set_status(span, code, message)设置。

ID 管理:ctr_id

追踪 ID 与 Span ID 由 lib/ctraces/include/ctraces/ctr_id.h 管理,尺寸常量直接对应 OTel 约定:

#define CTR_ID_OTEL_TRACE_SIZE 16 /* trace_id: 16 字节 (32 个 hex 字符) */ #define CTR_ID_OTEL_SPAN_SIZE 8 /* span_id: 8 字节 (16 个 hex 字符) */

可用 API 包括:ctr_id_create_random(size)生成随机 ID、ctr_id_create(buf, len)从二进制缓冲构造、ctr_id_from_base16/ctr_id_to_lower_base16完成 base16 字符串互转、ctr_id_cmp比较、ctr_id_set覆写内容。示例中"用完即释放"(ctr_id_destroy)的用法体现了 ID 值在设置进 span 后即被复制,调用方无需长期持有。

实战示例一:构建并文本化输出一个追踪(simple-c-api)

README 的 Usage 一节指向examples/目录,称其中有一个"simple"示例描述 API 用法,即 lib/ctraces/examples/simple-c-api.c。该示例完整走通了"建上下文 → 建层级 → 造 span/事件/link → 文本编码"的全流程,其调用顺序是学习 CTraces API 的最佳路径:

/* 1. 初始化选项(可选,创建上下文时也可传 NULL) */ ctr_opts_init(&opts); /* 2. 创建 ctrace 上下文 */ ctx = ctr_create(&opts); if (!ctx) { ctr_opts_exit(&opts); exit(EXIT_FAILURE); } /* 3. Resource Span 及其 resource */ resource_span = ctr_resource_span_create(ctx); ctr_resource_span_set_schema_url(resource_span, "https://ctraces/resource_span_schema_url"); resource = ctr_resource_span_get_resource(resource_span); ctr_resource_set_dropped_attr_count(resource, 5); /* 4. Scope Span 与 Instrumentation Scope */ scope_span = ctr_scope_span_create(resource_span); ctr_scope_span_set_schema_url(scope_span, "https://ctraces/scope_span_schema_url"); instrumentation_scope = ctr_instrumentation_scope_create("ctrace", "a.b.c", 3, NULL); ctr_scope_span_set_instrumentation_scope(scope_span, instrumentation_scope); /* 5. 生成随机 trace_id 与 span_id */ trace_id = ctr_id_create_random(CTR_ID_OTEL_TRACE_SIZE); span_id = ctr_id_create_random(CTR_ID_OTEL_SPAN_SIZE); /* 6. 创建根 span(parent 为 NULL)并绑定 ID */ span_root = ctr_span_create(ctx, scope_span, "main", NULL); ctr_span_set_span_id_with_cid(span_root, span_id); ctr_span_set_trace_id_with_cid(span_root, trace_id); /* 7. 为 span 添加各类属性 */ ctr_span_set_attribute_string(span_root, "agent", "Fluent Bit"); ctr_span_set_attribute_int64(span_root, "year", 2022); ctr_span_set_attribute_bool(span_root, "open_source", CTR_TRUE); ctr_span_set_attribute_double(span_root, "temperature", 25.5); ctr_span_set_attribute_array(span_root, "my_array", array); /* CFL 数组 */ ctr_span_set_attribute_kvlist(span_root, "my-list", kv); /* CFL 键值对列表 */ /* 8. 添加事件及事件属性 */ event = ctr_span_event_add(span_root, "connect to remote server"); ctr_span_event_set_attribute_string(event, "syscall 1", "open()"); ... /* 9. 创建子 span:复用父 span 的 trace_id,父 span_id 作为 parent_span_id */ span_child = ctr_span_create(ctx, scope_span, "do-work", span_root); ctr_span_set_trace_id_with_cid(span_child, trace_id); ctr_span_set_parent_span_id_with_cid(span_child, span_id); ctr_span_kind_set(span_child, CTRACE_SPAN_CLIENT); /* 10. 创建 Link(跨追踪引用) */ link = ctr_link_create_with_cid(span_child, trace_id, span_id); ctr_link_set_trace_state(link, "aaabbbccc"); ctr_link_set_dropped_attr_count(link, 2); /* 11. 编码为可读文本并打印 */ text = ctr_encode_text_create(ctx); printf("%s\n", text); ctr_encode_text_destroy(text); /* 12. 释放上下文与选项 */ ctr_destroy(ctx); ctr_opts_exit(&opts);

该示例同时展示了几个关键实践细节:

  • 属性类型全覆盖ctr_span_set_attribute_string/int64/bool/double/array/kvlist覆盖了 OTelKeyValue支持的标量、数组、键值对列表形态;数组与 kvlist 直接用 CFL API(cfl_array_createcfl_kvlist_insert_string)构造;
  • 父子 span 的 ID 规则:子 span 继承相同trace_id,但parent_span_id指向父 span 的span_id,且自身重新生成span_id——这正是示例中"destroy 旧 span_id 再随机生成"的原因;
  • 时间控制:除示例使用的创建方式外,ctr_span_start/ctr_span_end(以及指定纳秒时间戳的ctr_span_start_ts/ctr_span_end_ts)用于给 span 打起止时间戳。

实战示例二:编码为 OTLP 并推送给 OpenTelemetry Collector(otlp-encoder)

lib/ctraces/examples/otlp-encoder/otlp-encoder.c 演示了 CTraces 最核心的互操作能力——把上下文编码为 OpenTelemetry protobuf 字节流。其结尾的编码与发送逻辑:

/* Encode Trace as otlp buffer */ buf = ctr_encode_opentelemetry_create(ctx); /* 返回 cfl_sds_t 字节缓冲 */ ... headers = curl_slist_append(headers, "Content-Type: application/x-protobuf"); curl_easy_setopt(curl, CURLOPT_URL, "0.0.0.0:4318/v1/traces"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, buf); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, cfl_sds_len(buf)); res = curl_easy_perform(curl);

即一次调用ctr_encode_opentelemetry_create(声明见 lib/ctraces/include/ctraces/ctr_encode_opentelemetry.h,返回cfl_sds_t,用完需以ctr_encode_opentelemetry_destroy释放)即可得到可直接 POST 到 OTLP/HTTP 端点的完整ExportTraceServiceRequest字节流。

该目录下的 README 给出了端到端验证方式:以CTR_DEV=on构建后,示例二进制位于build/examples/ctraces-otlp-encoder;启动本地 OpenTelemetry Collector 并配置如下接收端:

receivers: otlp: protocols: http: endpoint: "0.0.0.0:4318" exporters: logging: loglevel: debug service: pipelines: traces: receivers: [otlp] exporters: [logging]

运行示例后,Collector 的loggingexporter 会输出该示例构造的完整追踪数据,可在 Collector 日志中看到 span、属性、事件、link,从而验证编码结果与 OTLP 协议完全兼容。

实战示例三:从 OTLP 字节流解码回上下文(otlp-decoder)

反方向由 lib/ctraces/examples/otlp-decoder.c 演示:读取仓库自带的样例二进制文件 lib/ctraces/examples/sample_trace.bin(一份真实 OTLP 追踪报文),调用ctr_decode_opentelemetry_create(&ctr, buf, bufsize, &offset)将其解码为struct ctrace上下文,随后用ctr_encode_text_create转成可读文本打印:

result = ctr_decode_opentelemetry_create(&ctr, buf, bufsize, &offset); if (result == -1) { printf("Unable to decode trace sample"); } text = ctr_encode_text_create(ctr); printf("%s\n", text);

解码成功后获得的上下文与手工构建的上下文完全同构,可继续用 span 查询、属性读取等 API 遍历。这一"解码 → 内存上下文 → 再编码"的能力正是 Fluent Bit 作为 OTel 数据中继/加工层的基础。

编解码器全景与 Fluent Bit 内部调用点

从 lib/ctraces/include/ctraces/ctraces.h 的 include 汇总可归纳 CTraces 的编解码矩阵:

方向格式入口 API源码
编码可读文本ctr_encode_text_createlib/ctraces/src/ctr_encode_text.c
编码MsgPackctr_encode_msgpack_createlib/ctraces/src/ctr_encode_msgpack.c
编码OpenTelemetry (protobuf)ctr_encode_opentelemetry_createlib/ctraces/src/ctr_encode_opentelemetry.c
解码MsgPackctr_decode_msgpack_createlib/ctraces/src/ctr_decode_msgpack.c
解码OpenTelemetry (protobuf)ctr_decode_opentelemetry_createlib/ctraces/src/ctr_decode_opentelemetry.c

在 Fluent Bit 主程序中,OTLP 追踪处理落在 src/opentelemetry/ 目录:flb_opentelemetry_traces.c负责追踪数据的解析与重组,flb_opentelemetry_otlp_proto.c处理 protobuf 层细节。前者即ctr_decode_opentelemetry_createctr_span_set_trace_id等 API 的直接调用方——Fluent Bit 收到 OTLP 报文后先解码为 CTraces 上下文,需要输出时再经编码器回写字节流。

小结

  • 构建cmake -DCTR_DEV=on+makeCTR_DEV开关同时启用调试、示例(examples/)与单元测试(tests/);仓库内库位于 lib/ctraces,依赖 lib/cfl 与内置 mpack。
  • 数据模型struct ctrace上下文按 OTLP 层级组织 Resource Span → Scope Span(含 Instrumentation Scope)→ Span,span 间以parent_span_id建立父子关系,全局span_list支持线性遍历。
  • 核心 APIctr_create/ctr_destroy管理生命周期;ctr_span_createctr_span_set_*系列构建 span 与属性(string/int64/bool/double/array/kvlist);ctr_id_create_random配合CTR_ID_OTEL_TRACE_SIZE(16B)/CTR_ID_OTEL_SPAN_SIZE(8B)生成合规 ID。
  • 互操作ctr_encode_opentelemetry_create/ctr_decode_opentelemetry_create实现与 OpenTelemetry Collector 的双向兼容,可直接对接4318OTLP/HTTP 端点;Fluent Bit 的 OTel 插件链正是构建在这套编解码之上。
  • 许可与作者:Apache License v2.0,Calyptia Team 开发维护(见 lib/ctraces/LICENSE)。

深入阅读建议从 lib/ctraces/examples/simple-c-api.c 起步,再依次运行 otlp-encoder/otlp-decoder 两个示例验证编解码链路,最后对照 lib/ctraces/src/ 与 src/opentelemetry/flb_opentelemetry_traces.c 观察 CTraces 在 Fluent Bit 生产路径中的实际用法。

【免费下载链接】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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询