Instrument your C++ application with OpenTelemetry
This walkthrough shows how to add observability to your C++ application using the OpenTelemetry C++ libraries and tools.
Feature | Supported |
---|---|
Automatic Instrumentation | No |
Automatic OneAgent Ingestion | No |
Prerequisites
- Dynatrace version 1.222+
- A supported C++ compiler (C++ 11 and later)
- The Protocol Buffers library
- The OpenTelemetry library
- For tracing, W3C Trace Context is enabled
- From the Dynatrace menu, go to Settings > Preferences > OneAgent features.
- Turn on Send W3C Trace Context HTTP headers.
Get the Dynatrace access details
Determine the API base URL
For details on how to assemble the base OTLP endpoint URL, see Export with OTLP. The URL should end in /api/v2/otlp
.
Get API access token
The access token for ingesting traces, logs, and metrics can be generated in your Dynatrace menu under Access tokens.
Export with OTLP has more details on the format and the necessary access scopes.
Set up OpenTelemetry
-
Add the following directives to your CMake build configuration in
CMakeLists.txt
:1find_package(CURL REQUIRED)2find_package(Protobuf REQUIRED)3find_package(opentelemetry-cpp CONFIG REQUIRED)45include_directories("${OPENTELEMETRY_CPP_INCLUDE_DIRS}")67target_link_libraries(8 <YOUR_EXE_NAME> ${OPENTELEMETRY_CPP_LIBRARIES}9 opentelemetry_trace10 opentelemetry_common11 opentelemetry_http_client_curl12 opentelemetry_exporter_otlp_http13 opentelemetry_exporter_otlp_http_client14 opentelemetry_otlp_recordable15 opentelemetry_resources16 opentelemetry_metrics17 opentelemetry_exporter_otlp_http_metric18) -
Create a file named
otel.h
in your application directory and save the following content:1// tracer_common.h23#pragma once45#include "opentelemetry/exporters/otlp/otlp_http_exporter_factory.h"6#include "opentelemetry/exporters/otlp/otlp_http_exporter_options.h"7#include "opentelemetry/exporters/ostream/span_exporter_factory.h"89#include "opentelemetry/context/propagation/global_propagator.h"10#include "opentelemetry/context/propagation/text_map_propagator.h"1112#include "opentelemetry/nostd/shared_ptr.h"1314#include "opentelemetry/sdk/trace/simple_processor_factory.h"15#include "opentelemetry/sdk/trace/tracer_context.h"16#include "opentelemetry/sdk/trace/tracer_context_factory.h"17#include "opentelemetry/sdk/trace/tracer_provider_factory.h"18#include "opentelemetry/trace/propagation/http_trace_context.h"19#include "opentelemetry/trace/provider.h"202122#include <cstring>23#include <iostream>24#include <vector>25#include <fstream>26#include <list>2728using namespace std;29namespace nostd = opentelemetry::nostd;30namespace otlp = opentelemetry::exporter::otlp;31namespace sdktrace = opentelemetry::sdk::trace;32namespace resource = opentelemetry::sdk::resource;3334namespace35{36 // Class definition for context propagation3738 template <typename T>39 class HttpTextMapCarrier : public opentelemetry::context::propagation::TextMapCarrier40 {41 public:42 HttpTextMapCarrier<T>(T &headers) : headers_(headers) {}4344 HttpTextMapCarrier() = default;4546 virtual nostd::string_view Get(nostd::string_view key) const noexcept override47 {48 std::string key_to_compare = key.data();49 if (key == opentelemetry::trace::propagation::kTraceParent)50 {51 key_to_compare = "Traceparent";52 }53 else if (key == opentelemetry::trace::propagation::kTraceState)54 {55 key_to_compare = "Tracestate";56 }57 auto it = headers_.find(key_to_compare);58 if (it != headers_.end())59 {60 return it->second;61 }62 return "";63 }6465 virtual void Set(nostd::string_view key, nostd::string_view value) noexcept override66 {67 headers_.insert(std::pair<std::string, std::string>(std::string(key), std::string(value)));68 }6970 T headers_;71 };72737475 void initOpenTelemetry()76 {77 // ===== GENERAL SETUP =====7879 std::string DT_API_URL = "";80 std::string DT_API_TOKEN = "";8182 resource::ResourceAttributes resource_attributes = {83 {"service.name", "cpp-quickstart"}, //TODO Replace with the name of your application84 {"service.version", "1.0.1"} //TODO Replace with the version of your application85 };8687 resource::ResourceAttributes dt_resource_attributes;8889 try90 {91 for (string name : {"dt_metadata_e617c525669e072eebe3d0f08212e8f2.properties", "/var/lib/dynatrace/enrichment/dt_metadata.properties", "/var/lib/dynatrace/enrichment/dt_host_metadata.properties"})92 {93 string file_path;94 ifstream dt_file;95 dt_file.open(name);96 if (dt_file.is_open())97 {98 string dt_metadata;99 ifstream dt_properties;100 while (getline(dt_file, file_path))101 {102 dt_properties.open(file_path);103 if (dt_properties.is_open())104 {105 while (getline(dt_properties, dt_metadata))106 {107 dt_resource_attributes.SetAttribute(108 dt_metadata.substr(0, dt_metadata.find("=")),109 dt_metadata.substr(dt_metadata.find("=") + 1)110 );111 }112 dt_properties.close();113 }114 }115 dt_file.close();116 }117 }118 }119 catch (...) {}120121122 // ===== TRACING SETUP =====123124 otlp::OtlpHttpExporterOptions traceOptions;125 traceOptions.url = DT_API_URL + "/v1/traces";126 traceOptions.content_type = otlp::HttpRequestContentType::kBinary;127 traceOptions.http_headers.insert(128 std::make_pair<const std::string, std::string>("Authorization", "Api-Token " + DT_API_TOKEN)129 );130131 auto dt_resource = resource::Resource::Create(dt_resource_attributes);132 auto resource = resource::Resource::Create(resource_attributes);133 auto merged_resource = dt_resource.Merge(resource);134135 auto exporter = otlp::OtlpHttpExporterFactory::Create(traceOptions);136 auto processor =137 sdktrace::SimpleSpanProcessorFactory::Create(std::move(exporter));138 std::vector<std::unique_ptr<sdktrace::SpanProcessor>> processors;139 processors.push_back(std::move(processor));140141 auto context = sdktrace::TracerContextFactory::Create(std::move(processors), merged_resource);142 std::shared_ptr<opentelemetry::trace::TracerProvider> provider = sdktrace::TracerProviderFactory::Create(std::move(context));143144 opentelemetry::trace::Provider::SetTracerProvider(provider);145146 opentelemetry::context::propagation::GlobalTextMapPropagator::SetGlobalPropagator(147 opentelemetry::nostd::shared_ptr<opentelemetry::context::propagation::TextMapPropagator>(148 new opentelemetry::trace::propagation::HttpTraceContext()149 )150 );151152153 // ===== METRIC SETUP =====154155 otlp_exporter::OtlpHttpMetricExporterOptions otlpOptions;156 otlpOptions.url = DT_API_URL + "/v1/metrics";157 otlpOptions.aggregation_temporality = metric_sdk::AggregationTemporality::kDelta;158 otlpOptions.content_type = otlp_exporter::HttpRequestContentType::kBinary;159 otlpOptions.http_headers.insert(160 std::make_pair<const std::string, std::string>("Authorization", "Api-Token " + DT_API_TOKEN)161 );162163 //This creates the exporter with the options we have defined above.164 auto exporter = otlp_exporter::OtlpHttpMetricExporterFactory::Create(otlpOptions);165166 //Build MeterProvider and Reader167 metric_sdk::PeriodicExportingMetricReaderOptions options;168 options.export_interval_millis = std::chrono::milliseconds(1000);169 options.export_timeout_millis = std::chrono::milliseconds(500);170171 std::unique_ptr<metric_sdk::MetricReader> reader{ new metric_sdk::PeriodicExportingMetricReader(std::move(exporter), options) };172173 auto provider = std::shared_ptr<metrics_api::MeterProvider>(new metric_sdk::MeterProvider());174 auto p = std::static_pointer_cast<metric_sdk::MeterProvider>(provider);175 p->AddMetricReader(std::move(reader));176177 metrics_api::Provider::SetMeterProvider(provider);178 }179} -
Configure
DT_API_URL
andDT_API_TOKEN
for the Dynatrace URL and access token inotel.h
.
Instrument your application
To use OpenTelemetry, you first need to complete these two steps:
-
Add the necessary header files to your code.
To add the header files, include
otel.h
wherever you want to make use of OpenTelemetry.1#include "otel.h" -
Initialize OpenTelemetry.
For the initialization, use the
initOpenTelemetry
function inotel.h
and call it early on in the startup code of your application.
Add tracing
-
Get a reference to the tracer provider.
1auto provider = opentelemetry::trace::Provider::GetTracerProvider(); -
Obtain a tracer object.
1auto tracer = provider->GetTracer("my-tracer"); -
With
tracer
, we can now start new spans and set them for the current execution scope.1StartSpanOptions options;2options.kind = SpanKind::kServer;34auto span = tracer->StartSpan("Call to /myendpoint", {5 { "http.method", "GET" },6 { "net.protocol.version", "1.1" }7 }, options);89auto scope = tracer->WithActiveSpan(span);1011// TODO: Your code goes here1213span->End();In the above code, we:
Create a new span and name it "Call to /myendpoint"
- Add two attributes, following the semantic naming convention, specific to the action of this span: information on the HTTP method and version
- Add a
TODO
in place of the eventual business logic - Call the span's
End()
method to complete the span
Collect metrics
-
Get a reference to the meter provider.
1auto provider = metrics_api::Provider::GetMeterProvider(); -
Obtain a meter object.
1nostd::shared_ptr<metrics_api::Meter> meter = provider->GetMeter("my-meter", "1.0.1"); -
With
meter
, we can now create individual instruments, such as a counter.1auto request_counter = meter->CreateUInt64Counter("request_counter"); -
We can now invoke the
Add()
method ofrequest_counter
to record new values with the counter and save additional attributes (for example,action.type
).1std::map<std::string, std::string> labels = { {"action.type", "create"} };2auto labelkv = opentelemetry::common::KeyValueIterableView<decltype(labels)>{ labels };34request_counter.Add(1, labelkv);
Connect logs
OpenTelemetry logging is currently not yet available for C++ and is still under development.
Ensure context propagation optional
Context propagation is particularly important when network calls (for example, REST) are involved.
In the following examples, we assume that we are handling context propagation using the standard W3C trace context headers, and we receive and set HTTP headers with the OpenTelemetry http_client::Headers
object.
For that purpose, we use an instance of the class HttpTextMapCarrier
, which we defined during the setup, and which is based on the OpenTelemetry class TextMapCarrier
.
Extracting the context when receiving a request
To extract information on an existing context, we call the Extract
method of the global propagator singleton and pass it the HttpTextMapCarrier
instance, as well as the current context. This returns a new context object (new_context
), which we allows us to continue the previous trace with our spans.
1#include "opentelemetry/trace/context.h"2#include "otel.h"34using namespace opentelemetry::trace;5namespace context = opentelemetry::context;67// your method8{9 StartSpanOptions options;10 options.kind = SpanKind::kServer;1112 // extract context from http header13 HttpTextMapCarrier<http_client::Headers> carrier;14 auto prop = context::propagation::GlobalTextMapPropagator::GetGlobalPropagator();15 auto current_ctx = context::RuntimeContext::GetCurrent();16 auto new_context = prop->Extract(carrier, current_ctx);17 options.parent = GetSpan(new_context)->GetContext();1819 // start scoped span with parent context extracted from http header20 auto span = get_tracer("manual-server")21 ->StartSpan("my-server-span", { //TODO Replace with the name of your span22 {"my-server-key-1", "my-server-value-1"} //TODO Add attributes23 }, options);2425 auto scope = get_tracer("http_server")->WithActiveSpan(span);2627 // your code goes here2829 span->End();30}
Injecting the context when sending requests
For injecting current context information into an outbound request, we call the Inject
method of the global propagator singleton and pass it the HttpTextMapCarrier
instance, as well as the current context. This adds the applicable headers to the carrier
instance, which we then use in the text step with our HTTP request.
1#include "opentelemetry/ext/http/client/http_client_factory.h"2#include "opentelemetry/ext/http/common/url_parser.h"3#include "opentelemetry/trace/context.h"4#include "otel.h"56using namespace opentelemetry::trace;7namespace context = opentelemetry::context;8namespace http_client = opentelemetry::ext::http::client;910// your method11{12 auto http_client = http_client::HttpClientFactory::CreateSync();13 std::string url = "<HTTP_URL>";1415 // start scoped active span16 StartSpanOptions options;17 options.kind = SpanKind::kClient;18 opentelemetry::ext::http::common::UrlParser url_parser(url);1920 std::string span_name = url_parser.path_; // Fetch the URL path to be used as span name21 auto span = get_tracer("http-client")22 ->StartSpan(span_name, {23 {"my-client-key-1", "my-client-value-1"} //TODO Add attributes24 }, options);2526 auto scope = get_tracer("http-client")->WithActiveSpan(span);2728 // inject current context into http header29 auto current_ctx = context::RuntimeContext::GetCurrent();30 HttpTextMapCarrier<http_client::Headers> carrier;31 auto prop = context::propagation::GlobalTextMapPropagator::GetGlobalPropagator();32 prop->Inject(carrier, current_ctx);3334 // send http request35 http_client::Result result = http_client->Get(url, carrier.headers_);3637 //your code goes here3839 span->End();40}
Configure data capture to meet privacy requirements optional
While Dynatrace automatically captures all OpenTelemetry resource and span attributes, only attribute values specified in the allowlist are stored and displayed in the Dynatrace web UI. This prevents accidental storage of personal data, so you can meet your privacy requirements and control the amount of monitoring data stored.
To view your custom span attributes, you need to allow them in the Dynatrace web UI first.
- Span attributes: In the Dynatrace menu, go to Settings and select Server-side service monitoring > Span attributes.
- Resource attributes: In the Dynatrace menu, go to Settings and select Server-side service monitoring > Resource attributes.
Verify data ingestion into Dynatrace
Once you have finished the instrumentation of your application, perform a couple of test actions to create and send demo traces, metrics, and logs and verify that they were correctly ingested into Dynatrace.
To do that for traces, in the Dynatrace menu, go to Distributed traces and select the Ingested traces tab. If you use OneAgent, select PurePaths instead.
Metrics and logs can be found under their respective entries at Observe and explore.