You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

咨询:通过Rust FFI从C调用OpenTelemetry时保留Trace/Span引用的方法

解决Rust FFI中跨调用保留OpenTelemetry Trace/Span的问题

针对你遇到的跨FFI调用无法保留Trace/Span的问题,核心矛盾是OpenTelemetry的Tracer和Span属于Rust复杂类型,无法直接在C侧传递或存储。以下是三种实用的解决方案,兼顾内存安全和易用性:


方案1:用不透明指针包装实例

将Rust侧的Tracer和Span打包成*mut c_void(不透明指针)传递给C,C只需保存指针,后续FFI调用时回传即可。Rust侧负责类型转换和内存管理。

Rust侧代码

use opentelemetry::{trace::{Tracer, Span}, global};
use std::os::raw::c_void;
use std::ptr;

// 创建并返回Tracer的不透明指针
#[no_mangle]
pub extern "C" fn otel_create_tracer() -> *mut c_void {
    let tracer = global::tracer("my-ffi-tracer");
    Box::into_raw(Box::new(tracer)) as *mut c_void
}

// 通过Tracer指针启动Span,返回Span的不透明指针
#[no_mangle]
pub extern "C" fn otel_start_span(tracer_ptr: *mut c_void, name: *const u8) -> *mut c_void {
    if tracer_ptr.is_null() || name.is_null() {
        return ptr::null_mut();
    }

    // 临时取出Tracer,用完放回堆内存(避免提前释放)
    let tracer = unsafe { Box::from_raw(tracer_ptr as *mut dyn Tracer) };
    let span_name = unsafe { std::ffi::CStr::from_ptr(name).to_str().unwrap_or("unknown") };
    let span = tracer.start(span_name);
    let _ = Box::into_raw(tracer);

    Box::into_raw(Box::new(span)) as *mut c_void
}

// 结束Span并释放内存
#[no_mangle]
pub extern "C" fn otel_end_span(span_ptr: *mut c_void) {
    if !span_ptr.is_null() {
        let mut span = unsafe { Box::from_raw(span_ptr as *mut dyn Span) };
        span.end();
        // Span会在此处自动drop释放
    }
}

// 销毁Tracer并释放内存
#[no_mangle]
pub extern "C" fn otel_destroy_tracer(tracer_ptr: *mut c_void) {
    if !tracer_ptr.is_null() {
        let _tracer = unsafe { Box::from_raw(tracer_ptr as *mut dyn Tracer) };
        // Tracer会在此处自动drop释放
    }
}

C侧调用示例

#include <stdio.h>

void* otel_create_tracer();
void* otel_start_span(void* tracer, const char* name);
void otel_end_span(void* span);
void otel_destroy_tracer(void* tracer);

int main() {
    void* tracer = otel_create_tracer();
    void* parent_span = otel_start_span(tracer, "main_process");
    
    // 创建嵌套Span
    void* child_span = otel_start_span(tracer, "sub_task");
    // 执行业务逻辑...
    otel_end_span(child_span);
    
    otel_end_span(parent_span);
    otel_destroy_tracer(tracer);
    return 0;
}

方案2:全局存储+ID映射

在Rust侧维护全局哈希表,将Tracer和Span映射为整数ID,C侧只需传递ID即可。适合不想让C管理指针的场景。

Rust侧代码

use opentelemetry::{trace::{Tracer, Span}, global};
use lazy_static::lazy_static;
use std::collections::HashMap;
use std::sync::Mutex;
use std::os::raw::c_int;

// 全局线程安全存储
lazy_static! {
    static ref TRACERS: Mutex<HashMap<c_int, Box<dyn Tracer>>> = Mutex::new(HashMap::new());
    static ref SPANS: Mutex<HashMap<c_int, Box<dyn Span>>> = Mutex::new(HashMap::new());
    static ref NEXT_ID: Mutex<c_int> = Mutex::new(1);
}

// 生成唯一ID
fn get_next_id() -> c_int {
    let mut id = NEXT_ID.lock().unwrap();
    let current = *id;
    *id += 1;
    current
}

