本文介绍通过OpenTelemetry Rust SDK将Rust应用的Trace数据接入到日志服务的操作步骤。

前提条件

  • 已创建Trace实例。更多信息,请参见创建Trace实例
  • 已安装Rust 1.46及以上版本的开发环境。

操作步骤

  1. 添加依赖项。
    [package]
    name = "test"
    version = "0.1.0"
    authors = [""]
    edition = "2018"
    
    # See more keys and their definitions at The Manifest Format.
    
    [dependencies]
    futures = "0.3"
    lazy_static = "1.4"
    opentelemetry = { version = "0.16.0", features = ["tokio-support", "metrics", "serialize"] }
    opentelemetry-otlp = { version = "0.9.0", features = ["tonic", "metrics", "tls", "tls-roots"] }
    serde_json = "1.0"
    tokio = { version = "1.0", features = ["full"] }
    tonic="0.4.0"
    url = "2.2.0"
  2. 运行代码。

    如下代码中的变量需根据实际情况替换。关于变量的详细说明,请参见表 1

    use opentelemetry::global::shutdown_tracer_provider;
    use opentelemetry::sdk::Resource;
    use opentelemetry::trace::TraceError;
    use opentelemetry::{
        baggage::BaggageExt,
        trace::{TraceContextExt, Tracer},
        Context, Key, KeyValue,
    };
    use opentelemetry::{global, sdk::trace as sdktrace};
    use opentelemetry_otlp::WithExportConfig;
    use std::error::Error;
    use std::time::Duration;
    use tonic::metadata::MetadataMap;
    use tonic::transport::ClientTlsConfig;
    use url::Url;
    static ENDPOINT: &str = "https://${endpoint}";
    static PROJECT: &str = "${project}";
    static INSTANCE_ID: &str = "${instance}";
    static AK_ID: &str = "${access-key-id}";
    static AK_SECRET: &str = "${access-key-secret}";
    static SERVICE_VERSION: &str = "${version}";
    static SERVICE_NAME: &str = "${service}";
    static SERVICE_NAMESPACE: &str = "${service.namespace}";
    static HOST_NAME: &str = "${host}";
    
    static SLS_PROJECT_HEADER: &str = "x-sls-otel-project";
    static SLS_INSTANCE_ID_HEADER: &str = "x-sls-otel-instance-id";
    static SLS_AK_ID_HEADER: &str = "x-sls-otel-ak-id";
    static SLS_AK_SECRET_HEADER: &str = "x-sls-otel-ak-secret";
    static SLS_SERVICE_VERSION: &str = "service.version";
    static SLS_SERVICE_NAME: &str = "service.name";
    static SLS_SERVICE_NAMESPACE: &str = "service.namespace";
    static SLS_HOST_NAME: &str = "host.name";
    
    fn init_tracer() -> Result<sdktrace::Tracer, TraceError> {
        let mut metadata_map = MetadataMap::with_capacity(4);
        metadata_map.insert(SLS_PROJECT_HEADER, PROJECT.parse().unwrap());
        metadata_map.insert(SLS_INSTANCE_ID_HEADER, INSTANCE_ID.parse().unwrap());
        metadata_map.insert(SLS_AK_ID_HEADER, AK_ID.parse().unwrap());
        metadata_map.insert(SLS_AK_SECRET_HEADER, AK_SECRET.parse().unwrap());
    
        let endpoint = ENDPOINT;
        let endpoint = Url::parse(&endpoint).expect("endpoint is not a valid url");
        let resource = vec![
            KeyValue::new(SLS_SERVICE_VERSION, SERVICE_VERSION),
            KeyValue::new(SLS_HOST_NAME, HOST_NAME),
            KeyValue::new(SLS_SERVICE_NAMESPACE, SERVICE_NAMESPACE),
            KeyValue::new(SLS_SERVICE_NAME, SERVICE_NAME),
        ];
    
        opentelemetry_otlp::new_pipeline()
            .tracing()
            .with_exporter(
                opentelemetry_otlp::new_exporter()
                    .tonic()
                    .with_endpoint(endpoint.as_str())
                    .with_metadata(dbg!(metadata_map))
                    .with_tls_config(
                        ClientTlsConfig::new().domain_name(
                            endpoint
                                .host_str()
                                .expect("the specified endpoint should have a valid host"),
                        ),
                    ),
            )
            .with_trace_config(sdktrace::config().with_resource(Resource::new(resource)))
            .install_batch(opentelemetry::runtime::Tokio)
    }
    
    const FOO_KEY: Key = Key::from_static_str("ex.com/foo");
    const BAR_KEY: Key = Key::from_static_str("ex.com/bar");
    const LEMONS_KEY: Key = Key::from_static_str("lemons");
    const ANOTHER_KEY: Key = Key::from_static_str("ex.com/another");
    
    lazy_static::lazy_static! {
        static ref COMMON_ATTRIBUTES: [KeyValue; 4] = [
            LEMONS_KEY.i64(10),
            KeyValue::new("A", "1"),
            KeyValue::new("B", "2"),
            KeyValue::new("C", "3"),
        ];
    }
    
    #[tokio::main]
    async fn main() -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
        let _ = init_tracer()?;
        let tracer = global::tracer("ex.com/basic");
        let _baggage =
            Context::current_with_baggage(vec![FOO_KEY.string("foo1"), BAR_KEY.string("bar1")])
                .attach();
    
        tracer.in_span("operation", |cx| {
            let span = cx.span();
            span.add_event(
                "Nice operation!".to_string(),
                vec![Key::new("bogons").i64(100)],
            );
            span.set_attribute(ANOTHER_KEY.string("yes"));
    
            tracer.in_span("Sub operation...", |cx| {
                let span = cx.span();
                span.set_attribute(LEMONS_KEY.string("five"));
    
                span.add_event("Sub span event".to_string(), vec![]);
            });
        });
    
        tokio::time::sleep(Duration::from_secs(60)).await;
        shutdown_tracer_provider();
        Ok(())
    }
    表 1. 变量说明
    变量 说明 示例
    ${service} 服务名。根据您的实际场景取值即可。 payment
    ${version} 服务版本号。建议按照va.b.c格式定义。 v0.1.2
    ${service.namespace} 服务归属的命名空间。 order
    ${host} 主机名。 localhost
    ${endpoint} 日志服务Project的接入地址,格式为${project}.${region-endpoint}:Port,其中:
    • ${project}:日志服务Project名称。
    • ${region-endpoint}:日志服务Project所在地域的访问域名,支持公网和阿里云内网(经典网络、VPC)。更多信息,请参见服务入口
    • Port:网络端口,固定为10010。
    test-project.cn-hangzhou.log.aliyuncs.com:10010
    ${project} 日志服务Project名称。 test-project
    ${instance} Trace服务实例名称。 test-traces
    ${access-key-id} 阿里云账号AccessKey ID。

    建议您使用只具备日志服务Project写入权限的RAM用户的AccessKey(包括AccessKey ID和AccessKey Secret)。授予RAM用户向指定Project写入数据权限的具体操作,请参见授权。如何获取AccessKey的具体操作,请参见访问密钥

    ${access-key-secret} 阿里云账号AccessKey Secret。

    建议您使用只具备日志服务Project写入权限的RAM用户的AccessKey。

后续步骤