For the complete documentation index, see llms.txt. Markdown versions are available by appending .md to documentation URLs.

C++ OpenTelemetry Instrumentation Guide

SigNoz Cloud - This page applies to SigNoz Cloud editions.
Self-Host - This page applies to self-hosted SigNoz editions.

This guide shows you how to instrument your C++ application with OpenTelemetry and send traces to SigNoz. You build the opentelemetry-cpp SDK into your project with CMake or Bazel, then initialize a tracer that exports over OTLP/HTTP.

Prerequisites

  • A C++ compiler with C++14 or later support
  • CMake 3.16 or later, or Bazel, to build your project
  • An instance of SigNoz (either Cloud or Self-Hosted)
  • Your application code

Send traces to SigNoz

Step 1. Set environment variables

The OTLP exporter reads its destination from the environment, so your source code holds no endpoint. Set these variables before you run your application:

export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
export OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
export OTEL_SERVICE_NAME="<service-name>"
export OTEL_RESOURCE_ATTRIBUTES="service.version=<service-version>"

Verify these values:

  • <region>: Your SigNoz Cloud region
  • <your-ingestion-key>: Your SigNoz ingestion key
  • <service-name>: A descriptive name for your service (for example, payment-service)
  • <service-version> (optional): Your release version, image tag, or git SHA (for example, 1.4.2 or a01dbef8)

Step 2. Add the opentelemetry-cpp dependency

opentelemetry-cpp ships the OTLP HTTP exporter as an opt-in build target. Enable it when you build the SDK.

The OTLP HTTP exporter links against Protobuf and libcurl. Install both from your package manager before you build the SDK. On Debian or Ubuntu:

sudo apt-get update
sudo apt-get install -y libprotobuf-dev protobuf-compiler libabsl-dev libcurl4-openssl-dev

Build and install the SDK with the OTLP HTTP exporter enabled:

git clone --depth 1 --branch v1.28.0 https://github.com/open-telemetry/opentelemetry-cpp.git
cd opentelemetry-cpp
cmake -B build -DCMAKE_BUILD_TYPE=Release -DWITH_OTLP_HTTP=ON -DBUILD_TESTING=OFF -DWITH_BENCHMARK=OFF
cmake --build build --target all --config Release
cmake --install build --config Release --prefix <install-root>

Set <install-root> to the directory that receives the headers and libraries, such as /usr/local.

Then link the SDK into your own target:

CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(my-cpp-app CXX)
 
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
find_package(opentelemetry-cpp CONFIG REQUIRED COMPONENTS api sdk exporters_otlp_http)
 
add_executable(my-cpp-app main.cc)
target_link_libraries(my-cpp-app
  PRIVATE
    opentelemetry-cpp::api
    opentelemetry-cpp::trace
    opentelemetry-cpp::otlp_http_exporter
)

If you installed to a non-standard prefix, point CMake at it when you configure your project:

cmake -B build -DCMAKE_PREFIX_PATH=<install-root>

Step 3. Initialize the tracer

Create main.cc. InitTracer connects the OTLP HTTP exporter to a batch span processor, then registers the result as the global tracer provider. No SigNoz values appear in the code. The exporter and the resource read them from the environment variables you set in Step 1:

main.cc
#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/tracer_provider.h"
#include "opentelemetry/sdk/trace/tracer_provider_factory.h"
#include "opentelemetry/trace/provider.h"
#include "opentelemetry/trace/scope.h"
#include "opentelemetry/trace/tracer_provider.h"
 
namespace trace        = opentelemetry::trace;
namespace trace_sdk    = opentelemetry::sdk::trace;
namespace otlp         = opentelemetry::exporter::otlp;
namespace resource_sdk = opentelemetry::sdk::resource;
 
namespace
{
std::shared_ptr<trace_sdk::TracerProvider> provider;
 
void InitTracer()
{
  // Reads OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS.
  // With OTEL_EXPORTER_OTLP_ENDPOINT set, the exporter appends /v1/traces.
  otlp::OtlpHttpExporterOptions exporter_options;
  auto exporter = otlp::OtlpHttpExporterFactory::Create(exporter_options);
 
  // Batch spans instead of sending one HTTP request per span.
  trace_sdk::BatchSpanProcessorOptions processor_options;
  auto processor =
      trace_sdk::BatchSpanProcessorFactory::Create(std::move(exporter), processor_options);
 
  // Reads OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES.
  auto resource = resource_sdk::Resource::Create({});
 
  provider = trace_sdk::TracerProviderFactory::Create(std::move(processor), resource);
  std::shared_ptr<trace::TracerProvider> api_provider = provider;
  trace_sdk::Provider::SetTracerProvider(api_provider);
}
 
void CleanupTracer()
{
  // Flush spans still queued in the batch processor before the process exits.
  if (provider)
  {
    provider->ForceFlush();
  }
 
  provider.reset();
  std::shared_ptr<trace::TracerProvider> none;
  trace_sdk::Provider::SetTracerProvider(none);
}
}  // namespace
 
