通过OpenTelemetry接入C++ Trace数据

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

前提条件

  • 已创建Trace实例。更多信息,请参见创建Trace实例

  • 已准备相关的开发环境,该环境需要支持编译及运行Opentelemetry C++ SDK。

    • 如果您使用的是CMake编译器,则其版本需为3.1及以上。

    • 如果您使用的是GCC或G++编译器,则其版本需要为4.8及以上。

    • 如果您使用的是MSVC,则其版本需要为vs2015及以上(建议为vs2019)。

  • 支持的C++版本如下所示。

    • ISO/IEC 14882:2011 (C++11, C++0x)

    • ISO/IEC 14882:2014 (C++14, C++1y)

    • ISO/IEC 14882:2017 (C++17, C++1z)

    • ISO/IEC 14882:2020 (C++20)

  • 更多依赖及版本信息,请参见opentelemetry-cpp

步骤一:SDK集成

您可以通过源码或包管理器集成SDK。更多信息,请参见Install opentelemetry-cpp

使用源码集成(作为独立的CMake项目编译)

前提条件

  • 支持在Windows、macOS、Linux平台进行编译。

  • 已安装支持C++11及以上版本的C++编译器。

  • 已安装Git。

  • 已安装CMake。

  • 已安装GoogleTest。

  • 已安装Google Benchmark。

操作步骤

  1. 获取opentelemetry-cpp源代码。

    $ cd <your-path>
    $ git clone --recurse-submodules https://github.com/open-telemetry/opentelemetry-cpp
  2. 创建CMake build配置。

    $ cd opentelemetry-cpp
    $ mkdir build && cd build && cmake ..
  3. 构建CMake targets。

    $ cmake --build . --target all
  4. 安装API的头文件。

    $ cmake --install . --prefix /<install-root>/

使用源码集成(集成到目标CMake项目中)

前置条件

  • 支持在Windows、macOS、Linux平台进行编译。

  • 已安装支持C++11及以上版本的C++编译器。

  • 已安装Git。

  • 已安装CMake。

  • 已安装GoogleTest。

  • 已安装Google Benchmark。

代码示例

# CMakeLists.txt
find_package(opentelemetry-cpp CONFIG REQUIRED)
...
target_include_directories(foo PRIVATE ${OPENTELEMETRY_CPP_INCLUDE_DIRS})
target_link_libraries(foo PRIVATE ${OPENTELEMETRY_CPP_LIBRARIES})

使用包管理器集成

使用包管理器集成的方式请参见using package managers

步骤二:初始化SDK

初始化Opentelemetry C++ SDK后,您才能正常使用SDK。请按照如下操作,初始化SDK。

// 导入以下头文件。
#include "opentelemetry/exporters/otlp/otlp_grpc_exporter_factory.h"
#include "opentelemetry/sdk/trace/simple_processor_factory.h"
#include "opentelemetry/sdk/trace/tracer_provider_factory.h"
#include "opentelemetry/trace/provider.h"

#include "opentelemetry/sdk/trace/tracer_provider.h"

#include "opentelemetry/sdk/version/version.h"
#include "opentelemetry/trace/provider.h"
#include "opentelemetry/sdk/resource/resource.h"
#include "opentelemetry/sdk/resource/semantic_conventions.h"

namespace trace = opentelemetry::trace;
namespace trace_sdk = opentelemetry::sdk::trace;
namespace otlp = opentelemetry::exporter::otlp;
namespace resource = opentelemetry::sdk::resource;

namespace
{
		// 初始化Exporter。Exporter用于导出Trace到日志服务Logstore。
    opentelemetry::exporter::otlp::OtlpGrpcExporterOptions opts;
    void InitTracer()
    {
        opts.endpoint = "https://${endpoint}";
        opts.use_ssl_credentials = true;
        opts.ssl_credentials_cacert_path = "<your root pem file path>"; // 需手动指定root pem文件路径。一般包含在grpc依赖库路径中。

        // Setup credentials info
        opts.metadata.insert(std::pair<std::string, std::string>("x-sls-otel-project", "${project}"));
        opts.metadata.insert(std::pair<std::string, std::string>("x-sls-otel-instance-id", "${instanceId}"));
        opts.metadata.insert(std::pair<std::string, std::string>("x-sls-otel-ak-id", "${access-key-id}"));
        opts.metadata.insert(std::pair<std::string, std::string>("x-sls-otel-ak-secret", "${access-key-secret}"));

        // Create OTLP exporter instance
        auto exporter = otlp::OtlpGrpcExporterFactory::Create(opts);
        auto processor = trace_sdk::SimpleSpanProcessorFactory::Create(std::move(exporter));

        // 初始化tracer provider。tracer provider用于暴露主要的API,对Span进行预处理。
				// 自定义TraceId、SpanId生成规则,自定义采样器等。您可以根据实际需要进行配置。
        resource::ResourceAttributes attributes = {
          {resource::SemanticConventions::kServiceName, "${service}"}, //一般为子模块名称。
          {resource::SemanticConventions::kServiceNamespace, "${service.namespace}"}, //一般为模块或App名称。
          {resource::SemanticConventions::kServiceVersion, "${version}"}, //一般为模块或App版本号。
          {resource::SemanticConventions::kHostName, "${host}"},
          {resource::SemanticConventions::kDeploymentEnvironment, "${environment}"}
        };
        auto resource = opentelemetry::sdk::resource::Resource::Create(attributes);

        std::shared_ptr<opentelemetry::trace::TracerProvider> provider =
            trace_sdk::TracerProviderFactory::Create(std::move(processor), std::move(resource));
        // Set the global trace provider
        trace::Provider::SetTracerProvider(provider);
    }

