通过OpenTelemetry接入Qt Trace数据

Qt是一种跨平台的C++开发框架,可用于开发GUI、网络、数据库、OpenGL等应用程序。本文介绍如何在Qt跨平台项目中集成OpenTelemetry C++ SDK以采集Qt 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)

  • 已安装Git版本控制工具。

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

步骤一:配置Visual Studio

为了使OpenTelemetry C++ SDK与Qt项目一起编译,需要将Qt的编译工具切换为MSVC,即需要在Visual Studio中安装Qt Visual Studio Tools插件。

  1. 在Visual Studio顶部菜单栏中,选择扩展>管理扩展,然后搜索Qt Visual Studio Tools

  2. 单击Qt Visual Studio Tools对应的下载image

  3. 待插件下载完成后,关闭Visual Studio的所有窗口。

  4. 在自动弹出的VSIX Installer对话框中,单击Modify,插件会自动完成安装。image

  5. 确认插件安装完成。

    重新打开Visual Studio项目,如果扩展菜单下有Qt VS Tools选项,则表示Qt Visual Studio Tools插件安装成功。image

关于Qt Visual Studio Tools的更多信息,请参见Qt Visual Studio Tools

步骤二:集成SDK

opentelemetry-cpp项目的构建编译依赖CMake工具,即需要将Qt的编译构建工具切换为CMake。本文以opentelemetry-cpp 1.9.0版本为例。

(推荐)通过源码集成

  1. 将opentelemetry-cpp源码克隆到Qt项目所在的文件夹中。

    $ cd <your qt project directory>
    $ git clone --recurse-submodules https://github.com/open-telemetry/opentelemetry-cpp
  2. 打开Qt项目的CMakeLists.txt文件,添加如下内容。

    # CMakeLists.txt
    add_subdirectory(opentelemetry-cpp)
    ...
    target_include_directories(OTelExample4QT2 PRIVATE ${CMAKE_SOURCE_DIR}/opentelemetry-cpp/api/include
                               PRIVATE ${CMAKE_SOURCE_DIR}/opentelemetry-cpp/sdk/include
                               PRIVATE ${CMAKE_SOURCE_DIR}/opentelemetry-cpp/exporters/ostream/include
                               PRIVATE ${CMAKE_SOURCE_DIR}/opentelemetry-cpp/exporters/otlp/include
                               PRIVATE ${CMAKE_SOURCE_DIR}/opentelemetry-cpp/ext/include)
    target_link_libraries(OTelExample4QT2 PRIVATE Qt${QT_VERSION_MAJOR}::Widgets opentelemetry_trace  opentelemetry_common
                          opentelemetry_http_client_curl opentelemetry_exporter_ostream_span opentelemetry_exporter_in_memory
                          opentelemetry_exporter_ostream_span opentelemetry_exporter_otlp_grpc opentelemetry_exporter_otlp_grpc_client
                          opentelemetry_exporter_otlp_grpc_log opentelemetry_exporter_otlp_grpc_metrics opentelemetry_exporter_otlp_http
                          opentelemetry_exporter_otlp_http_client opentelemetry_exporter_otlp_http_metric opentelemetry_metrics
                          opentelemetry_http_client_curl opentelemetry_otlp_recordable opentelemetry_proto opentelemetry_resources
                          opentelemetry_trace opentelemetry_version)
  3. 在目标项目的编译构建配置页面,单击Current Configuration,确认编译构建opentelemetry-cpp项目成功。image

    • 如果Configuration中出现opentelemetry-cpp相关的配置项(例如WITH_ABSEIL等),表示编译构建成功。image

    • 如果Configuration中没有出现opentelemetry-cpp相关的配置项,请在页面右侧选择添加 > Boolean,添加BUILD_TESTING=OFF,然后单击执行CMake

      完成该操作后,如果仍然没有出现opentelemetry-cpp相关的配置项,建议您根据项目错误提示进行排查。image

      建议opentelemetry-cpp相关的CMake配置只做如下改动。其中c-ares_DIR、re2_DIR、gRPC_DIR、absl_DIR、nlohmann_json_DIR等,建议使用默认值。

      重要

      如果您的Qt项目已集成对应的库,请按照您的项目实际情况进行配置。

      WITH_EXAMPLES:BOOL=OFF
      WITH_OTLP:BOOL=ON
      WITH_OTLP_GRPC:BOOL=ON
      WITH_OTLP_HTTP:BOOL=ON
      WITH_STL:BOOL=ON
      c-ares_DIR:PATH=<your c-ares path>
      re2_DIR:PATH=<your re2 path>
      gRPC_DIR:PATH=<your gRPC path>
      absl_DIR:PATH=<your absl path> 
      WITH_ABSEIL:BOOL=ON
      nlohmann_json_DIR:PATH=<your nlohmann_json path>
      CURL_DIR:PATH=<your curl path>
      OPENSSL_ROOT_DIR:PATH=<your openssl path>
      OPENSSL_USE_STATIC_LIBS:BOOL=ON
      Protobuf_DIR:PATH=<your proto path>
      Protobuf_PROTOC_EXECUTABLE:FILEPATH=<your protoc exe filepath>
      PROTO_INCLUDE_DIR:FILEPATH=<your proto filepath>
      CMAKE_CXX_FLAGS=D_HAS_EXCEPTIONS=0
      gRPC_CPP_PLUGIN_EXECUTABLE:FILEPATH=<your grpc plugin exe filepath>
      gRPC_ZLIB_PROVIDER=package
      ZLIB_ROOT=<your zlib install path>
      ZLIB_USE_STATIC_LIBS=True
      ZLIB_LIBRARY_RELEASE=<your zlib release lib path>
      ZLIB_LIBRARY_DEBUG=<your zlib debug lib path>