int main()
{
  InitTracer();
 
  auto tracer = trace::Provider::GetTracerProvider()->GetTracer("my-cpp-app");
  {
    // Scope ends the span when the block exits.
    auto scope = trace::Scope(tracer->StartSpan("startup"));
 
    // Call your application code here. Any span started while this scope is
    // active becomes a child of "startup".
  }
 
  CleanupTracer();
  return 0;
}

Call InitTracer once at startup, and call CleanupTracer before main returns. If you skip the cleanup, the SDK discards the spans still in the batch queue.

Step 4. Run your application

Run it where you set the variables in Step 1.

cmake -B build
cmake --build build
./build/my-cpp-app

Validate

Run your instrumented application, then verify that traces reach SigNoz:

  1. Run your application so it executes the code path that starts a span.
  2. Open SigNoz and navigate to Traces.
  3. Click Refresh and filter by the service.name you set in OTEL_SERVICE_NAME.
  4. Click any trace to see its spans, timing, and attributes.
SigNoz Traces Explorer listing spans from an instrumented C++ service
Spans from an instrumented C++ application, filtered by service name

Spans leave in batches, so wait a few seconds after the run before you look for them.

Troubleshooting

Why don't traces appear in SigNoz?

Verify that the environment variables reached the process:

echo $OTEL_EXPORTER_OTLP_ENDPOINT
echo $OTEL_SERVICE_NAME

When OTEL_EXPORTER_OTLP_ENDPOINT is empty, the exporter uses its default of http://localhost:4318/v1/traces. Spans then go to a local Collector that can be absent.

Then test the connection to the endpoint:

curl -v https://ingest.<region>.signoz.cloud:443/v1/traces

For a full walkthrough covering SDK diagnostics, Collector connectivity, and common ingestion errors (also for logs and metrics), see Debug missing traces, logs, and metrics in SigNoz.

Why does find_package fail on utf8_range::utf8_validity?

The SDK was built against a Protobuf that CMake fetched and built itself, so the installed CMake config points at targets that were never installed. Install Protobuf from your package manager, then rebuild and reinstall the SDK.

Why does the CMake build fail on benchmark::benchmark?

WITH_BENCHMARK is on by default and does not follow BUILD_TESTING. With only -DBUILD_TESTING=OFF, CMake still creates the benchmark targets but never imports Google Benchmark. Pass -DWITH_BENCHMARK=OFF as well, as shown in Step 2.

Why does the build fail on the OTLP exporter headers?

opentelemetry-cpp does not build the OTLP HTTP exporter by default. For CMake, rebuild the SDK with -DWITH_OTLP_HTTP=ON. For Bazel, verify that your target depends on @io_opentelemetry_cpp//exporters/otlp:otlp_http_exporter.

Why does Bazel report that cc_binary was removed?

Bazel 9 removed the built-in C++ rules. Add rules_cc to MODULE.bazel, then load cc_binary at the top of your BUILD file, as shown in Step 2.

Why does Bazel report that //api does not exist?

A label that begins with // points at your own workspace. Prefix every SDK label with the repository name, as in @io_opentelemetry_cpp//api.

Why do spans go missing when the process exits?

The batch span processor queues spans and exports them on a timer. Call provider->ForceFlush() through CleanupTracer before main returns, as shown in Step 3.

Why does the service show as unknown_service?

The process started without OTEL_SERVICE_NAME. Resource::Create then uses unknown_service as the service name, or unknown_service:<binary> when it can read the executable name.

Setup OpenTelemetry Collector (Optional)

What is the OpenTelemetry Collector?

The Collector sits between your application and SigNoz. Your application sends spans to the Collector, and the Collector forwards them to SigNoz.

Why use it?

  • Cleaning up data: Filter out noisy spans, or remove sensitive attributes before they leave your servers.
  • Keeping your app lightweight: Move batching, retries, and compression out of your process.
  • Adding context: Tag spans with host, Kubernetes, or cloud metadata that the application does not know.
  • Future flexibility: Fan out to more than one backend without a rebuild of your binary.

To send this setup through a Collector, point OTEL_EXPORTER_OTLP_ENDPOINT at the Collector's OTLP/HTTP address. The default address is http://localhost:4318. Remove OTEL_EXPORTER_OTLP_HEADERS, because the Collector holds the ingestion key.

See Switch from direct export to Collector for step-by-step instructions to convert your setup.

For more details, see Why use the OpenTelemetry Collector? and the Collector configuration guide.

Next steps

Get Help

If you need help with the steps in this topic, please reach out to us on SigNoz Community Slack. If you are a SigNoz Cloud user, please use in product chat support located at the bottom right corner of your SigNoz instance or contact us at cloud-support@signoz.io.

Is this page helpful

Last updatedAugust 27, 2026

Edit on GitHub