#[no_mangle]
pub extern "C" fn otel_create_tracer() -> c_int {
    let tracer = global::tracer("my-ffi-tracer");
    let id = get_next_id();
    TRACERS.lock().unwrap().insert(id, Box::new(tracer));
    id
}

#[no_mangle]
pub extern "C" fn otel_start_span(tracer_id: c_int, name: *const u8) -> c_int {
    let span_name = unsafe { std::ffi::CStr::from_ptr(name).to_str().unwrap_or("unknown") };
    let mut tracers = TRACERS.lock().unwrap();
    
    if let Some(tracer) = tracers.get_mut(&tracer_id) {
        let span = tracer.start(span_name);
        let span_id = get_next_id();
        SPANS.lock().unwrap().insert(span_id, Box::new(span));
        span_id
    } else {
        -1 // 返回错误标识
    }
}

#[no_mangle]
pub extern "C" fn otel_end_span(span_id: c_int) {
    let mut spans = SPANS.lock().unwrap();
    if let Some(mut span) = spans.remove(&span_id) {
        span.end();
    }
}

#[no_mangle]
pub extern "C" fn otel_destroy_tracer(tracer_id: c_int) {
    TRACERS.lock().unwrap().remove(&tracer_id);
}

C侧调用示例

#include <stdio.h>

int otel_create_tracer();
int otel_start_span(int tracer_id, const char* name);
void otel_end_span(int span_id);
void otel_destroy_tracer(int tracer_id);

int main() {
    int tracer_id = otel_create_tracer();
    int parent_id = otel_start_span(tracer_id, "main");
    
    int child_id = otel_start_span(tracer_id, "nested_task");
    // 执行操作...
    otel_end_span(child_id);
    
    otel_end_span(parent_id);
    otel_destroy_tracer(tracer_id);
    return 0;
}

方案3:线程本地存储(TLS)

如果C代码是单线程或每个线程的Trace/Span独立,可使用Rust的thread_local!宏存储当前线程的Trace/Span上下文,无需C传递任何标识。

Rust侧代码

use opentelemetry::{trace::{Tracer, Span}, global};
use std::cell::RefCell;

// 线程本地存储当前Tracer和Span
thread_local! {
    static CURRENT_TRACER: RefCell<Option<Box<dyn Tracer>>> = RefCell::new(None);
    static CURRENT_SPAN: RefCell<Option<Box<dyn Span>>> = RefCell::new(None);
}

#[no_mangle]
pub extern "C" fn otel_init_thread_tracer() {
    CURRENT_TRACER.with(|t| {
        *t.borrow_mut() = Some(Box::new(global::tracer("my-ffi-tracer")));
    });
}

#[no_mangle]
pub extern "C" fn otel_start_current_span(name: *const u8) {
    let span_name = unsafe { std::ffi::CStr::from_ptr(name).to_str().unwrap_or("unknown") };
    CURRENT_TRACER.with(|t| {
        if let Some(tracer) = t.borrow_mut().as_mut() {
            let span = tracer.start(span_name);
            CURRENT_SPAN.with(|s| {
                *s.borrow_mut() = Some(Box::new(span));
            });
        }
    });
}

#[no_mangle]
pub extern "C" fn otel_end_current_span() {
    CURRENT_SPAN.with(|s| {
        if let Some(mut span) = s.borrow_mut().take() {
            span.end();
        }
    });
}

关键注意事项

  1. 内存安全:无论哪种方案,都必须确保资源被正确释放——指针方案要调用销毁函数,ID映射要移除无用条目,TLS会随线程退出自动清理,但长期运行线程需手动管理。
  2. 线程安全:多线程场景下,全局哈希表必须加锁;TLS方案天然支持多线程隔离。
  3. 上下文传递:若需要跨线程传递Trace/Span,可将OpenTelemetry的Context也包装成不透明指针或ID,在目标线程恢复上下文。

内容的提问来源于stack exchange,提问作者MB14

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 23:33:22