至此,Qt项目的构建配置已完成。如果无法编译成功,请按照相关错误提示进行排查。

通过vcpkg包管理器集成

重要

从vcpkg包获取到的SDK二进制文件,可能与您的项目存在兼容性问题,建议您充分测试后再使用。更多信息,请参见using-package-managers

  1. 从vcpkg包管理器中获取二进制文件。

    1. 在您的工作目录中,打开一个command prompt或者terminal。

    2. 使用Git克隆vcpkg包。

      $ git clone https://github.com/microsoft/vcpkg
    3. 执行bootstrap-vcpkg.bat脚本。

      $ .\vcpkg\bootstrap-vcpkg.bat

      image

    4. 进入到vcpkg目录,执行以下脚本。

      $ .\vcpkg.exe install opentelemetry-cpp[otlp-http]:x64-windows --recurse

      执行以上命令成功后,OpenTelemetry C++ SDK以及对应的依赖SDK将被安装到 vcpkg/installed/x64-winddows目录下。目录说明,如下所示。

      • bin/目录:包含动态链接库(.dll 文件)。

      • lib/目录:包含静态链接库文件(.lib 文件)。

      • include/目录:包含头文件(.h 文件)。

    5. (可选)为了便于后续在Qt项目中使用依赖库,可以通过以下命令安装vcpkg cmake配置文件到系统变量中。

      $ .\vcpkg.exe integrate --install
  2. 配置Qt项目。

    1. 确认Qt项目的编译工具已切换为MSVC。

      具体操作,请参见步骤一:配置Visual Studio

    2. 配置CMake options。

      1. 在目标项目的编译构建配置页面,单击Initial Configuration

      2. 新增CMAKE_TOOLCHAIN_FILE

        CMAKE_TOOLCHAIN_FILE的值需设置为vcpkg目录下的vcpkg.cmake文件。该文件目录为<vcpkg root>/scripts/buildsystems/vcpkg.cmakeimage

    3. 编译项目,确认是否有成功。

      如果有报错,请先排查问题。例如确认编译工具是否已切换为MSVC、是否已正确配置CMAKE_TOOLCHAIN_FILE。

  3. 配置CMakeLists.txt。

    在CMakeLists.txt文件中加入以下配置。

    # opentelemetry-cpp
    find_package(curl CONFIG REQUIRED)
    find_package(nlohmann_json CONFIG REQUIRED)
    find_package(grpc CONFIG REQUIRED)
    find_package(protobuf CONFIG REQUIRED)
    find_package(opentelemetry-cpp CONFIG REQUIRED)
    # end
    
    # opentelemetry-cpp
    target_include_directories(
        ${PROJECT_NAME}
        PUBLIC
        ${PROJECT_SOURCE_DIR}
        ${VCPKG_INCLUDE_DIR}
        ${OPENTELEMETRY_CPP_INCLUDE_DIRS}
    )
    target_link_directories(
        ${PROJECT_NAME}
        PUBLIC
        ${VCPKG_LIB_DIR}
        ${OPENTELEMETRY_CPP_LIBRARY_DIRS}
    )
    # ${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Widgets部分需要根据项目的实际情况进行配置
    target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Widgets ${OPENTELEMETRY_CPP_LIBRARIES})
    # en

    至此,Qt项目的构建配置已完成。如果无法编译成功,请按照相关错误提示进行排查。

