---
title: "C++"
description: "Instrument C++ applications with opentelemetry-cpp and send traces directly to Middleware over OTLP/HTTP."
section: "APM"
breadcrumb: "APM > C++"
canonical: "https://docs.middleware.io/get-started/apm/cpp"
---
# C++

This guide shows you how to instrument a C++ application with the upstream [opentelemetry-cpp](https://github.com/open-telemetry/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](/get-started/agent/otel).

## 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:

```bash
export OTEL_EXPORTER_OTLP_ENDPOINT="https://<uid>.middleware.io:443"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_EXPORTER_OTLP_HEADERS="authorization=<MW_API_KEY>"
export OTEL_SERVICE_NAME="<service-name>"
export 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`).

> **Note:** 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:

```bash
sudo apt-get update
sudo 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:

```bash
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
sudo cmake --install build --config Release --prefix /usr/local
```

> **Tip:** 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.

#### 3 Link opentelemetry-cpp in CMakeLists.txt

```cmake
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
)
```

#### 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:

```cpp
#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"

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

std::shared_ptr<trace_sdk::TracerProvider> tracer_provider;

void InitTracer()
{
  otlp::OtlpHttpExporterOptions exporter_options;
  auto exporter = otlp::OtlpHttpExporterFactory::Create(exporter_options);

  trace_sdk::BatchSpanProcessorOptions processor_options;
  auto processor =
      trace_sdk::BatchSpanProcessorFactory::Create(std::move(exporter), processor_options);

  tracer_provider =
      trace_sdk::TracerProviderFactory::Create(std::move(processor), resource::Resource::Create({}));

  std::shared_ptr<trace_api::TracerProvider> api_provider = tracer_provider;
  trace_sdk::Provider::SetTracerProvider(api_provider);
}

void CleanupTracer()
{
  // Flush pending spans before the application exits
  if (tracer_provider)
  {
    tracer_provider->ForceFlush();
  }
  tracer_provider.reset();
  std::shared_ptr<trace_api::TracerProvider> none;
  trace_sdk::Provider::SetTracerProvider(none);
}

int main()
{
  InitTracer();

  auto tracer = trace_api::Provider::GetTracerProvider()->GetTracer("my-cpp-app");
  {
    auto span  = tracer->StartSpan("main-operation");
    auto scope = tracer->WithActiveSpan(span);
    // your application logic
    span->End();
  }

  CleanupTracer();
  return 0;
}
```

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

```bash
cmake -B build
cmake --build build
./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:

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

## Other deployment targets

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

#### Docker

```bash
docker run \
  -e OTEL_EXPORTER_OTLP_ENDPOINT="https://<uid>.middleware.io:443" \
  -e OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" \
  -e OTEL_EXPORTER_OTLP_HEADERS="authorization=<MW_API_KEY>" \
  -e OTEL_SERVICE_NAME="<service-name>" \
  <your-image>
```

#### Kubernetes

```yaml
env:
  - name: OTEL_EXPORTER_OTLP_ENDPOINT
    value: "https://<uid>.middleware.io:443"
  - name: OTEL_EXPORTER_OTLP_PROTOCOL
    value: "http/protobuf"
  - name: OTEL_EXPORTER_OTLP_HEADERS
    value: "authorization=<MW_API_KEY>"
  - name: OTEL_SERVICE_NAME
    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**](https://app.middleware.io/apm/services) or the **Traces** explorer in Middleware and filter by your service name.

## Environment variables

| Variable | Purpose | Example |
| --- | --- | --- |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Middleware OTLP endpoint for your project. | `https://<uid>.middleware.io:443` |
| `OTEL_EXPORTER_OTLP_PROTOCOL` | OTLP transport used by the exporter. | `http/protobuf` |
| `OTEL_EXPORTER_OTLP_HEADERS` | Authenticates requests with your Middleware API key. | `authorization=<MW_API_KEY>` |
| `OTEL_SERVICE_NAME` | Service name shown in Middleware APM. | `payments-api` |
| `OTEL_RESOURCE_ATTRIBUTES` | Extra 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 page or join our Slack channel.
