C++

This guide shows you how to instrument a C++ application with the upstream opentelemetry-cpp SDK and send traces directly to Middleware. No Middleware Host Agent is required: the OTLP HTTP exporter in your application sends data straight to your Middleware account.

Prerequisites#

  • A C++14 (or newer) compiler and CMake 3.16+
  • Linux build dependencies for the OTLP HTTP exporter: Protobuf, Abseil and libcurl. The commands below use Debian/Ubuntu package names.
  • Middleware account & project: your Middleware UID (https://<uid>.middleware.io) and API key (MW_API_KEY)
  • Network access from your application to https://<uid>.middleware.io:443

For a deeper overview of Middleware's OTLP endpoints and headers, see the OpenTelemetry getting started guide.

Setup#

1 Set environment variables#

The OTLP HTTP exporter and the SDK resource read their configuration from standard environment variables. Set them wherever your application runs:

1export OTEL_EXPORTER_OTLP_ENDPOINT="https://<MW_UID>.middleware.io:443"
2export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
3export OTEL_EXPORTER_OTLP_HEADERS="authorization=<MW_API_KEY>"
4export OTEL_SERVICE_NAME="<service-name>"
5export OTEL_RESOURCE_ATTRIBUTES="service.version=<service-version>,deployment.environment=<environment>"

Replace:

  • <uid> with your Middleware project UID.
  • <MW_API_KEY> with your Middleware API key.
  • <service-name> with a meaningful name for this service (for example, payments-api).

Set OTEL_EXPORTER_OTLP_ENDPOINT to the root URL only. The OTLP HTTP exporter appends /v1/traces for you.

2 Build and install opentelemetry-cpp#

Install the build dependencies:

1sudo apt-get update
2sudo apt-get install -y build-essential cmake git libprotobuf-dev protobuf-compiler libabsl-dev libcurl4-openssl-dev

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

1git clone --depth 1 --branch v1.28.0 https://github.com/open-telemetry/opentelemetry-cpp.git
2cd opentelemetry-cpp
3cmake -B build -DCMAKE_BUILD_TYPE=Release -DWITH_OTLP_HTTP=ON -DBUILD_TESTING=OFF -DWITH_BENCHMARK=OFF
4cmake --build build --target all --config Release
5sudo cmake --install build --config Release --prefix /usr/local

If you use a package manager such as vcpkg or Conan, install opentelemetry-cpp with the OTLP HTTP exporter feature instead of building from source. The CMake targets in the next step are the same.

1cmake_minimum_required(VERSION 3.16)
2project(my-cpp-app CXX)
3
4set(CMAKE_CXX_STANDARD 14)
5set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
7find_package(opentelemetry-cpp CONFIG REQUIRED COMPONENTS api sdk exporters_otlp_http)
8
9add_executable(my-cpp-app main.cc)
10target_link_libraries(my-cpp-app
11  PRIVATE
12    opentelemetry-cpp::api
13    opentelemetry-cpp::trace
14    opentelemetry-cpp::otlp_http_exporter
15)

4 Initialize the tracer#

Set up a tracer provider with a batch span processor and the OTLP HTTP exporter in main.cc. Call CleanupTracer() before the process exits so buffered spans are flushed:

1#include <memory>
2#include <utility>
3
4#include "opentelemetry/exporters/otlp/otlp_http_exporter_factory.h"
5#include "opentelemetry/exporters/otlp/otlp_http_exporter_options.h"
6#include "opentelemetry/sdk/resource/resource.h"
7#include "opentelemetry/sdk/trace/batch_span_processor_factory.h"
8#include "opentelemetry/sdk/trace/batch_span_processor_options.h"
9#include "opentelemetry/sdk/trace/provider.h"
10#include "opentelemetry/sdk/trace/tracer_provider.h"
11#include "opentelemetry/sdk/trace/tracer_provider_factory.h"
12#include "opentelemetry/trace/provider.h"
13
14namespace otlp      = opentelemetry::exporter::otlp;
15namespace resource  = opentelemetry::sdk::resource;
16namespace trace_api = opentelemetry::trace;
17namespace trace_sdk = opentelemetry::sdk::trace;
18
19std::shared_ptr<trace_sdk::TracerProvider> tracer_provider;
20
21void InitTracer()
22{
23  otlp::OtlpHttpExporterOptions exporter_options;
24  auto exporter = otlp::OtlpHttpExporterFactory::Create(exporter_options);
25
26  trace_sdk::BatchSpanProcessorOptions processor_options;
27  auto processor =
28      trace_sdk::BatchSpanProcessorFactory::Create(std::move(exporter), processor_options);
29
30  tracer_provider =
31      trace_sdk::TracerProviderFactory::Create(std::move(processor), resource::Resource::Create({}));
32
33  std::shared_ptr<trace_api::TracerProvider> api_provider = tracer_provider;
34  trace_sdk::Provider::SetTracerProvider(api_provider);
35}
36
37void CleanupTracer()
38{
39  // Flush pending spans before the application exits
40  if (tracer_provider)
41  {
42    tracer_provider->ForceFlush();
43  }
44  tracer_provider.reset();
45  std::shared_ptr<trace_api::TracerProvider> none;
46  trace_sdk::Provider::SetTracerProvider(none);
47}
48
49int main()
50{
51  InitTracer();
52
53  auto tracer = trace_api::Provider::GetTracerProvider()->GetTracer("my-cpp-app");
54  {
55    auto span  = tracer->StartSpan("main-operation");
56    auto scope = tracer->WithActiveSpan(span);
57    // your application logic
58    span->End();
59  }
60
61  CleanupTracer();
62  return 0;
63}

A default-constructed OtlpHttpExporterOptions reads the endpoint, protocol and headers from the OTEL_EXPORTER_OTLP_* variables, and Resource::Create({}) merges OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES into the resource.

5 Build and run your application#

1cmake -B build
2cmake --build build
3./build/my-cpp-app

Add attributes to spans#

Attach attributes when you start a span, or later on the span object, so you can filter on them in Middleware:

1auto span = tracer->StartSpan("handle-order", {{"order.id", order_id}});
2span->SetAttribute("order.items", item_count);
3// ...
4span->End();

Other deployment targets#

The OpenTelemetry configuration is the same everywhere; only the way you declare environment variables changes.

1docker run \
2  -e OTEL_EXPORTER_OTLP_ENDPOINT="https://<MW_UID>.middleware.io:443" \
3  -e OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" \
4  -e OTEL_EXPORTER_OTLP_HEADERS="authorization=<MW_API_KEY>" \
5  -e OTEL_SERVICE_NAME="<service-name>" \
6  <your-image>
1env:
2  - name: OTEL_EXPORTER_OTLP_ENDPOINT
3    value: "https://<MW_UID>.middleware.io:443"
4  - name: OTEL_EXPORTER_OTLP_PROTOCOL
5    value: "http/protobuf"
6  - name: OTEL_EXPORTER_OTLP_HEADERS
7    value: "authorization=<MW_API_KEY>"
8  - name: OTEL_SERVICE_NAME
9    value: "<service-name>"

Store the API key in a Kubernetes Secret and reference it with valueFrom.secretKeyRef in production.

View your data#

After you start the application and send some traffic, wait 3–5 minutes, then open APM → Services or the Traces explorer in Middleware and filter by your service name.

Environment variables#

VariablePurposeExample
OTEL_EXPORTER_OTLP_ENDPOINTMiddleware OTLP endpoint for your project.https://<uid>.middleware.io:443
OTEL_EXPORTER_OTLP_PROTOCOLOTLP transport used by the exporter.http/protobuf
OTEL_EXPORTER_OTLP_HEADERSAuthenticates requests with your Middleware API key.authorization=<MW_API_KEY>
OTEL_SERVICE_NAMEService name shown in Middleware APM.payments-api
OTEL_RESOURCE_ATTRIBUTESExtra resource attributes attached to every span.service.version=1.4.2,deployment.environment=prod

Troubleshooting#

  • find_package(opentelemetry-cpp) fails: pass the install prefix to CMake, for example cmake -B build -DCMAKE_PREFIX_PATH=/usr/local.
  • No data in Middleware: confirm the environment variables are set in the process that runs your app, and that the endpoint does not already include /v1/traces.
  • Spans missing for short-lived programs: call CleanupTracer() before exiting. The batch processor exports in the background, and anything still buffered is lost if the process exits first.
  • Debugging the exporter: set exporter_options.console_debug = true; before creating the exporter, and raise the SDK log level with opentelemetry::sdk::common::internal_log::GlobalLogHandler::SetLogLevel(opentelemetry::sdk::common::internal_log::LogLevel::Debug); (from opentelemetry/sdk/common/global_log_handler.h) to log each export request and response.

Need assistance or want to learn more about Middleware? Get in touch with us via our Contact Us or join our Slack channel.

What did you think of this content?