步骤三:接入验证

  1. 在Qt项目中加入以下代码,然后运行项目。

    #include "mainwindow.h"
    
    #include <QApplication>
    
    #include "opentelemetry/exporters/ostream/span_exporter_factory.h"
    #include "opentelemetry/exporters/otlp/otlp_http.h"
    #include "opentelemetry/exporters/otlp/otlp_http_exporter_factory.h"
    #include "opentelemetry/exporters/otlp/otlp_http_exporter_options.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"
    
    #include "opentelemetry/sdk/common/global_log_handler.h"
    
    
    
    namespace trace = opentelemetry::trace;
    namespace trace_sdk = opentelemetry::sdk::trace;
    namespace otlp = opentelemetry::exporter::otlp;
    namespace trace_exporter = opentelemetry::exporter::trace;
    namespace resource = opentelemetry::sdk::resource;
    
    namespace
    {
        opentelemetry::exporter::otlp::OtlpHttpExporterOptions opts;
        void InitTracer()
        {
            opts.url = "https://<your project>.<your endpoint>/opentelemetry/v1/traces";
            opts.console_debug = true;
            opts.content_type = otlp::HttpRequestContentType::kBinary;
            // Setup credentials info
            opts.http_headers.insert(std::pair<std::string, std::string>("x-sls-otel-project", "<your project>"));
            opts.http_headers.insert(std::pair<std::string, std::string>("x-sls-otel-instance-id", "<your instanceId>"));
            opts.http_headers.insert(std::pair<std::string, std::string>("x-sls-otel-ak-id", "<your accesskey id>"));
            opts.http_headers.insert(std::pair<std::string, std::string>("x-sls-otel-ak-secret", "<your accesskey secret>"));
    
            // Create OTLP exporter instance
            auto exporter = otlp::OtlpHttpExporterFactory::Create(opts);
            auto processor = trace_sdk::SimpleSpanProcessorFactory::Create(std::move(exporter));
    
            resource::ResourceAttributes attributes = {
              {resource::SemanticConventions::kServiceName, "test"},
              {resource::SemanticConventions::kServiceNamespace, "OTelExample4QT"},
              {resource::SemanticConventions::kServiceVersion, "1.0.0"},
              {resource::SemanticConventions::kHostName, "Win64"},
              {resource::SemanticConventions::kDeploymentEnvironment, "dev"}
            };
            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
    
    namespace trace = opentelemetry::trace;
    namespace nostd = opentelemetry::nostd;
    
    namespace
    {
        nostd::shared_ptr<trace::Tracer> get_tracer()
        {
            auto provider = trace::Provider::GetTracerProvider();
            return provider->GetTracer("foo_library", OPENTELEMETRY_SDK_VERSION);
        }
    
        void basic_f1()
        {
            auto span = get_tracer()->StartSpan("basic_f1");
            // do your stuff
            // ...
            span->End();
        }
    
        void basic_f1_with_attributes()
        {
            auto span = get_tracer()->StartSpan("basic_f1_with_attributes");
            span->SetAttribute("ags", 12);
            span->SetAttribute("sex", "man");
            span->SetAttribute("height", 154.5);
    
    
            span->AddEvent("message: success");
    
            span->SetStatus(trace::StatusCode::kError);
    
            span->End();
        }
    
        void basic_active_f1()
        {
            auto span_child = get_tracer()->StartSpan("operation B");
            // do your stuff
            // ...
            span_child->End();
        }
    
        void basic_active()
        {
            auto span = get_tracer()->StartSpan("operation A");
            auto scope = get_tracer()->WithActiveSpan(span);
    
            basic_active_f1();
    
            span->End();
        }
    
    
        void f1()
        {
            auto scoped_span = trace::Scope(get_tracer()->StartSpan("f1"));
        }
    
        void f2()
        {
            auto scoped_span = trace::Scope(get_tracer()->StartSpan("f2"));
    
            f1();
            f1();
        }
    }  // namespace
    
    void foo_library()
    {
    
        basic_f1();
        basic_active();
        basic_f1_with_attributes();
    
        auto scoped_span = trace::Scope(get_tracer()->StartSpan("library"));
    
        f2();
    
    }
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        MainWindow w;
        w.show();
    
        InitTracer();
    
        foo_library();
    
        CleanupTracer();
    
        return a.exec();
    }
    
  2. 日志服务控制台的Trace服务应用中,查看Trace数据。

    添加过滤条件service : "test",查看已接入的Qt Trace数据。image