diff --git a/CHANGELOG.md b/CHANGELOG.md index 98cb7cea63..c6b0c4d967 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Increment the: ## [Unreleased] +* [CODE HEALTH] clean up nostd::variant access in otlp exporters + [#3973](https://github.com/open-telemetry/opentelemetry-cpp/pull/3973) + * [SDK] Add `TracerProvider::UpdateTracerConfigurator()` and example [#4065](https://github.com/open-telemetry/opentelemetry-cpp/issues/4065) diff --git a/CMakeLists.txt b/CMakeLists.txt index 16ccd110cf..ef883466b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -529,6 +529,7 @@ message(STATUS "---------------------------------------------") message(STATUS "WITH_NLOHMANN_JSON: ${USE_NLOHMANN_JSON}") message(STATUS "WITH_CURL_LOGGING: ${WITH_CURL_LOGGING}") message(STATUS "WITH_OTLP_HTTP_COMPRESSION: ${WITH_OTLP_HTTP_COMPRESSION}") +message(STATUS "WITH_OTLP_UTF8_VALIDITY: ${WITH_OTLP_UTF8_VALIDITY}") message(STATUS "---------------------------------------------") message(STATUS "examples and test options") message(STATUS "---------------------------------------------") diff --git a/exporters/otlp/BUILD b/exporters/otlp/BUILD index 60311dc949..d6c369b9e9 100644 --- a/exporters/otlp/BUILD +++ b/exporters/otlp/BUILD @@ -633,6 +633,20 @@ cc_library( ], ) +cc_test( + name = "otlp_populate_attribute_utils_test", + srcs = ["test/otlp_populate_attribute_utils_test.cc"], + tags = [ + "otlp", + "test", + ], + deps = [ + ":otlp_recordable", + "//sdk/src/metrics", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "otlp_recordable_test", srcs = ["test/otlp_recordable_test.cc"], diff --git a/exporters/otlp/CMakeLists.txt b/exporters/otlp/CMakeLists.txt index 1d2852dc73..aac92e075c 100644 --- a/exporters/otlp/CMakeLists.txt +++ b/exporters/otlp/CMakeLists.txt @@ -871,6 +871,21 @@ if(WITH_OTLP_FILE) endif() if(BUILD_TESTING) + add_executable(otlp_populate_attribute_utils_test + test/otlp_populate_attribute_utils_test.cc) + target_link_libraries( + otlp_populate_attribute_utils_test ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_otlp_recordable + protobuf::libprotobuf) + gtest_add_tests( + TARGET otlp_populate_attribute_utils_test + TEST_PREFIX exporter.otlp. + TEST_LIST otlp_populate_attribute_utils_test) + if(WITH_OTLP_UTF8_VALIDITY AND TARGET utf8_range::utf8_validity) + target_compile_definitions(otlp_populate_attribute_utils_test + PRIVATE ENABLE_OTLP_UTF8_VALIDITY) + endif() + add_executable(otlp_recordable_test test/otlp_recordable_test.cc) target_link_libraries( otlp_recordable_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} diff --git a/exporters/otlp/src/otlp_http_client.cc b/exporters/otlp/src/otlp_http_client.cc index 7c515b6332..a927ea5f57 100644 --- a/exporters/otlp/src/otlp_http_client.cc +++ b/exporters/otlp/src/otlp_http_client.cc @@ -749,13 +749,21 @@ sdk::common::ExportResult OtlpHttpClient::Export( std::function &&result_callback, std::size_t max_running_requests) noexcept { - auto session = createSession(message, std::move(result_callback)); - if (opentelemetry::nostd::holds_alternative(session)) { - return opentelemetry::nostd::get(session); - } + auto created = createSession(message, std::move(result_callback)); + if (const auto *result = opentelemetry::nostd::get_if(&created)) + { + return *result; + } - addSession(std::move(opentelemetry::nostd::get(session))); + // created contains a valid HttpSessionData object + auto *session_data = opentelemetry::nostd::get_if(&created); + if (session_data == nullptr) + { + return sdk::common::ExportResult::kFailure; + } + addSession(std::move(*session_data)); + } // created (and the moved-from HttpSessionData) released here // Wait for the response to be received if (options_.console_debug) diff --git a/exporters/otlp/src/otlp_metric_utils.cc b/exporters/otlp/src/otlp_metric_utils.cc index e57359ee48..c62ee55a8d 100644 --- a/exporters/otlp/src/otlp_metric_utils.cc +++ b/exporters/otlp/src/otlp_metric_utils.cc @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#include +#include #include #include #include @@ -11,6 +11,7 @@ #include "opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h" #include "opentelemetry/exporters/otlp/otlp_preferred_temporality.h" #include "opentelemetry/nostd/variant.h" +#include "opentelemetry/sdk/common/global_log_handler.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #include "opentelemetry/sdk/metrics/data/circular_buffer.h" #include "opentelemetry/sdk/metrics/data/metric_data.h" @@ -36,6 +37,18 @@ namespace otlp { namespace metric_sdk = opentelemetry::sdk::metrics; +namespace +{ + +struct NumberDataPointValueSetter +{ + proto::metrics::v1::NumberDataPoint *point; + void operator()(int64_t v) const noexcept { point->set_as_int(v); } + void operator()(double v) const noexcept { point->set_as_double(v); } +}; + +} // namespace + proto::metrics::v1::AggregationTemporality OtlpMetricUtils::GetProtoAggregationTemporality( const opentelemetry::sdk::metrics::AggregationTemporality &aggregation_temporality) noexcept { @@ -87,23 +100,24 @@ void OtlpMetricUtils::ConvertSumMetric(const metric_sdk::MetricData &metric_data (metric_data.instrument_descriptor.type_ == metric_sdk::InstrumentType::kObservableCounter)); auto start_ts = metric_data.start_ts.time_since_epoch().count(); auto ts = metric_data.end_ts.time_since_epoch().count(); - for (auto &point_data_with_attributes : metric_data.point_data_attr_) + for (const auto &point_data_with_attributes : metric_data.point_data_attr_) { proto::metrics::v1::NumberDataPoint *proto_sum_point_data = sum->add_data_points(); proto_sum_point_data->set_start_time_unix_nano(start_ts); proto_sum_point_data->set_time_unix_nano(ts); - auto sum_data = nostd::get(point_data_with_attributes.point_data); - - if ((nostd::holds_alternative(sum_data.value_))) - { - proto_sum_point_data->set_as_int(nostd::get(sum_data.value_)); - } - else + const auto *maybe_sum_data = + nostd::get_if(&point_data_with_attributes.point_data); + if (maybe_sum_data == nullptr) { - proto_sum_point_data->set_as_double(nostd::get(sum_data.value_)); + OTEL_INTERNAL_LOG_ERROR( + "[OTLP Metrics] ConvertSumMetric: point data type mismatch, skipping data point"); + continue; } + const auto &sum_data = *maybe_sum_data; + + nostd::visit(NumberDataPointValueSetter{proto_sum_point_data}, sum_data.value_); // set attributes - for (auto &kv_attr : point_data_with_attributes.attributes) + for (const auto &kv_attr : point_data_with_attributes.attributes) { OtlpPopulateAttributeUtils::PopulateAttribute(proto_sum_point_data->add_attributes(), kv_attr.first, kv_attr.second, false); @@ -119,63 +133,48 @@ void OtlpMetricUtils::ConvertHistogramMetric( GetProtoAggregationTemporality(metric_data.aggregation_temporality)); auto start_ts = metric_data.start_ts.time_since_epoch().count(); auto ts = metric_data.end_ts.time_since_epoch().count(); - for (auto &point_data_with_attributes : metric_data.point_data_attr_) + for (const auto &point_data_with_attributes : metric_data.point_data_attr_) { proto::metrics::v1::HistogramDataPoint *proto_histogram_point_data = histogram->add_data_points(); proto_histogram_point_data->set_start_time_unix_nano(start_ts); proto_histogram_point_data->set_time_unix_nano(ts); - auto histogram_data = - nostd::get(point_data_with_attributes.point_data); - // sum - if ((nostd::holds_alternative(histogram_data.sum_))) + const auto *maybe_histogram_data = + nostd::get_if(&point_data_with_attributes.point_data); + if (maybe_histogram_data == nullptr) { - // Use static_cast to avoid C4244 in MSVC - proto_histogram_point_data->set_sum( - static_cast(nostd::get(histogram_data.sum_))); - } - else - { - proto_histogram_point_data->set_sum(nostd::get(histogram_data.sum_)); + OTEL_INTERNAL_LOG_ERROR( + "[OTLP Metrics] ConvertHistogramMetric: point data type mismatch, skipping data point"); + continue; } + const auto &histogram_data = *maybe_histogram_data; + // sum + // Use static_cast to avoid C4244 in MSVC + proto_histogram_point_data->set_sum( + nostd::visit([](auto v) { return static_cast(v); }, histogram_data.sum_)); // count proto_histogram_point_data->set_count(histogram_data.count_); if (histogram_data.record_min_max_) { - if (nostd::holds_alternative(histogram_data.min_)) - { - // Use static_cast to avoid C4244 in MSVC - proto_histogram_point_data->set_min( - static_cast(nostd::get(histogram_data.min_))); - } - else - { - proto_histogram_point_data->set_min(nostd::get(histogram_data.min_)); - } - if (nostd::holds_alternative(histogram_data.max_)) - { - // Use static_cast to avoid C4244 in MSVC - proto_histogram_point_data->set_max( - static_cast(nostd::get(histogram_data.max_))); - } - else - { - proto_histogram_point_data->set_max(nostd::get(histogram_data.max_)); - } + // Use static_cast to avoid C4244 in MSVC + proto_histogram_point_data->set_min( + nostd::visit([](auto v) { return static_cast(v); }, histogram_data.min_)); + proto_histogram_point_data->set_max( + nostd::visit([](auto v) { return static_cast(v); }, histogram_data.max_)); } // buckets - for (auto bound : histogram_data.boundaries_) + for (const auto &bound : histogram_data.boundaries_) { proto_histogram_point_data->add_explicit_bounds(bound); } // bucket counts - for (auto bucket_value : histogram_data.counts_) + for (const auto &bucket_value : histogram_data.counts_) { proto_histogram_point_data->add_bucket_counts(bucket_value); } // attributes - for (auto &kv_attr : point_data_with_attributes.attributes) + for (const auto &kv_attr : point_data_with_attributes.attributes) { OtlpPopulateAttributeUtils::PopulateAttribute(proto_histogram_point_data->add_attributes(), kv_attr.first, kv_attr.second, false); @@ -191,18 +190,23 @@ void OtlpMetricUtils::ConvertExponentialHistogramMetric( GetProtoAggregationTemporality(metric_data.aggregation_temporality)); auto start_ts = metric_data.start_ts.time_since_epoch().count(); auto ts = metric_data.end_ts.time_since_epoch().count(); - for (auto &point_data_with_attributes : metric_data.point_data_attr_) + for (const auto &point_data_with_attributes : metric_data.point_data_attr_) { proto::metrics::v1::ExponentialHistogramDataPoint *proto_histogram_point_data = histogram->add_data_points(); proto_histogram_point_data->set_start_time_unix_nano(start_ts); proto_histogram_point_data->set_time_unix_nano(ts); - auto histogram_data = nostd::get( - point_data_with_attributes.point_data); - if (histogram_data.positive_buckets_ == nullptr && histogram_data.negative_buckets_ == nullptr) + const auto *maybe_histogram_data = + nostd::get_if( + &point_data_with_attributes.point_data); + if (maybe_histogram_data == nullptr) { + OTEL_INTERNAL_LOG_ERROR( + "[OTLP Metrics] ConvertExponentialHistogramMetric: point data type mismatch, skipping " + "data point"); continue; } + const auto &histogram_data = *maybe_histogram_data; // sum proto_histogram_point_data->set_sum(histogram_data.sum_); proto_histogram_point_data->set_count(histogram_data.count_); @@ -212,7 +216,7 @@ void OtlpMetricUtils::ConvertExponentialHistogramMetric( proto_histogram_point_data->set_max(histogram_data.max_); } // negative buckets - if (!histogram_data.negative_buckets_->Empty()) + if (histogram_data.negative_buckets_ != nullptr && !histogram_data.negative_buckets_->Empty()) { auto negative_buckets = proto_histogram_point_data->mutable_negative(); negative_buckets->set_offset(histogram_data.negative_buckets_->StartIndex()); @@ -224,7 +228,7 @@ void OtlpMetricUtils::ConvertExponentialHistogramMetric( } } // positive buckets - if (!histogram_data.positive_buckets_->Empty()) + if (histogram_data.positive_buckets_ != nullptr && !histogram_data.positive_buckets_->Empty()) { auto positive_buckets = proto_histogram_point_data->mutable_positive(); positive_buckets->set_offset(histogram_data.positive_buckets_->StartIndex()); @@ -239,7 +243,7 @@ void OtlpMetricUtils::ConvertExponentialHistogramMetric( proto_histogram_point_data->set_zero_count(histogram_data.zero_count_); // attributes - for (auto &kv_attr : point_data_with_attributes.attributes) + for (const auto &kv_attr : point_data_with_attributes.attributes) { OtlpPopulateAttributeUtils::PopulateAttribute(proto_histogram_point_data->add_attributes(), kv_attr.first, kv_attr.second, false); @@ -252,24 +256,24 @@ void OtlpMetricUtils::ConvertGaugeMetric(const opentelemetry::sdk::metrics::Metr { auto start_ts = metric_data.start_ts.time_since_epoch().count(); auto ts = metric_data.end_ts.time_since_epoch().count(); - for (auto &point_data_with_attributes : metric_data.point_data_attr_) + for (const auto &point_data_with_attributes : metric_data.point_data_attr_) { proto::metrics::v1::NumberDataPoint *proto_gauge_point_data = gauge->add_data_points(); proto_gauge_point_data->set_start_time_unix_nano(start_ts); proto_gauge_point_data->set_time_unix_nano(ts); - auto gauge_data = - nostd::get(point_data_with_attributes.point_data); - - if ((nostd::holds_alternative(gauge_data.value_))) + const auto *maybe_gauge_data = + nostd::get_if(&point_data_with_attributes.point_data); + if (maybe_gauge_data == nullptr) { - proto_gauge_point_data->set_as_int(nostd::get(gauge_data.value_)); - } - else - { - proto_gauge_point_data->set_as_double(nostd::get(gauge_data.value_)); + OTEL_INTERNAL_LOG_ERROR( + "[OTLP Metrics] ConvertGaugeMetric: point data type mismatch, skipping data point"); + continue; } + const auto &gauge_data = *maybe_gauge_data; + + nostd::visit(NumberDataPointValueSetter{proto_gauge_point_data}, gauge_data.value_); // set attributes - for (auto &kv_attr : point_data_with_attributes.attributes) + for (const auto &kv_attr : point_data_with_attributes.attributes) { OtlpPopulateAttributeUtils::PopulateAttribute(proto_gauge_point_data->add_attributes(), kv_attr.first, kv_attr.second, false); @@ -321,6 +325,8 @@ void OtlpMetricUtils::PopulateResourceMetrics( { if (scope_metrics.scope_ == nullptr) { + OTEL_INTERNAL_LOG_ERROR( + "[OTLP Metrics] PopulateResourceMetrics: scope is null, skipping scope metrics"); continue; } auto scope_lib_metrics = resource_metrics->add_scope_metrics(); diff --git a/exporters/otlp/src/otlp_populate_attribute_utils.cc b/exporters/otlp/src/otlp_populate_attribute_utils.cc index 14259773db..fc140115ec 100644 --- a/exporters/otlp/src/otlp_populate_attribute_utils.cc +++ b/exporters/otlp/src/otlp_populate_attribute_utils.cc @@ -5,25 +5,30 @@ # include #endif -#include -#include +#include +#include #include #include +#include #include #include +#include + #include "opentelemetry/common/attribute_value.h" #include "opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h" #include "opentelemetry/nostd/span.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/nostd/variant.h" #include "opentelemetry/sdk/common/attribute_utils.h" +#include "opentelemetry/sdk/common/global_log_handler.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/version.h" // clang-format off #include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" // IWYU pragma: keep +// IWYU pragma: no_include "net/proto2/public/repeated_field.h" #include "opentelemetry/proto/common/v1/common.pb.h" #include "opentelemetry/proto/resource/v1/resource.pb.h" #include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" // IWYU pragma: keep @@ -35,14 +40,15 @@ namespace exporter namespace otlp { +namespace +{ + // // See `attribute_value.h` for details. // const int kAttributeValueSize = 16; const int kOwnedAttributeValueSize = 15; -namespace -{ // Per OpenTelemetry spec, uint64_t attribute values exceeding INT64_MAX must be // encoded as a decimal string rather than wrapping to a negative int64 via narrowing. // https://opentelemetry.io/docs/specs/otel/common/attribute-type-mapping/#integer-values @@ -57,307 +63,331 @@ inline void SetUint64Value(opentelemetry::proto::common::v1::AnyValue *proto_val proto_value->set_string_value(std::to_string(val)); } } -} // namespace -void OtlpPopulateAttributeUtils::PopulateAnyValue( - opentelemetry::proto::common::v1::AnyValue *proto_value, - const opentelemetry::common::AttributeValue &value, - bool allow_bytes) noexcept -{ - if (nullptr == proto_value) - { - return; - } +template +struct IsSupportedAttributeValue : std::false_type +{}; - // Assert size of variant to ensure that this method gets updated if the variant - // definition changes - static_assert( - nostd::variant_size::value == kAttributeValueSize, - "AttributeValue contains unknown type"); +struct AttributeValueVisitor +{ + opentelemetry::proto::common::v1::AnyValue *proto_value_; + bool allow_bytes_; - if (nostd::holds_alternative(value)) - { - proto_value->set_bool_value(nostd::get(value)); - } - else if (nostd::holds_alternative(value)) - { - proto_value->set_int_value(nostd::get(value)); - } - else if (nostd::holds_alternative(value)) - { - proto_value->set_int_value(nostd::get(value)); - } - else if (nostd::holds_alternative(value)) + template + void operator()(T &&) const { - proto_value->set_int_value(nostd::get(value)); + static_assert( + IsSupportedAttributeValue::value, + "AttributeValueVisitor: Value type in opentelemetry::common::AttributeValue does not have " + "an overload operator implemented in this visitor OR implicit conversion attempted!"); } - else if (nostd::holds_alternative(value)) - { - SetUint64Value(proto_value, nostd::get(value)); - } - else if (nostd::holds_alternative(value)) - { - proto_value->set_double_value(nostd::get(value)); - } - else if (nostd::holds_alternative(value)) + + void operator()(bool bool_value) const { proto_value_->set_bool_value(bool_value); } + void operator()(int32_t int32_value) const { proto_value_->set_int_value(int32_value); } + void operator()(int64_t int64_value) const { proto_value_->set_int_value(int64_value); } + void operator()(uint32_t uint32_value) const { proto_value_->set_int_value(uint32_value); } + void operator()(uint64_t uint64_value) const { SetUint64Value(proto_value_, uint64_value); } + void operator()(double double_value) const { proto_value_->set_double_value(double_value); } + void operator()(const char *str_value) const { - const char *str_value = nostd::get(value); #if defined(ENABLE_OTLP_UTF8_VALIDITY) if (utf8_range::IsStructurallyValid(str_value)) { - proto_value->set_string_value(str_value); + proto_value_->set_string_value(str_value); } else { - proto_value->set_bytes_value(str_value, strlen(str_value)); + proto_value_->set_bytes_value(str_value, std::strlen(str_value)); } #else - proto_value->set_string_value(str_value); + proto_value_->set_string_value(str_value); #endif } - else if (nostd::holds_alternative(value)) + void operator()(nostd::string_view str_value) const { - nostd::string_view str_value = nostd::get(value); #if defined(ENABLE_OTLP_UTF8_VALIDITY) if (utf8_range::IsStructurallyValid({str_value.data(), str_value.size()})) { - proto_value->set_string_value(str_value.data(), str_value.size()); + proto_value_->set_string_value(str_value.data(), str_value.size()); } else { - proto_value->set_bytes_value(str_value.data(), str_value.size()); + proto_value_->set_bytes_value(str_value.data(), str_value.size()); } #else - proto_value->set_string_value(str_value.data(), str_value.size()); + proto_value_->set_string_value(str_value.data(), str_value.size()); #endif } - else if (nostd::holds_alternative>(value)) + void operator()(nostd::span span_bool_value) const { - if (allow_bytes) - { - proto_value->set_bytes_value( - reinterpret_cast(nostd::get>(value).data()), - nostd::get>(value).size()); - } - else - { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) - { - array_value->add_values()->set_int_value(val); - } - } - } - else if (nostd::holds_alternative>(value)) - { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(span_bool_value.size())); + for (const auto &val : span_bool_value) { array_value->add_values()->set_bool_value(val); } } - else if (nostd::holds_alternative>(value)) + void operator()(nostd::span span_int32_value) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(span_int32_value.size())); + for (const auto &val : span_int32_value) { array_value->add_values()->set_int_value(val); } } - else if (nostd::holds_alternative>(value)) + void operator()(nostd::span span_int64_value) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(span_int64_value.size())); + for (const auto &val : span_int64_value) { array_value->add_values()->set_int_value(val); } } - else if (nostd::holds_alternative>(value)) + void operator()(nostd::span span_uint32_value) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(span_uint32_value.size())); + for (const auto &uint32_value : span_uint32_value) { - array_value->add_values()->set_int_value(val); + array_value->add_values()->set_int_value(uint32_value); } } - else if (nostd::holds_alternative>(value)) + void operator()(nostd::span span_double_value) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(span_double_value.size())); + for (const auto &double_value : span_double_value) { - SetUint64Value(array_value->add_values(), val); + array_value->add_values()->set_double_value(double_value); } } - else if (nostd::holds_alternative>(value)) + void operator()(nostd::span span_string_value) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) - { - array_value->add_values()->set_double_value(val); - } - } - else if (nostd::holds_alternative>(value)) - { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(span_string_value.size())); + for (const auto &string_value : span_string_value) { #if defined(ENABLE_OTLP_UTF8_VALIDITY) - if (utf8_range::IsStructurallyValid({val.data(), val.size()})) + if (utf8_range::IsStructurallyValid({string_value.data(), string_value.size()})) { - array_value->add_values()->set_string_value(val.data(), val.size()); + array_value->add_values()->set_string_value(string_value.data(), string_value.size()); } else { - array_value->add_values()->set_bytes_value(val.data(), val.size()); + array_value->add_values()->set_bytes_value(string_value.data(), string_value.size()); } #else - array_value->add_values()->set_string_value(val.data(), val.size()); + array_value->add_values()->set_string_value(string_value.data(), string_value.size()); #endif } } -} - -void OtlpPopulateAttributeUtils::PopulateAnyValue( - opentelemetry::proto::common::v1::AnyValue *proto_value, - const opentelemetry::sdk::common::OwnedAttributeValue &value, - bool allow_bytes) noexcept -{ - if (nullptr == proto_value) - { - return; - } - - // Assert size of variant to ensure that this method gets updated if the variant - // definition changes - static_assert(nostd::variant_size::value == - kOwnedAttributeValueSize, - "OwnedAttributeValue contains unknown type"); - - if (nostd::holds_alternative(value)) - { - proto_value->set_bool_value(nostd::get(value)); - } - else if (nostd::holds_alternative(value)) - { - proto_value->set_int_value(nostd::get(value)); - } - else if (nostd::holds_alternative(value)) - { - proto_value->set_int_value(nostd::get(value)); - } - else if (nostd::holds_alternative(value)) + void operator()(nostd::span span_uint64_value) const { - proto_value->set_int_value(nostd::get(value)); - } - else if (nostd::holds_alternative(value)) - { - SetUint64Value(proto_value, nostd::get(value)); - } - else if (nostd::holds_alternative(value)) - { - proto_value->set_double_value(nostd::get(value)); + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(span_uint64_value.size())); + for (const auto &uint64_value : span_uint64_value) + { + SetUint64Value(array_value->add_values(), uint64_value); + } } - else if (nostd::holds_alternative>(value)) + void operator()(nostd::span span_uint8_value) const { - if (allow_bytes) + if (allow_bytes_) { - const std::vector &byte_array = nostd::get>(value); - proto_value->set_bytes_value(reinterpret_cast(byte_array.data()), - byte_array.size()); + proto_value_->set_bytes_value(reinterpret_cast(span_uint8_value.data()), + span_uint8_value.size()); } else { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = + proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(span_uint8_value.size())); + for (const auto &uint8_value : span_uint8_value) { - array_value->add_values()->set_int_value(val); + array_value->add_values()->set_int_value(uint8_value); } } } - else if (nostd::holds_alternative(value)) +}; + +struct OwnedAttributeValueVisitor +{ + opentelemetry::proto::common::v1::AnyValue *proto_value_; + bool allow_bytes_{true}; + + template + void operator()(T &&) const { - const std::string &str_value = nostd::get(value); -#if defined(ENABLE_OTLP_UTF8_VALIDITY) - if (utf8_range::IsStructurallyValid(str_value)) - { - proto_value->set_string_value(str_value); - } - else - { - proto_value->set_bytes_value(str_value); - } -#else - proto_value->set_string_value(str_value); -#endif + static_assert(IsSupportedAttributeValue::value, + "OwnedAttributeValueVisitor: Value type in " + "opentelemetry::sdk::common::OwnedAttributeValue does not have an overload " + "operator implemented in this visitor OR implicit conversion attempted!"); } - else if (nostd::holds_alternative>(value)) + + void operator()(bool bool_value) const { proto_value_->set_bool_value(bool_value); } + void operator()(int32_t int32_value) const { proto_value_->set_int_value(int32_value); } + void operator()(uint32_t uint32_value) const { proto_value_->set_int_value(uint32_value); } + void operator()(int64_t int64_value) const { proto_value_->set_int_value(int64_value); } + void operator()(uint64_t uint64_value) const { SetUint64Value(proto_value_, uint64_value); } + void operator()(double double_value) const { proto_value_->set_double_value(double_value); } + void operator()(const std::string &string_value) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto val : nostd::get>(value)) - { - array_value->add_values()->set_bool_value(val); - } + proto_value_->set_string_value(string_value); } - else if (nostd::holds_alternative>(value)) + void operator()(const std::vector &vector_bool_value) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(vector_bool_value.size())); + for (const auto bool_value : vector_bool_value) { - array_value->add_values()->set_int_value(val); + array_value->add_values()->set_bool_value(bool_value); } } - else if (nostd::holds_alternative>(value)) + void operator()(const std::vector &vector_int32_value) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(vector_int32_value.size())); + for (const auto &int32_value : vector_int32_value) { - array_value->add_values()->set_int_value( - val); // NOLINT(cppcoreguidelines-narrowing-conversions) + array_value->add_values()->set_int_value(int32_value); } } - else if (nostd::holds_alternative>(value)) + void operator()(const std::vector &vector_uint32_value) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(vector_uint32_value.size())); + for (const auto &uint32_value : vector_uint32_value) { - array_value->add_values()->set_int_value(val); + array_value->add_values()->set_int_value(static_cast(uint32_value)); } } - else if (nostd::holds_alternative>(value)) + void operator()(const std::vector &vector_int64_value) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(vector_int64_value.size())); + for (const auto &int64_value : vector_int64_value) { - SetUint64Value(array_value->add_values(), val); + array_value->add_values()->set_int_value(int64_value); } } - else if (nostd::holds_alternative>(value)) + void operator()(const std::vector &vector_double_value) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(vector_double_value.size())); + for (const auto &double_value : vector_double_value) { - array_value->add_values()->set_double_value(val); + array_value->add_values()->set_double_value(double_value); } } - else if (nostd::holds_alternative>(value)) + void operator()(const std::vector &vector_string_value) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(vector_string_value.size())); + for (const auto &string_value : vector_string_value) { #if defined(ENABLE_OTLP_UTF8_VALIDITY) - if (utf8_range::IsStructurallyValid(val)) + if (utf8_range::IsStructurallyValid({string_value.data(), string_value.size()})) { - array_value->add_values()->set_string_value(val); + array_value->add_values()->set_string_value(string_value.data(), string_value.size()); } else { - array_value->add_values()->set_bytes_value(val); + array_value->add_values()->set_bytes_value(string_value.data(), string_value.size()); } #else - array_value->add_values()->set_string_value(val); + array_value->add_values()->set_string_value(string_value.data(), string_value.size()); #endif } } + void operator()(const std::vector &vector_uint64_value) const + { + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(vector_uint64_value.size())); + for (const auto &uint64_value : vector_uint64_value) + { + SetUint64Value(array_value->add_values(), uint64_value); + } + } + void operator()(const std::vector &vector_uint8_value) const + { + if (allow_bytes_) + { + proto_value_->set_bytes_value(reinterpret_cast(vector_uint8_value.data()), + vector_uint8_value.size()); + } + else + { + opentelemetry::proto::common::v1::ArrayValue *array_value = + proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(vector_uint8_value.size())); + for (const auto &uint8_value : vector_uint8_value) + { + array_value->add_values()->set_int_value(uint8_value); + } + } + } +}; + +} // namespace + +void OtlpPopulateAttributeUtils::PopulateAnyValue( + opentelemetry::proto::common::v1::AnyValue *proto_value, + const opentelemetry::common::AttributeValue &value, + bool allow_bytes) noexcept +{ +#if defined(OPENTELEMETRY_HAVE_EXCEPTIONS) + try + { +#endif + nostd::visit(AttributeValueVisitor{proto_value, allow_bytes}, value); +#if defined(OPENTELEMETRY_HAVE_EXCEPTIONS) + } +# if defined(OPENTELEMETRY_HAVE_STD_VARIANT) + catch (const std::bad_variant_access &e) +# else + catch (const opentelemetry::nostd::bad_variant_access &e) +# endif + { + OTEL_INTERNAL_LOG_ERROR( + "[OTLP Populate Attribute] bad_variant_access in PopulateAnyValue: " << e.what()); + } + catch (const std::bad_alloc &e) + { + OTEL_INTERNAL_LOG_ERROR( + "[OTLP Populate Attribute] std::bad_alloc in PopulateAnyValue: " << e.what()); + } +#endif +} + +void OtlpPopulateAttributeUtils::PopulateAnyValue( + opentelemetry::proto::common::v1::AnyValue *proto_value, + const opentelemetry::sdk::common::OwnedAttributeValue &value, + bool allow_bytes) noexcept +{ +#if defined(OPENTELEMETRY_HAVE_EXCEPTIONS) + try + { +#endif + nostd::visit(OwnedAttributeValueVisitor{proto_value, allow_bytes}, value); +#if defined(OPENTELEMETRY_HAVE_EXCEPTIONS) + } +# if defined(OPENTELEMETRY_HAVE_STD_VARIANT) + catch (const std::bad_variant_access &e) +# else + catch (const opentelemetry::nostd::bad_variant_access &e) +# endif + { + OTEL_INTERNAL_LOG_ERROR( + "[OTLP Populate Attribute] bad_variant_access in PopulateAnyValue: " << e.what()); + } + catch (const std::bad_alloc &e) + { + OTEL_INTERNAL_LOG_ERROR( + "[OTLP Populate Attribute] std::bad_alloc in PopulateAnyValue: " << e.what()); + } +#endif } void OtlpPopulateAttributeUtils::PopulateAttribute( diff --git a/exporters/otlp/test/otlp_http_exporter_test.cc b/exporters/otlp/test/otlp_http_exporter_test.cc index 3cbfa9d16c..6ff814c945 100644 --- a/exporters/otlp/test/otlp_http_exporter_test.cc +++ b/exporters/otlp/test/otlp_http_exporter_test.cc @@ -554,6 +554,18 @@ TEST(OtlpHttpExporterTest, Shutdown) EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kFailure); } +TEST(OtlpHttpExporterTest, ExportFailsWithInvalidUrl) +{ + auto client_opts = MakeOtlpHttpClientOptions(HttpRequestContentType::kBinary, false); + client_opts.url = "not a valid url ://"; + + OtlpHttpClient client(std::move(client_opts)); + + proto::collector::trace::v1::ExportTraceServiceRequest request; + auto result = client.Export(request); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kFailure); +} + // Create spans, let processor call Export() TEST_F(OtlpHttpExporterTestPeer, ExportJsonIntegrationTestSync) { diff --git a/exporters/otlp/test/otlp_populate_attribute_utils_test.cc b/exporters/otlp/test/otlp_populate_attribute_utils_test.cc new file mode 100644 index 0000000000..92ec479fff --- /dev/null +++ b/exporters/otlp/test/otlp_populate_attribute_utils_test.cc @@ -0,0 +1,440 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include + +#include "opentelemetry/common/attribute_value.h" +#include "opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/sdk/common/attribute_utils.h" +#include "opentelemetry/version.h" + +// clang-format off +#include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" // IWYU pragma: keep +#include "opentelemetry/proto/common/v1/common.pb.h" +#include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" // IWYU pragma: keep +// clang-format on + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +class PopulateAnyValueTest : public ::testing::Test +{ +protected: + opentelemetry::proto::common::v1::AnyValue proto_; +}; + +class PopulateAttributeTest : public ::testing::Test +{ +protected: + opentelemetry::proto::common::v1::KeyValue kv_; +}; + +// ============================================================ +// PopulateAnyValue with common::AttributeValue Tests +// ============================================================ + +TEST_F(PopulateAnyValueTest, AnyValueBool) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_, common::AttributeValue{true}, false); + EXPECT_TRUE(proto_.bool_value()); +} + +TEST_F(PopulateAnyValueTest, AnyValueInt32) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_, common::AttributeValue{int32_t{42}}, false); + EXPECT_EQ(proto_.int_value(), 42); +} + +TEST_F(PopulateAnyValueTest, AnyValueInt64) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_, common::AttributeValue{int64_t{-1}}, false); + EXPECT_EQ(proto_.int_value(), -1); +} + +TEST_F(PopulateAnyValueTest, AnyValueUint32) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_, common::AttributeValue{uint32_t{100}}, + false); + EXPECT_EQ(proto_.int_value(), 100); +} + +TEST_F(PopulateAnyValueTest, AnyValueDouble) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_, common::AttributeValue{3.14}, false); + EXPECT_DOUBLE_EQ(proto_.double_value(), 3.14); +} + +TEST_F(PopulateAnyValueTest, AnyValueCString) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_, common::AttributeValue{"test123"}, false); + EXPECT_EQ(proto_.string_value(), "test123"); +} + +TEST_F(PopulateAnyValueTest, AnyValueStringView) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, common::AttributeValue{nostd::string_view{"test123"}}, false); + EXPECT_EQ(proto_.string_value(), "test123"); +} + +#if defined(ENABLE_OTLP_UTF8_VALIDITY) +TEST_F(PopulateAnyValueTest, AnyValueCStringInvalidUtf8) +{ + // Bare continuation bytes are structurally invalid UTF-8. + const char invalid_utf8[] = {'\x80', '\x81', '\x82', '\x83', '\0'}; + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_, common::AttributeValue{invalid_utf8}, + false); + EXPECT_EQ(proto_.bytes_value(), std::string("\x80\x81\x82\x83", 4)); +} + +TEST_F(PopulateAnyValueTest, AnyValueStringViewInvalidUtf8) +{ + // Bare continuation bytes are structurally invalid UTF-8. + const char invalid_utf8[] = {'\x80', '\x81', '\x82', '\x83'}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, common::AttributeValue{nostd::string_view{invalid_utf8, 4}}, false); + EXPECT_EQ(proto_.bytes_value(), std::string("\x80\x81\x82\x83", 4)); +} +#endif // defined(ENABLE_OTLP_UTF8_VALIDITY) + +// uint64_t values within int64_t range map to int_value per the `Mapping Arbitrary Data to OTLP +// AnyValue` specification. +TEST_F(PopulateAnyValueTest, AnyValueUint64InRange) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, common::AttributeValue{static_cast(std::numeric_limits::max())}, + false); + EXPECT_EQ(proto_.int_value(), std::numeric_limits::max()); +} + +// uint64_t values above INT64_MAX map to string_value per the `Mapping Arbitrary Data to OTLP +// AnyValue` specification. +TEST_F(PopulateAnyValueTest, AnyValueUint64OutOfRange) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, common::AttributeValue{std::numeric_limits::max()}, false); + EXPECT_EQ(proto_.string_value(), std::to_string(std::numeric_limits::max())); +} + +TEST_F(PopulateAnyValueTest, AnyValueSpanUint8AllowBytes) +{ + const uint8_t data[] = {0x01, 0x02, 0x03}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, common::AttributeValue{nostd::span{data}}, true); + EXPECT_EQ(proto_.bytes_value(), std::string("\x01\x02\x03", 3)); +} + +// TODO: This case is not mentioned by the spec. Consider whether to remove disallowing bytes for +// span. +TEST_F(PopulateAnyValueTest, AnyValueSpanUint8DisallowBytes) +{ + const uint8_t data[] = {0x01, 0x02, 0x03}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, common::AttributeValue{nostd::span{data}}, false); + ASSERT_EQ(proto_.array_value().values_size(), 3); + EXPECT_EQ(proto_.array_value().values(0).int_value(), 1); + EXPECT_EQ(proto_.array_value().values(1).int_value(), 2); + EXPECT_EQ(proto_.array_value().values(2).int_value(), 3); +} + +TEST_F(PopulateAnyValueTest, AnyValueSpanBool) +{ + const bool data[] = {true, false, true}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, common::AttributeValue{nostd::span{data}}, false); + ASSERT_EQ(proto_.array_value().values_size(), 3); + EXPECT_TRUE(proto_.array_value().values(0).bool_value()); + EXPECT_FALSE(proto_.array_value().values(1).bool_value()); + EXPECT_TRUE(proto_.array_value().values(2).bool_value()); +} + +TEST_F(PopulateAnyValueTest, AnyValueSpanInt32) +{ + const int32_t data[] = {10, -20, 30}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, common::AttributeValue{nostd::span{data}}, false); + ASSERT_EQ(proto_.array_value().values_size(), 3); + EXPECT_EQ(proto_.array_value().values(0).int_value(), 10); + EXPECT_EQ(proto_.array_value().values(1).int_value(), -20); + EXPECT_EQ(proto_.array_value().values(2).int_value(), 30); +} + +TEST_F(PopulateAnyValueTest, AnyValueSpanInt64) +{ + const int64_t data[] = {-1000, 2000}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, common::AttributeValue{nostd::span{data}}, false); + ASSERT_EQ(proto_.array_value().values_size(), 2); + EXPECT_EQ(proto_.array_value().values(0).int_value(), -1000); + EXPECT_EQ(proto_.array_value().values(1).int_value(), 2000); +} + +TEST_F(PopulateAnyValueTest, AnyValueSpanUint32) +{ + const uint32_t data[] = {100, 200}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, common::AttributeValue{nostd::span{data}}, false); + ASSERT_EQ(proto_.array_value().values_size(), 2); + EXPECT_EQ(proto_.array_value().values(0).int_value(), 100); + EXPECT_EQ(proto_.array_value().values(1).int_value(), 200); +} + +TEST_F(PopulateAnyValueTest, AnyValueSpanUint64) +{ + const uint64_t data[] = {100, 200, 300}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, common::AttributeValue{nostd::span{data}}, false); + ASSERT_EQ(proto_.array_value().values_size(), 3); + EXPECT_EQ(proto_.array_value().values(0).int_value(), 100); + EXPECT_EQ(proto_.array_value().values(1).int_value(), 200); + EXPECT_EQ(proto_.array_value().values(2).int_value(), 300); +} + +// uint64_t values above INT64_MAX in a span map to string_value per the `Mapping Arbitrary Data +// to OTLP AnyValue` specification. +TEST_F(PopulateAnyValueTest, AnyValueSpanUint64Overflow) +{ + const uint64_t data[] = {static_cast(std::numeric_limits::max()), + std::numeric_limits::max()}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, common::AttributeValue{nostd::span{data}}, false); + ASSERT_EQ(proto_.array_value().values_size(), 2); + EXPECT_EQ(proto_.array_value().values(0).int_value(), std::numeric_limits::max()); + EXPECT_EQ(proto_.array_value().values(1).string_value(), + std::to_string(std::numeric_limits::max())); +} + +TEST_F(PopulateAnyValueTest, AnyValueSpanDouble) +{ + const double data[] = {1.5, 2.5}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, common::AttributeValue{nostd::span{data}}, false); + ASSERT_EQ(proto_.array_value().values_size(), 2); + EXPECT_DOUBLE_EQ(proto_.array_value().values(0).double_value(), 1.5); + EXPECT_DOUBLE_EQ(proto_.array_value().values(1).double_value(), 2.5); +} + +TEST_F(PopulateAnyValueTest, AnyValueSpanStringView) +{ + const nostd::string_view data[] = {"foo", "bar"}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, common::AttributeValue{nostd::span{data}}, false); + ASSERT_EQ(proto_.array_value().values_size(), 2); + EXPECT_EQ(proto_.array_value().values(0).string_value(), "foo"); + EXPECT_EQ(proto_.array_value().values(1).string_value(), "bar"); +} + +#if defined(ENABLE_OTLP_UTF8_VALIDITY) +TEST_F(PopulateAnyValueTest, AnyValueSpanStringViewInvalidUtf8) +{ + // Mix of valid and invalid UTF-8 entries. + const char invalid_utf8[] = {'\x80', '\x81', '\x82', '\x83'}; + const nostd::string_view data[] = {"valid", nostd::string_view{invalid_utf8, 4}}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, common::AttributeValue{nostd::span{data}}, false); + ASSERT_EQ(proto_.array_value().values_size(), 2); + EXPECT_EQ(proto_.array_value().values(0).string_value(), "valid"); + EXPECT_EQ(proto_.array_value().values(1).bytes_value(), std::string("\x80\x81\x82\x83", 4)); +} +#endif // defined(ENABLE_OTLP_UTF8_VALIDITY) + +// ============================================================ +// PopulateAnyValue with sdk::common::OwnedAttributeValue Tests +// ============================================================ + +TEST_F(PopulateAnyValueTest, OwnedAnyValueBool) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_, sdk::common::OwnedAttributeValue{false}, + false); + EXPECT_FALSE(proto_.bool_value()); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueInt32) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_, + sdk::common::OwnedAttributeValue{int32_t{7}}, false); + EXPECT_EQ(proto_.int_value(), 7); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueUint32) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, sdk::common::OwnedAttributeValue{uint32_t{200}}, false); + EXPECT_EQ(proto_.int_value(), 200); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueInt64) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, sdk::common::OwnedAttributeValue{int64_t{-99}}, false); + EXPECT_EQ(proto_.int_value(), -99); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueDouble) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_, sdk::common::OwnedAttributeValue{2.71}, + false); + EXPECT_DOUBLE_EQ(proto_.double_value(), 2.71); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueString) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, sdk::common::OwnedAttributeValue{std::string{"owned"}}, false); + EXPECT_EQ(proto_.string_value(), "owned"); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorUint8AllowBytes) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, sdk::common::OwnedAttributeValue{std::vector{0xAA, 0xBB}}, true); + EXPECT_EQ(proto_.bytes_value(), std::string("\xAA\xBB", 2)); +} + +// TODO: This case is not mentioned by the spec. Consider whether to remove disallowing bytes for +// vector. +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorUint8DisallowBytes) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, sdk::common::OwnedAttributeValue{std::vector{0xAA, 0xBB}}, false); + ASSERT_EQ(proto_.array_value().values_size(), 2); + EXPECT_EQ(proto_.array_value().values(0).int_value(), 0xAA); + EXPECT_EQ(proto_.array_value().values(1).int_value(), 0xBB); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorBool) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, sdk::common::OwnedAttributeValue{std::vector{true, false, true}}, false); + ASSERT_EQ(proto_.array_value().values_size(), 3); + EXPECT_TRUE(proto_.array_value().values(0).bool_value()); + EXPECT_FALSE(proto_.array_value().values(1).bool_value()); + EXPECT_TRUE(proto_.array_value().values(2).bool_value()); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorInt32) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, sdk::common::OwnedAttributeValue{std::vector{10, -20, 30}}, false); + ASSERT_EQ(proto_.array_value().values_size(), 3); + EXPECT_EQ(proto_.array_value().values(0).int_value(), 10); + EXPECT_EQ(proto_.array_value().values(1).int_value(), -20); + EXPECT_EQ(proto_.array_value().values(2).int_value(), 30); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorUint32) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, sdk::common::OwnedAttributeValue{std::vector{100, 200}}, false); + ASSERT_EQ(proto_.array_value().values_size(), 2); + EXPECT_EQ(proto_.array_value().values(0).int_value(), 100); + EXPECT_EQ(proto_.array_value().values(1).int_value(), 200); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorInt64) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, sdk::common::OwnedAttributeValue{std::vector{-1000, 2000}}, false); + ASSERT_EQ(proto_.array_value().values_size(), 2); + EXPECT_EQ(proto_.array_value().values(0).int_value(), -1000); + EXPECT_EQ(proto_.array_value().values(1).int_value(), 2000); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorDouble) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, sdk::common::OwnedAttributeValue{std::vector{1.1, 2.2, 3.3}}, false); + ASSERT_EQ(proto_.array_value().values_size(), 3); + EXPECT_DOUBLE_EQ(proto_.array_value().values(0).double_value(), 1.1); + EXPECT_DOUBLE_EQ(proto_.array_value().values(1).double_value(), 2.2); + EXPECT_DOUBLE_EQ(proto_.array_value().values(2).double_value(), 3.3); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorString) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, sdk::common::OwnedAttributeValue{std::vector{"one", "two", "three"}}, + false); + ASSERT_EQ(proto_.array_value().values_size(), 3); + EXPECT_EQ(proto_.array_value().values(0).string_value(), "one"); + EXPECT_EQ(proto_.array_value().values(1).string_value(), "two"); + EXPECT_EQ(proto_.array_value().values(2).string_value(), "three"); +} + +#if defined(ENABLE_OTLP_UTF8_VALIDITY) +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorStringInvalidUtf8) +{ + // Mix of valid and invalid UTF-8 entries. + const std::string invalid_utf8("\x80\x81\x82\x83", 4); + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, sdk::common::OwnedAttributeValue{std::vector{"valid", invalid_utf8}}, + false); + ASSERT_EQ(proto_.array_value().values_size(), 2); + EXPECT_EQ(proto_.array_value().values(0).string_value(), "valid"); + EXPECT_EQ(proto_.array_value().values(1).bytes_value(), std::string("\x80\x81\x82\x83", 4)); +} +#endif // defined(ENABLE_OTLP_UTF8_VALIDITY) + +// uint64_t values within int64_t range map to int_value per the `Mapping Arbitrary Data to OTLP +// AnyValue` specification. +TEST_F(PopulateAnyValueTest, OwnedAnyValueUint64InRange) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, + sdk::common::OwnedAttributeValue{static_cast(std::numeric_limits::max())}, + false); + EXPECT_EQ(proto_.int_value(), std::numeric_limits::max()); +} + +// uint64_t values above INT64_MAX map to string_value per the `Mapping Arbitrary Data to OTLP +// AnyValue` specification. +TEST_F(PopulateAnyValueTest, OwnedAnyValueUint64OutOfRange) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, sdk::common::OwnedAttributeValue{std::numeric_limits::max()}, false); + EXPECT_EQ(proto_.string_value(), std::to_string(std::numeric_limits::max())); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorUint64) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_, sdk::common::OwnedAttributeValue{std::vector{100, 200, 300}}, false); + ASSERT_EQ(proto_.array_value().values_size(), 3); + EXPECT_EQ(proto_.array_value().values(0).int_value(), 100); + EXPECT_EQ(proto_.array_value().values(1).int_value(), 200); + EXPECT_EQ(proto_.array_value().values(2).int_value(), 300); +} + +// ============================================================ +// PopulateAttribute Tests +// ============================================================ + +TEST_F(PopulateAttributeTest, PopulateAttributeKeyValue) +{ + OtlpPopulateAttributeUtils::PopulateAttribute(&kv_, nostd::string_view{"my-key"}, + common::AttributeValue{int64_t{123}}, false); + EXPECT_EQ(kv_.key(), "my-key"); + EXPECT_EQ(kv_.value().int_value(), 123); +} + +TEST_F(PopulateAttributeTest, PopulateAttributeKeyValueOwned) +{ + OtlpPopulateAttributeUtils::PopulateAttribute( + &kv_, nostd::string_view{"my-key"}, sdk::common::OwnedAttributeValue{std::string{"my-value"}}, + false); + EXPECT_EQ(kv_.key(), "my-key"); + EXPECT_EQ(kv_.value().string_value(), "my-value"); +} + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE