Skip to main content

C++

This guide explains how to instrument a C++ application in Motadata APM for trace ingestion.

C++ instrumentation is supported only through the Linux agent, and can be configured for the following deployment types:

  • Host/VM
  • Docker

Prerequisites

Before instrumenting a C++ application, ensure the following prerequisites are met:

  • The Motadata Agent must be installed and running on the Linux machine where the application is deployed.
  • The OTEL collector must also be running as part of the Motadata Agent.
  • To verify the agent status, run:
service motadata status
  • C++ instrumentation is supported only for Linux.
  • CMake must be 3.16 or higher.
  • Compiler must be GCC 5+ or Clang 6+.
  • protoc must be 3.12 or higher.
  • pkg-config, curl, git, and required build dependencies must be available.
  • The OTEL C++ SDK version used in this setup is v1.23.0.
  • The Motadata wrapper library used for instrumentation is motadata-apm-cpp-instrumentation.
  • The application must already be working and able to start without errors before instrumentation is added.
  • You may need root/sudo access for dependency installation, SDK installation, and runtime setup.

Configuration Steps

Step1: Register the Application Service in Motadata APM

Go to Menu > Settings > APM > Application Registration. Click Application Registration to register a new application.

From the registration screen, select the required instrumentation type.

Step2: Configure C++ Instrumentation on Linux Host/VM

C++ Trace Configuration

FieldDescription
Select AgentSelect the Linux Host/VM where this C++ application is running.
Select LanguageSelect C++ from the language icons.
Service NameProvide a unique and meaningful service name, such as order-service, inventory-engine, or pricing-service.
Setup InstructionsPlease refer the below section for the steps to instrument a C++ application.
Service Attributes (Tags)Add key-value tags to your application for better filtering and organizing data in Explorer. Attribute key names must be lowercase.
Add Custom ParametersAllows you to define custom parameters for advanced use cases.

Click Apply Configuration to complete the registration.

Providing these details displays you can instrument your application. Execute the setup script and restart the application to start collecting Traces.

Setup Instructions

1. Verify Your Machine Has the Required Tools

Run the following commands and confirm that each one returns a valid version:

Command to runWhat to expect
cmake --versionMust show version 3.16 or higher. CMake is the build system.
g++ --versionMust show GCC 5 or higher. This is the C++ compiler. Alternatively, you can use clang++ --version and it must show Clang 6 or higher.
protoc --versionMust show 3.12 or higher. This is the Protobuf compiler, required by the Motadata instrumentation SDK to serialize spans.
curl --versionAny version is acceptable. libcurl is used to send traces over HTTP.
git --versionAny version is acceptable. Git is used to download the Motadata instrumentation SDK source code.
pkg-config --versionAny version is acceptable. pkg-config helps CMake locate installed libraries.

If any dependency is missing, install it.

For Ubuntu or Debian:

sudo apt-get update
sudo apt-get install -y \
build-essential \
cmake \
git \
pkg-config \
libcurl4-openssl-dev \
libssl-dev \
libprotobuf-dev \
protobuf-compiler

For RHEL, CentOS, or Amazon Linux:

sudo yum groupinstall "Development Tools"
sudo yum install -y \
cmake3 git pkg-config \
libcurl-devel openssl-devel \
protobuf-devel protobuf-compiler

If cmake3 is installed instead of cmake, create the alias:

sudo ln -s /usr/bin/cmake3 /usr/bin/cmake

2. Install and Build Instrumentation C++ SDK

cd /opt

sudo git clone --depth 1 --branch v1.23.0 \
https://github.com/open-telemetry/opentelemetry-cpp.git

cd /opt/opentelemetry-cpp

sudo git submodule update --init --recursive

Create the build directory and configure the build:

cd /opt/opentelemetry-cpp
mkdir build
cd build

cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_STANDARD=14 \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DWITH_OTLP_HTTP=ON \
-DWITH_OTLP_GRPC=OFF \
-DBUILD_TESTING=OFF \
-DWITH_EXAMPLES=OFF \
-DCMAKE_INSTALL_PREFIX=/usr/local
CMake FlagWhat it does and why
-DCMAKE_BUILD_TYPE=ReleaseCompile in Release mode for an optimized and smaller binary. Do not use Debug here.
-DCMAKE_CXX_STANDARD=14Use C++14 or higher. The motadata-apm-cpp-instrumentation library supports C++14 and above.
-DCMAKE_POSITION_INDEPENDENT_CODE=ONRequired for static libraries to link correctly into other programs.
-DWITH_OTLP_HTTP=ONEnables the HTTP exporter. This is how spans are sent to port 4318, so it is required.
-DWITH_OTLP_GRPC=OFFDisables the gRPC exporter. This keeps the build simpler and it is not required here.
-DBUILD_TESTING=OFFSkips compiling the SDK test suite, which saves around 10 minutes of build time.
-DWITH_EXAMPLES=OFFSkips compiling SDK examples because they are not needed.
-DCMAKE_INSTALL_PREFIX=/usr/localInstalls the compiled SDK headers and libraries into /usr/local, which is a standard system path.

Compile the SDK

cmake --build . -j$(nproc)
sudo cmake --install .

Verify the installation:

ls /usr/local/include/opentelemetry/

Expected output: folders named trace/, sdk/, exporters/, common/, etc.

ls /usr/local/lib/ | grep opentelemetry | head -5

Expected output: several files named libopentelemetry_*.a

3. Add the Motadata Wrapper to the Project

Clone the wrapper repository into the project root so that the directory structure contains motadata-cpp/ alongside your project files.

Expected structure:

your-project/
├── CMakeLists.txt
├── src/
│ ├── main.cpp
│ └── other_files.cpp
└── motadata-cpp/
├── include/
│ └── motadata.h
├── src/
│ └── motadata.cpp
└── CMakeLists.txt

4. Update CMakeLists.txt

Add the Motadata wrapper as a subdirectory and link the required libraries.

cmake_minimum_required(VERSION 3.16)
project(YourProjectName LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_subdirectory(motadata-cpp)

add_executable(your_app
src/main.cpp
src/other_files.cpp
)

target_link_libraries(your_app PRIVATE
motadata
curl
pthread
)
AdditionWhy it is needed
add_subdirectory(motadata-cpp)Tells CMake to enter the motadata-cpp/ folder and process its CMakeLists.txt. This automatically builds the instrumentation library as part of your project build, so no separate build step is needed.
motadataLinks the compiled wrapper library into your application. After this, your code can call motadata:: functions.
curlLinks libcurl, which is the HTTP client library. The wrapper uses curl internally to send spans to the Collector. This is a standard system library already installed on the machine.
pthreadLinks the POSIX threads library. It is required on Linux for multithreaded execution and is a standard system library.

5. Verify the Library Compiles

Before writing any instrumentation code, confirm the library builds correctly with your project:

cd your-project
mkdir build
cd build

Configure your project

cmake .. -DCMAKE_BUILD_TYPE=Release

In the CMake output you should see a line like:

-- motadata configuration:
-- Version : 1.0.0
-- C++ Standard : 17

Compile everything

make -j$(nproc)

6. Include the Motadata Header

In every .cpp file where spans will be created, add:

#include "motadata.h"

Initialize Telemetry

Initialize telemetry once, at the top of main():

#include "motadata.h"
#include <iostream>

int main() {
motadata::TelemetryConfig cfg;
cfg.service_name = "your-service-name";
cfg.service_version = "1.0.0";
cfg.collector_url = "http://localhost:4318";
cfg.debug_print_to_console = true;

if (!motadata::initTelemetry(cfg)) {
std::cerr << "[telemetry] Warning: init failed, continuing without tracing\n";
}

runYourApplication();

motadata::shutdown();
return 0;
}
Config FieldExplanation
service_nameDefines how your service appears in the observability UI. Use a short, lowercase, descriptive name. Example: order-service, auth-api, data-processor.
service_versionSpecifies your application version string. This is useful for identifying which release introduced a problem. Example: 2.1.0, release-47.
collector_urlSpecifies the URL of the Motadata Instrumentation Collector. The default value is http://localhost:4318. Change this only if the Collector is running on a different host or port.
debug_print_to_consoleWhen set to true, every span is also printed to stdout in a human-readable format. This is useful during development and troubleshooting. Set it to false in production because it adds unnecessary log noise.

7. Create Spans

Basic span example:

void processOrder(const std::string& order_id, double total) {
auto span = motadata::startSpan("processOrder");

span->setAttr("order.id", order_id);
span->setAttr("order.total", total);
span->setAttr("order.currency", "USD");

validateOrder(order_id);
chargePayment(order_id, total);
sendConfirmation(order_id);

span->setOk();
span->end();
}

Error handling example:

void chargePaymentGateway(const std::string& order_id, double amount) {
auto span = motadata::startSpan("chargePaymentGateway");
span->setAttr("order.id", order_id);
span->setAttr("amount", amount);

try {
callGatewayAPI(order_id, amount);
span->setOk();
span->end();
} catch (const std::exception& e) {
span->recordException(e);
span->end();
throw;
}
}

Nested spans

void processOrder() {
auto span = motadata::startSpan("processOrder");
fetchOrderFromDatabase();
chargePaymentGateway();
sendConfirmationEmail();

span->setOk();
span->end();
}

void fetchOrderFromDatabase() {
auto span = motadata::startSpan("fetchOrderFromDatabase");
span->setAttr("db.system", "postgresql");
span->setAttr("db.name", "orders");
span->setAttr("db.statement", "SELECT * FROM orders WHERE id = ?");
span->setAttr("db.rows_returned", 1);
span->setOk();
span->end();
}

Cross-thread example:

void processOrdersInBackground() {
auto parent = motadata::startSpan("processOrdersInBackground");
parent->setAttr("batch.size", 100);

auto ctx = parent->getContext();

std::thread worker([ctx]() {
auto child = motadata::startSpan("WorkerThread::ProcessBatch", ctx);
child->setAttr("thread.type", "batch-worker");
child->setOk();
child->end();
});

worker.join();

parent->setOk();
parent->end();
}

HTTP propagation example:

void callInventoryService(const std::string& sku) {
auto span = motadata::startSpan("callInventoryService");
span->setAttr("http.method", "GET");
span->setAttr("http.url", "http://inventory-service/api/stock/" + sku);

motadata::HttpHeaders headers;
headers["Content-Type"] = "application/json";

motadata::injectHeaders(headers);

span->setAttr("http.status_code", 200);
span->setOk();
span->end();
}

8. Build the Application

cd your-project
mkdir build
cd build

cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)

9. Run and Verify

Run the application:

./your_app

If debug_print_to_console = true, spans are printed to the terminal during execution.

You can also verify collector connectivity independently:

curl -X POST http://localhost:4318/v1/traces \
-H "Content-Type: application/json" \
-d '{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"connectivity-test"}}]},"scopeSpans":[{"spans":[{"traceId":"00000000000000000000000000000001","spanId":"0000000000000001","name":"test-span","kind":1,"startTimeUnixNano":"1677000000000000000","endTimeUnixNano":"1677000001000000000","status":{"code":1}}]}]}]}'

Expected response:

{}

Important Rules

RuleExplanation
Always call span->end()Every startSpan() must have a matching end() on every code path.
Always call motadata::shutdown() before exitBuffered spans are flushed during shutdown.
Call motadata::initTelemetry() only onceInitialize telemetry once, at the top of main().
Never terminate the application if telemetry initialization failsApplication flow must continue even when tracing is unavailable.
Never share span objects across threadsUse span->getContext() and create a new child span in the target thread.

Step4: Verify Trace Collection

Once the application is running, verify the following:

  • Confirm that the service has been registered successfully.
  • On the service registration screen, the Service Trace Collection Status should display Running.
  • The traces should start appearing in the APM Explorer screen.