C++ OpenTelemetry: различия между версиями
(Новая страница: «В этом пошаговом руководстве показано, как добавить возможность наблюдения в ваше прило...») |
|||
| Строка 217: | Строка 217: | ||
resource::ResourceAttributes resource_attributes = <nowiki>{{"service.name", name}, | resource::ResourceAttributes resource_attributes = <nowiki>{{"service.name", name}, | ||
{"service.version", version}}</nowiki>; | |||
<nowiki> </nowiki> resource::ResourceAttributes dt_resource_attributes; | <nowiki> </nowiki> resource::ResourceAttributes dt_resource_attributes; | ||
<nowiki> </nowiki> try | <nowiki> </nowiki> try | ||
<nowiki> </nowiki> { | <nowiki> </nowiki> { | ||
<nowiki> </nowiki> for (string name : {"dt_metadata_e617c525669e072eebe3d0f08212e8f2.properties", | <nowiki> </nowiki> for (string name : {"dt_metadata_e617c525669e072eebe3d0f08212e8f2.properties", | ||
<nowiki> </nowiki> "/var/lib/ | <nowiki> </nowiki> "/var/lib/astromkey/enrichment/dt_metadata.properties", | ||
<nowiki> </nowiki> "/var/lib/ | <nowiki> </nowiki> "/var/lib/astromkey/enrichment/dt_host_metadata.properties"}) | ||
<nowiki> </nowiki> { | <nowiki> </nowiki> { | ||
<nowiki> </nowiki> string file_path; | <nowiki> </nowiki> string file_path; | ||
| Строка 271: | Строка 271: | ||
<nowiki> </nowiki>void initMeter() { | <nowiki> </nowiki>void initMeter() { | ||
<nowiki> </nowiki> resource::ResourceAttributes resource_attributes = <nowiki>{{"service.name", name}, | <nowiki> </nowiki> resource::ResourceAttributes resource_attributes = <nowiki>{{"service.name", name}, | ||
{"service.version", version}}</nowiki>; | |||
<nowiki> </nowiki> otlp::OtlpHttpMetricExporterOptions otlpOptions; | <nowiki> </nowiki> otlp::OtlpHttpMetricExporterOptions otlpOptions; | ||
<nowiki> </nowiki> auto resource = resource::Resource::Create(resource_attributes); | <nowiki> </nowiki> auto resource = resource::Resource::Create(resource_attributes); | ||
Текущая версия на 09:06, 15 октября 2025
В этом пошаговом руководстве показано, как добавить возможность наблюдения в ваше приложение C++ с помощью библиотек и инструментов OpenTelemetry C++.
| Особенность | Поддерживается |
|---|---|
| Автоматические инструменты | Нет |
| Трассировки | Да |
| Метрики | Да |
| Логи | Да |
Предпосылки
- Ключ-АСТРОМ версии 1.222+
- Поддерживаемый компилятор C++ (C++ 11 и более поздние версии)
- Библиотека буферов протоколов
- Библиотека OpenTelemetry
- Для трассировки включен контекст трассировки W3C.
- Перейдите в Настройки > Функции ЕдиногоАгента.
- Включите опцию Отправлять HTTP-заголовки контекста трассировки W3C.
Получение данных для доступа к Ключ-АСТРОМ
Определение базовых URL API
Подробную информацию о сборке базового URL-адреса конечной точки OTLP см. в разделе Экспорт с помощью OTLP. URL-адрес должен заканчиваться на /api/v2/otlp.
Получение токена доступа API
Токен доступа для сбора трассировок, логов и метрик можно создать в разделе Токены доступа.
Экспорт с помощью OTLP содержит более подробную информацию о формате и необходимых областях доступа.
Настройка OpenTelemetry
1. Добавьте следующие директивы в конфигурацию сборки CMake CMakeLists.txt:
| find_package(CURL REQUIRED)
find_package(Protobuf REQUIRED) find_package(opentelemetry-cpp CONFIG REQUIRED) include_directories("${OPENTELEMETRY_CPP_INCLUDE_DIRS}") target_link_libraries( <YOUR_EXE_NAME> ${OPENTELEMETRY_CPP_LIBRARIES} opentelemetry_trace opentelemetry_common opentelemetry_http_client_curl opentelemetry_exporter_otlp_http opentelemetry_exporter_otlp_http_client opentelemetry_otlp_recordable opentelemetry_resources opentelemetry_metrics opentelemetry_exporter_otlp_http_metric ) |
2. Создайте файл с именем otel.h в директорииии вашего приложения и сохраните следующее содержимое:
| #include "opentelemetry/trace/provider.h"
#include "opentelemetry/trace/propagation/http_trace_context.h" #include "opentelemetry/context/propagation/global_propagator.h" #include "opentelemetry/sdk/trace/simple_processor_factory.h" #include "opentelemetry/sdk/trace/tracer_context.h" #include "opentelemetry/sdk/trace/tracer_context_factory.h" #include "opentelemetry/sdk/trace/tracer_provider_factory.h" #include "opentelemetry/exporters/ostream/span_exporter_factory.h" #include "opentelemetry/exporters/otlp/otlp_http_exporter_factory.h" #include "opentelemetry/metrics/provider.h" #include "opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader.h" #include "opentelemetry/sdk/metrics/view/view_registry_factory.h" #include "opentelemetry/sdk/metrics/meter_context_factory.h" #include "opentelemetry/sdk/metrics/meter_provider_factory.h" #include "opentelemetry/exporters/ostream/metric_exporter_factory.h" #include "opentelemetry/exporters/otlp/otlp_http_metric_exporter_factory.h" #include "opentelemetry/logs/provider.h" #include "opentelemetry/sdk/logs/logger.h" #include "opentelemetry/sdk/logs/logger_provider_factory.h" #include "opentelemetry/sdk/logs/simple_log_record_processor_factory.h" #include "opentelemetry/sdk/logs/logger_context_factory.h" #include "opentelemetry/exporters/ostream/log_record_exporter.h" #include "opentelemetry/exporters/otlp/otlp_http_log_record_exporter_factory.h"
#include <iostream> #include <vector> #include <fstream> #include <list> #include <memory> #include <thread> #include <iostream> #include <string>
namespace nostd = opentelemetry::nostd; namespace otlp = opentelemetry::exporter::otlp; namespace resource = opentelemetry::sdk::resource;
namespace trace_sdk = opentelemetry::sdk::trace;
namespace metrics_sdk = opentelemetry::sdk::metrics;
namespace logs_sdk = opentelemetry::sdk::logs;
{ // Class definition for context propagation
otlp::OtlpHttpMetricExporterOptions options;
std::string version{ "1.0.1" };
std::string name{ "app_cpp" };
std::string schema{ "https://opentelemetry.io/schemas/1.2.0" };
template <typename T>
class HttpTextMapCarrier : public opentelemetry::context::propagation::TextMapCarrier
{
public:
HttpTextMapCarrier<T>(T &headers) : headers_(headers) {}
HttpTextMapCarrier() = default;
virtual nostd::string_view Get(nostd::string_view key) const noexcept override
{
std::string key_to_compare = key.data();
// Header's first letter seems to be automatically capitaliazed by our test http-server, so
// compare accordingly.
if (key == opentelemetry::trace::propagation::kTraceParent)
{
key_to_compare = "Traceparent";
}
else if (key == opentelemetry::trace::propagation::kTraceState)
{
key_to_compare = "Tracestate";
}
auto it = headers_.find(key_to_compare);
if (it != headers_.end())
{
return it->second;
}
return "";
}
virtual void Set(nostd::string_view key, nostd::string_view value) noexcept override
{
headers_.insert(std::pair<std::string, std::string>(std::string(key), std::string(value)));
}
T headers_;
};
// ===== GENERAL SETUP =====
void initTracer()
{
otlp::OtlpHttpExporterOptions traceOptions;
traceOptions.url = std::string(std::getenv("DT_API_URL")) + "/v1/traces";
traceOptions.content_type = otlp::HttpRequestContentType::kBinary;
traceOptions.http_headers.insert(
std::make_pair<const std::string, std::string>("Authorization", std::getenv("DT_API_TOKEN")));
resource::ResourceAttributes resource_attributes = {{"service.name", name},
{"service.version", version}};
resource::ResourceAttributes dt_resource_attributes;
try
{
for (string name : {"dt_metadata_e617c525669e072eebe3d0f08212e8f2.properties",
"/var/lib/astromkey/enrichment/dt_metadata.properties",
"/var/lib/astromkey/enrichment/dt_host_metadata.properties"})
{
string file_path;
ifstream dt_file;
dt_file.open(name);
if (dt_file.is_open())
{
string dt_metadata;
ifstream dt_properties;
while (getline(dt_file, file_path))
{
dt_properties.open(file_path);
if (dt_properties.is_open())
{
while (getline(dt_properties, dt_metadata))
{
dt_resource_attributes.SetAttribute(
dt_metadata.substr(0, dt_metadata.find("=")),
dt_metadata.substr(dt_metadata.find("=") + 1)
);
}
dt_properties.close();
}
}
dt_file.close();
}
}
}
catch (...) {}
auto dt_resource = resource::Resource::Create(dt_resource_attributes);
auto resource = resource::Resource::Create(resource_attributes);
auto merged_resource = dt_resource.Merge(resource);
auto exporter = otlp::OtlpHttpExporterFactory::Create(traceOptions);
auto processor = trace_sdk::SimpleSpanProcessorFactory::Create(std::move(exporter));
std::vector<std::unique_ptr<trace_sdk::SpanProcessor>> processors;
processors.push_back(std::move(processor));
auto context = trace_sdk::TracerContextFactory::Create(std::move(processors), merged_resource);
std::shared_ptr<opentelemetry::trace::TracerProvider> provider = opentelemetry::sdk::trace::TracerProviderFactory::Create(std::move(context));
// Set the global trace provider
opentelemetry::trace::Provider::SetTracerProvider(provider);
// set global propagator
opentelemetry::context::propagation::GlobalTextMapPropagator::SetGlobalPropagator(
opentelemetry::nostd::shared_ptr<opentelemetry::context::propagation::TextMapPropagator>(
new opentelemetry::trace::propagation::HttpTraceContext()));
}
// ===== METRIC SETUP =====
void initMeter() {
resource::ResourceAttributes resource_attributes = {{"service.name", name},
{"service.version", version}};
otlp::OtlpHttpMetricExporterOptions otlpOptions;
auto resource = resource::Resource::Create(resource_attributes);
otlpOptions.url = std::string(std::getenv("DT_API_URL")) + "/v1/metrics";
otlpOptions.aggregation_temporality = otlp::PreferredAggregationTemporality::kDelta;
otlpOptions.content_type = otlp::HttpRequestContentType::kBinary;
otlpOptions.http_headers.insert(std::make_pair<const std::string, std::string>("Authorization", std::getenv("DT_API_TOKEN")));
//This creates the exporter with the options we have defined above.
auto exporter = otlp::OtlpHttpMetricExporterFactory::Create(otlpOptions);
metrics_sdk::PeriodicExportingMetricReaderOptions options;
options.export_interval_millis = std::chrono::milliseconds(1000);
options.export_timeout_millis = std::chrono::milliseconds(500);
std::unique_ptr<metrics_sdk::MetricReader> reader{new metrics_sdk::PeriodicExportingMetricReader(std::move(exporter), options) };
auto context = metrics_sdk::MeterContextFactory::Create(opentelemetry::sdk::metrics::ViewRegistryFactory::Create(), resource);
context->AddMetricReader(std::move(reader));
auto u_provider = metrics_sdk::MeterProviderFactory::Create(std::move(context));
std::shared_ptr<opentelemetry::metrics::MeterProvider> provider(std::move(u_provider));
metrics_api::Provider::SetMeterProvider(provider);
}
// ===== LOG SETUP =====
void initLogger() {
resource::ResourceAttributes resource_attributes = {{"service.name", name},
{"service.version", version}};
auto resource = resource::Resource::Create(resource_attributes);
otlp::OtlpHttpLogRecordExporterOptions loggerOptions;
loggerOptions.url = std::string(std::getenv("DT_API_URL")) + "/v1/logs";
loggerOptions.http_headers.insert(std::make_pair<const std::string, std::string>("Authorization", std::getenv("DT_API_TOKEN")));
loggerOptions.content_type = opentelemetry::exporter::otlp::HttpRequestContentType::kBinary;
auto exporter = otlp::OtlpHttpLogRecordExporterFactory::Create(loggerOptions);
auto processor = logs_sdk::SimpleLogRecordProcessorFactory::Create(std::move(exporter));
std::vector<std::unique_ptr<logs_sdk::LogRecordProcessor>> processors;
processors.push_back(std::move(processor));
auto context = logs_sdk::LoggerContextFactory::Create(std::move(processors), resource);
std::shared_ptr<logs_api::LoggerProvider> provider = logs_sdk::LoggerProviderFactory::Create(std::move(context));
opentelemetry::logs::Provider::SetLoggerProvider(provider);
}
nostd::shared_ptr<opentelemetry::logs::Logger> get_logger(std::string scope){
// TODO: add your log provider here
return logger;
}
opentelemetry::nostd::shared_ptr<opentelemetry::trace::Tracer> get_tracer(std::string tracer_name)
{
// TODO: add your trace provider here
return tracer;
}
nostd::unique_ptr<opentelemetry::metrics::Counter<uint64_t>> initIntCounter()
{
// TODO: add your custom metrics here
return request_counter;
}
} |
Расширение данных Ключ-АСТРОМ
Операции чтения файлов, анализирующие файлы dt_metadata в примере кода, пытаются прочитать файлы данных ЕдиногоАгента, чтобы обогатить запрос OTLP и гарантировать, что вся соответствующая информация о топологии доступна в Ключ-АСТРОМ.
3. Настройте DT_API_URL и DT_API_TOKEN для URL-адреса Ключ-АСТРОМ и токена доступа в otel.h.
Инструментирование своего приложения
Чтобы использовать OpenTelemetry, вам сначала необходимо выполнить следующие два шага:
1. Добавьте необходимые заголовочные файлы в свой код.
Чтобы добавить заголовочные файлы otel.h, включите их везде, где вы хотите использовать OpenTelemetry.
| #include "otel.h" |
2. Инициализируйте OpenTelemetry.
Для инициализации initOpenTelemetry используйте функцию otel.h и вызовите ее в самом начале кода запуска вашего приложения.
Добавление трассировки
1. Получите ссылку на поставщика трассировки.
| auto provider = opentelemetry::trace::Provider::GetTracerProvider(); |
2 Получите объект трассировки.
| // In our case the GetTraces method takes the tracer name and returns the tracer provider
auto tracer = provider->GetTracer(tracer_name); |
3. Теперь с помощью tracer, мы можем запускать новые интервалы и устанавливать их для текущей области выполнения.
| StartSpanOptions options;
options.kind = SpanKind::kServer; auto span = tracer->StartSpan("Call to /myendpoint", { { "http.method", "GET" }, { "net.protocol.version", "1.1" } }, options); auto scope = tracer->WithActiveSpan(span); // TODO: Your code goes here span->End(); |
В приведенном выше коде мы:
- Создали новый диапазон и назвали его «Call to /myendpoint».
- Добавили два атрибута, следуя семантическому соглашению об именовании, специфичные для действия этого диапазона: информацию о методе HTTP и версии.
- Добавили
TODOвместо конечной бизнес-логики - Вызвали метод span
End()для завершения span.
Сбор метрик
1. Получите ссылку на поставщика метрик.
| auto provider = metrics_api::Provider::GetMeterProvider(); |
2. Получите объект метрик.
| nostd::shared_ptr<metrics_api::Meter> meter = provider->GetMeter("my-meter", "1.0.1"); |
3. С помощью meter мы теперь можем создавать отдельные инструменты, например, метрики.
| auto request_counter = meter->CreateUInt64Counter("request_counter"); |
4. Теперь мы можем вызвать метод Add() для записи новых значений с помощью метрики request_counter и сохранения дополнительных атрибутов (например, action.type).
| std::map<std::string, std::string> labels = { {"action.type", "create"} };
auto labelkv = opentelemetry::common::KeyValueIterableView<decltype(labels)>{ labels }; request_counter->Add(1, labelkv); |
Подключение логов
1. Получите ссылку на поставщика логов.
| auto provider = logs_api::Provider::GetLoggerProvider(); |
2. Вызовите метод поставщика GetLogger() для получения экземпляра логов.
| auto logger = provider->GetLogger("scope_name", "", OPENTELEMETRY_SDK_VERSION); |
3. Вызовите любой из доступных методов ведения логов, чтобы записать оператор логов. В следующем примере регистрируется оператор отладки.
| logger->Debug("My debug statement here"); |
Обеспечение распространения контекста (необязательно)
Распространение контекста особенно важно, когда задействованы сетевые вызовы (например, REST).
В следующих примерах мы предполагаем, что мы обрабатываем распространение контекста с помощью стандартных заголовков контекста трассировки W3C, а также получаем и устанавливаем заголовки HTTP с помощью объекта OpenTelemetry http_client::Headers.
Для этой цели мы используем экземпляр класса HttpTextMapCarrier, который мы определили во время настройки и который основан на классе OpenTelemetry TextMapCarrier.
Извлечение контекста при получении запроса
Чтобы извлечь информацию о существующем контексте, мы вызываем метод Extract глобального propagator singleton и передаем ему экземпляр HttpTextMapCarrier, а также текущий контекст. Это возвращает новый объект контекста (new_context), который позволяет нам продолжить предыдущую трассировку с помощью наших интервалов.
| StartSpanOptions options;
options.kind = SpanKind::kServer; std::string span_name = request.uri; // extract context from http header std::map<std::string, std::string> &request_headers = const_cast<std::map<std::string, std::string> &>(request.headers); const HttpTextMapCarrier<std::map<std::string, std::string>> carrier(request_headers); auto prop = context::propagation::GlobalTextMapPropagator::GetGlobalPropagator(); auto current_ctx = context::RuntimeContext::GetCurrent(); auto new_context = prop->Extract(carrier, current_ctx); options.parent = GetSpan(new_context)->GetContext(); auto span = get_tracer("manual-server") ->StartSpan("my-server-span", { //TODO Replace with the name of your span {"my-server-key-1", "my-server-value-1"} //TODO Add attributes }, options); auto scope = get_tracer("http_server")->WithActiveSpan(span); for (auto &kv : request.headers) { span->SetAttribute("http.header." + std::string(kv.first.data()), kv.second); } span->AddEvent("Processing request"); response.headers[HTTP_SERVER_NS::CONTENT_TYPE] = HTTP_SERVER_NS::CONTENT_TYPE_TEXT; response.body = doCall(); span->End(); |
Внедрение контекста при отправке запросов
Для внедрения информации о текущем контексте в исходящий запрос мы вызываем метод Inject глобального propagator singleton и передаем ему экземпляр HttpTextMapCarrier, а также текущий контекст. Это добавляет к экземпляру carrier соответствующие заголовки, которые затем используются на этапе текста в нашем HTTP-запросе.
| auto http_client = http_client::HttpClientFactory::CreateSync();
std::string url = std::getenv("URL"); // TODO set URL you want to call
StartSpanOptions options; options.kind = SpanKind::kClient; opentelemetry::ext::http::common::UrlParser url_parser(url); std::string span_name = url_parser.path_; auto span = get_tracer("http-client")->StartSpan(span_name, {{opentelemetry::semconv::url::kUrlFull, url_parser.url_},
{opentelemetry::semconv::url::kUrlScheme, url_parser.scheme_},
{opentelemetry::semconv::http::kHttpRequestMethod, "GET"}},
options);
auto scope = get_tracer("http-client")->WithActiveSpan(span); // inject current context into http header auto current_ctx = context::RuntimeContext::GetCurrent(); HttpTextMapCarrier<http_client::Headers> carrier; auto prop = context::propagation::GlobalTextMapPropagator::GetGlobalPropagator(); prop->Inject(carrier, current_ctx);
http_client::Result result = http_client->GetNoSsl(url, carrier.headers_); //your code goes here //then end span |
Настройка сбора данных в соответствии с требованиями конфиденциальности (необязательно)
Хотя Ключ-АСТРОМ автоматически собирает все атрибуты OpenTelemetry, в веб-интерфейсе Ключ-АСТРОМ сохраняются и отображаются только значения атрибутов, указанные в списке разрешенных. Это предотвращает случайное сохранение персональных данных, позволяя вам соблюдать требования к конфиденциальности и контролировать объем хранимых данных мониторинга.
Чтобы просматривать пользовательские атрибуты, необходимо сначала разрешить их использование в веб-интерфейсе Ключ-АСТРОМ.
Проверка загрузки данных в Ключ-АСТРОМ
После завершения инструментирования вашего приложения выполните несколько тестовых действий для создания и отправки демонстрационных трассировок, метрик и логов, а также проверьте, что они были правильно загружены в Ключ-АСТРОМ.
Чтобы сделать это для трассировок, перейдите в раздел Трассировки и выберите вкладку Распределенные трассировки. Если вы используете ЕдиныйАгент, выберите PurePaths .
Для просмотра метрик и логов перейдите в раздел Метрики или Логов или Логи и события.