    void CleanupTracer()
    {
        // We call ForceFlush to prevent to cancel running exportings, It's optional.
        opentelemetry::nostd::shared_ptr<opentelemetry::trace::TracerProvider> provider =
            trace::Provider::GetTracerProvider();
        if (provider)
        {
            static_cast<trace_sdk::TracerProvider*>(provider.get())->ForceFlush();
        }

        std::shared_ptr<opentelemetry::trace::TracerProvider> none;
        trace::Provider::SetTracerProvider(none);
    }
}  // namespace

变量

说明

示例

${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服务实例ID。更多信息,请参见创建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。

${service.namespace}

服务归属的命名空间。

order

${service}

服务名。根据您的实际场景配置。

payment

${version}

服务版本号。建议按照va.b.c格式定义。

v0.1.2

${host}

主机名。

localhost

${environment}

部署环境。例如测试环境、生产环境。根据您的实际场景配置。

pre

步骤三:使用SDK

创建Tracer

建议根据不同的业务场景创建Tracer。创建Tracer时需要传入library name,利于按照library区分不同的Trace数据。

namespace trace = opentelemetry::trace;
namespace nostd = opentelemetry::nostd;

namespace
{
    nostd::shared_ptr<trace::Tracer> get_tracer()
    {
        auto provider = trace::Provider::GetTracerProvider();
        return provider->GetTracer("<your library name>", OPENTELEMETRY_SDK_VERSION);
    }
}  // namespace

创建基本Span

Span代表事务中的操作,每个Span都封装了操作名称、起止时间戳、属性信息、事件信息和Context信息等。

auto span = get_tracer()->StartSpan("basic_f1");
// do your stuff
// ...
span->End();

创建嵌套Span

当您希望为嵌套操作关联Span时,OpenTelemetry支持在进程内和跨远程进程进行跟踪。例如针对methodA调用methodB ,您可以通过以下方式创建嵌套Span。

void method_a() 
{
    auto span = get_tracer()->StartSpan("operation A");
    auto scope = get_tracer()->WithActiveSpan(span);

    method_b();

    span->End();
}

void method_b()
{
    auto span_child = get_tracer()->StartSpan("operation B");
    // do your stuff
    // ...
    span_child->End();
}

OpenTemetry API还提供了一种自动化的方式来传播parentSpan,示例如下:

void method_a() 
{
    auto scoped_span = trace::Scope(get_tracer()->StartSpan("operation A"));
    method_b();
}

void method_b()
{
    auto scoped_span = trace::Scope(get_tracer()->StartSpan("operation B"));
}

创建带属性的Span

您可以通过属性在Span上提供特定操作的上下文信息。例如执行结果、关联的其他业务信息等。

auto span = get_tracer()->StartSpan("<my operation>");
span->SetAttribute("age", 12);
span->SetAttribute("sex", "man");
// do your stuff
// ...
span->SetAttribute("height", 154.5);
span->End();

创建带事件的Span

您可以通过携带多个事件的方式对Span进行注释。

auto span = get_tracer()->StartSpan("<my operation>");
// do your stuff
// ...
span->AddEvent("message: success");
span->End();

给Span添加状态

Span包含trace::StatusCode::kUnsettrace::StatusCode::kOktrace::StatusCode::kError三个状态,分别表示默认状态、成功状态、操作包含错误。

auto span = get_tracer()->StartSpan("<my operation>");
// do your stuff
// ...
span->SetStatus(trace::StatusCode::kError);
span->End();

传播上下文信息

OpenTelemetry提供了一种基于文本的方法,传播上下文信息。

#include "opentelemetry/trace/context.h"
#include "opentelemetry/context/propagation/global_propagator.h"
#include "opentelemetry/context/propagation/text_map_propagator.h"
#include "opentelemetry/trace/propagation/http_trace_context.h"
#include "opentelemetry/nostd/shared_ptr.h"

// 以HttpTraceContext为例,演示context propagation的基本用法。
// 在SDK初始化时,完成global propagator配置。
opentelemetry::context::propagation::GlobalTextMapPropagator::SetGlobalPropagator(
    opentelemetry::nostd::shared_ptr<opentelemetry::context::propagation::TextMapPropagator>(
    new opentelemetry::trace::propagation::HttpTraceContext()));


// 在client侧发起网络请求时,注入Header信息。
opentelemetry::context::propagation::TextMapCarrier carrier; // 实际使用时,需要替换为目标TextMapCarrier。
auto propagator = opentelemetry::context::propagation::GlobalTextMapPropagator::GetGlobalPropagator();

// 注入Header信息。
auto current_ctx = opentelemetry::context::RuntimeContext::GetCurrent();
propagator->Inject(carrier, current_ctx);

// 在server侧,提取Header信息。
auto current_ctx = opentelemetry::context::RuntimeContext::GetCurrent();
auto new_context = propagator->Extract(carrier, current_ctx);
auto remote_span = opentelemetry::trace::GetSpan(new_context);

目前,OpenTelemetry SDK支持按照W3C Trace Context标准传播上下文信息。更多信息,请参见W3CTraceContextPropagator类

更多OpenTelemetry SDK使用信息,请参考官方文档

后续步骤