OpenTelemetry C++ SDK¶
This document uses SDK instrumentation: initialize the SDK in the application code, create spans, and configure the exporter to send trace data to Guance through DataKit. This is not zero-code injection; installing dependencies or setting environment variables will not automatically collect all framework calls. This document covers Trace only and does not involve Kubernetes.
Prerequisites¶
- The following build commands apply to Debian/Ubuntu and require permissions to install development dependencies.
- Use a compiler with C++17 support, CMake 3.16 or higher, Git, libcurl, Protobuf development libraries with
protoc, and nlohmann-json. - The example pins OpenTelemetry C++
v1.23.0. When upgrading, verify the compatibility of the SDK, compiler, dependency libraries, and ABI together. - DataKit is installed and configured with the upload address and token using the installation command of the target workspace. The application must be able to access DataKit HTTP port
9529.
1. Enable the OpenTelemetry Collector¶
On the DataKit host, enter the configuration directory. Only copy the sample when the configuration does not already exist; adjust existing files directly:
Make sure opentelemetry.conf contains the following configuration. Custom tags are preserved through customer_tags:
[[inputs.opentelemetry]]
customer_tags = ["team", "app.operation"]
[inputs.opentelemetry.http]
http_status_ok = 200
trace_api = "/otel/v1/traces"
metric_api = "/otel/v1/metrics"
logs_api = "/otel/v1/logs"
Local access uses 127.0.0.1:9529. For cross-host access, set a listen address that the application can reach in [http_api].listen in the DataKit main configuration datakit.conf, and restrict the network access scope. The HTTP listen address is not set in the collector file.
Restart and check DataKit:
/v1/ping only verifies that the HTTP service is reachable; it does not mean trace data has been ingested. For full details, see OpenTelemetry Collector. DataKit handles workspace authentication; the sample application does not configure the workspace token directly.
2. Instrument the Application with OpenTelemetry¶
Install Dependencies¶
Install dependencies on the development machine and fetch the official source code. The following directory names should not exist yet:
sudo apt-get update
sudo apt-get install -y build-essential cmake git libcurl4-openssl-dev \
libprotobuf-dev protobuf-compiler nlohmann-json3-dev
mkdir otel-cpp-demo
cd otel-cpp-demo
git clone --branch v1.23.0 --depth 1 --recurse-submodules --shallow-submodules \
https://github.com/open-telemetry/opentelemetry-cpp.git
Create CMakeLists.txt in the otel-cpp-demo root directory. WITH_OTLP_HTTP enables the HTTP exporter; disabling tests and examples reduces build time:
cmake_minimum_required(VERSION 3.16)
project(otel_cpp_demo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(BUILD_TESTING OFF CACHE BOOL "" FORCE)
set(WITH_BENCHMARK OFF CACHE BOOL "" FORCE)
set(WITH_EXAMPLES OFF CACHE BOOL "" FORCE)
set(WITH_OTLP_GRPC OFF CACHE BOOL "" FORCE)
set(WITH_OTLP_HTTP ON CACHE BOOL "" FORCE)
add_subdirectory(opentelemetry-cpp)
add_executable(otel-cpp-demo main.cpp)
target_link_libraries(otel-cpp-demo PRIVATE
opentelemetry_trace
opentelemetry_exporter_otlp_http
)
Initialize the SDK and Create a Span¶
Create main.cpp in the same directory. Read environment variables through the Resource detector, register the provider, and explicitly end spans, flush, and shut down the provider:
#include <chrono>
#include <memory>
#include <utility>
#include "opentelemetry/exporters/otlp/otlp_http_exporter_factory.h"
#include "opentelemetry/exporters/otlp/otlp_http_exporter_options.h"
#include "opentelemetry/sdk/resource/resource.h"
#include "opentelemetry/sdk/trace/batch_span_processor_factory.h"
#include "opentelemetry/sdk/trace/batch_span_processor_options.h"
#include "opentelemetry/sdk/trace/provider.h"
#include "opentelemetry/sdk/trace/samplers/parent.h"
#include "opentelemetry/sdk/trace/samplers/trace_id_ratio.h"
#include "opentelemetry/sdk/trace/tracer_provider.h"
namespace otlp = opentelemetry::exporter::otlp;
namespace sdktrace = opentelemetry::sdk::trace;
int main()
{
otlp::OtlpHttpExporterOptions options;
options.content_type = otlp::HttpRequestContentType::kBinary;
auto exporter = otlp::OtlpHttpExporterFactory::Create(options);
sdktrace::BatchSpanProcessorOptions batch_options;
auto processor = sdktrace::BatchSpanProcessorFactory::Create(
std::move(exporter), batch_options);
auto resource = opentelemetry::sdk::resource::Resource::Create({});
auto sampler = std::make_unique<sdktrace::ParentBasedSampler>(
std::make_shared<sdktrace::TraceIdRatioBasedSampler>(1.0));
auto provider = std::make_shared<sdktrace::TracerProvider>(
std::move(processor), resource, std::move(sampler));
std::shared_ptr<opentelemetry::trace::TracerProvider> api_provider = provider;
sdktrace::Provider::SetTracerProvider(api_provider);
auto tracer = provider->GetTracer("otel-cpp-demo");
auto parent = tracer->StartSpan("checkout");
{
auto scope = tracer->WithActiveSpan(parent);
auto child = tracer->StartSpan("db.lookup");
child->SetAttribute("app.operation", "lookup");
child->End();
}
parent->End();
const bool flushed = provider->ForceFlush(std::chrono::seconds(10));
const bool stopped = provider->Shutdown(std::chrono::seconds(10));
return flushed && stopped ? 0 : 1;
}
Build and Run¶
Build in the project root directory that contains CMakeLists.txt:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --target otel-cpp-demo --parallel 2
In the same terminal where you start the application, configure the following parameters. For cross-host access, replace 127.0.0.1 with the actual DataKit address:
export OTEL_SERVICE_NAME="order-service"
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment.name=prod,service.version=1.0.0,team=backend"
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="http://127.0.0.1:9529/otel/v1/traces"
./build/otel-cpp-demo
For long-running services, initialize the provider only once. On shutdown, stop request processing and end spans first, then call ForceFlush() and Shutdown(). Do not shut down the provider after every request.
3. Data Reporting Parameters¶
| Parameter or configuration | Description |
|---|---|
OTEL_SERVICE_NAME |
service.name; the example uses order-service. Set it to a stable service name. |
OTEL_RESOURCE_ATTRIBUTES |
Comma-separated resource attributes. The example sets the environment, version, and team. Custom fields should be added to DataKit customer_tags. |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT |
Full trace-specific endpoint: http://127.0.0.1:9529/otel/v1/traces. Takes precedence over the generic base endpoint. |
OTEL_EXPORTER_OTLP_ENDPOINT |
Optional base endpoint: http://127.0.0.1:9529/otel. When the trace-specific endpoint is not set, the exporter appends /v1/traces. |
OTEL_EXPORTER_OTLP_HEADERS |
Optional OTLP request headers in the key=value,key2=value2 format. Configure only when the receiver or proxy requires authentication. |
This example builds and creates an OtlpHttpExporter with content_type = kBinary. Changing OTEL_EXPORTER_OTLP_PROTOCOL will not turn it into a gRPC exporter. gRPC requires enabling a separate build option, linking the corresponding exporter, and modifying the initialization code.
Sampling is configured in code as ParentBased with a root trace ratio of 1.0, following the sampling decision of the parent span. In production, change the ratio in the example to 0.1 to sample approximately 10% of root traces. This example explicitly sets the sampler and does not rely on OTEL_TRACES_SAMPLER or OTEL_TRACES_SAMPLER_ARG.
The example explicitly creates the trace export pipeline; OTEL_TRACES_EXPORTER does not select or close it. No Metric or Log providers are created, so setting OTEL_METRICS_EXPORTER or OTEL_LOGS_EXPORTER will not enable the corresponding signals. For logs, you can use DataKit log file collection separately.
Context Propagation and Business Integration¶
The scope created by WithActiveSpan only affects the current context and cannot automatically carry the parent to other threads or services. Thread switching requires explicitly passing and restoring the Context. For HTTP/RPC, combine HttpTraceContext with TextMapCarrier to extract and inject traceparent and tracestate, and set the remote parent for server-side entry points. Real business logic also needs to set span status on error paths and ensure all spans are ended.
Verification and Troubleshooting¶
- After running the example, search for traces by
order-servicein Guance APM and confirm thatcheckoutand its child spandb.lookupare present. - If there is no data, check whether the collector is enabled, whether the application environment variables take effect, whether the HTTP path includes
/otel/v1/traces, and check export errors in DataKit and the application. - Make sure spans are ended and the provider is flushed before exit. Forcefully exiting or using a root trace ratio of
0will prevent the expected data from being visible; network connectivity does not equal successful export.