diff --git a/api/include/opentelemetry/common/attribute_value.h b/api/include/opentelemetry/common/attribute_value.h index cf4105ea3b..342e889061 100644 --- a/api/include/opentelemetry/common/attribute_value.h +++ b/api/include/opentelemetry/common/attribute_value.h @@ -24,12 +24,12 @@ namespace common /// - Homogenous arrays of primitive type values. /// /// \warning The OpenTelemetry C++ API does not support the following attribute: -/// uint64_t, nostd::span, and nostd::span types. +/// uint64_t, nostd::span, and nostd::span types. /// \parblock The OpenTelemetry C++ API currently supports several attribute /// value types that are not covered by the OpenTelemetry specification: /// - \c uint64_t /// - \c nostd::span -/// - \c nostd::span +/// - \c nostd::span /// /// Those types are reserved for future use and currently should not be /// used. There are no guarantees around how those values are handled by @@ -55,8 +55,6 @@ using AttributeValue = // Not currently supported by the specification, but reserved for future use. // Added to provide support for all primitive C++ types. nostd::span, - // Not currently supported by the specification, but reserved for future use. - // See https://github.com/open-telemetry/opentelemetry-specification/issues/780 nostd::span>; enum AttributeType diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h index 441d1d3c58..02273938bf 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h @@ -56,20 +56,22 @@ class OtlpPopulateAttributeUtils &instrumentation_scope) noexcept; static void PopulateAnyValue(opentelemetry::proto::common::v1::AnyValue *proto_value, - const opentelemetry::common::AttributeValue &value) noexcept; + const opentelemetry::common::AttributeValue &value, + bool allow_bytes) noexcept; - static void PopulateAnyValue( - opentelemetry::proto::common::v1::AnyValue *proto_value, - const opentelemetry::sdk::common::OwnedAttributeValue &value) noexcept; + static void PopulateAnyValue(opentelemetry::proto::common::v1::AnyValue *proto_value, + const opentelemetry::sdk::common::OwnedAttributeValue &value, + bool allow_bytes) noexcept; static void PopulateAttribute(opentelemetry::proto::common::v1::KeyValue *attribute, nostd::string_view key, - const opentelemetry::common::AttributeValue &value) noexcept; + const opentelemetry::common::AttributeValue &value, + bool allow_bytes) noexcept; - static void PopulateAttribute( - opentelemetry::proto::common::v1::KeyValue *attribute, - nostd::string_view key, - const opentelemetry::sdk::common::OwnedAttributeValue &value) noexcept; + static void PopulateAttribute(opentelemetry::proto::common::v1::KeyValue *attribute, + nostd::string_view key, + const opentelemetry::sdk::common::OwnedAttributeValue &value, + bool allow_bytes) noexcept; }; } // namespace otlp diff --git a/exporters/otlp/src/otlp_log_recordable.cc b/exporters/otlp/src/otlp_log_recordable.cc index 374c3c2f5a..4f1877bb41 100644 --- a/exporters/otlp/src/otlp_log_recordable.cc +++ b/exporters/otlp/src/otlp_log_recordable.cc @@ -194,7 +194,7 @@ void OtlpLogRecordable::SetSeverity(opentelemetry::logs::Severity severity) noex void OtlpLogRecordable::SetBody(const opentelemetry::common::AttributeValue &message) noexcept { - OtlpPopulateAttributeUtils::PopulateAnyValue(proto_record_.mutable_body(), message); + OtlpPopulateAttributeUtils::PopulateAnyValue(proto_record_.mutable_body(), message, true); } void OtlpLogRecordable::SetEventId(int64_t /* id */, nostd::string_view event_name) noexcept @@ -236,7 +236,7 @@ void OtlpLogRecordable::SetTraceFlags(const opentelemetry::trace::TraceFlags &tr void OtlpLogRecordable::SetAttribute(opentelemetry::nostd::string_view key, const opentelemetry::common::AttributeValue &value) noexcept { - OtlpPopulateAttributeUtils::PopulateAttribute(proto_record_.add_attributes(), key, value); + OtlpPopulateAttributeUtils::PopulateAttribute(proto_record_.add_attributes(), key, value, true); } void OtlpLogRecordable::SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept diff --git a/exporters/otlp/src/otlp_metric_utils.cc b/exporters/otlp/src/otlp_metric_utils.cc index 118edeff0a..0409037b0a 100644 --- a/exporters/otlp/src/otlp_metric_utils.cc +++ b/exporters/otlp/src/otlp_metric_utils.cc @@ -108,7 +108,7 @@ void OtlpMetricUtils::ConvertSumMetric(const metric_sdk::MetricData &metric_data for (auto &kv_attr : point_data_with_attributes.attributes) { OtlpPopulateAttributeUtils::PopulateAttribute(proto_sum_point_data->add_attributes(), - kv_attr.first, kv_attr.second); + kv_attr.first, kv_attr.second, false); } } } @@ -180,7 +180,7 @@ void OtlpMetricUtils::ConvertHistogramMetric( for (auto &kv_attr : point_data_with_attributes.attributes) { OtlpPopulateAttributeUtils::PopulateAttribute(proto_histogram_point_data->add_attributes(), - kv_attr.first, kv_attr.second); + kv_attr.first, kv_attr.second, false); } } } @@ -244,7 +244,7 @@ void OtlpMetricUtils::ConvertExponentialHistogramMetric( for (auto &kv_attr : point_data_with_attributes.attributes) { OtlpPopulateAttributeUtils::PopulateAttribute(proto_histogram_point_data->add_attributes(), - kv_attr.first, kv_attr.second); + kv_attr.first, kv_attr.second, false); } } } @@ -274,7 +274,7 @@ void OtlpMetricUtils::ConvertGaugeMetric(const opentelemetry::sdk::metrics::Metr for (auto &kv_attr : point_data_with_attributes.attributes) { OtlpPopulateAttributeUtils::PopulateAttribute(proto_gauge_point_data->add_attributes(), - kv_attr.first, kv_attr.second); + kv_attr.first, kv_attr.second, false); } } } diff --git a/exporters/otlp/src/otlp_populate_attribute_utils.cc b/exporters/otlp/src/otlp_populate_attribute_utils.cc index b7ff686306..bc9a7d618a 100644 --- a/exporters/otlp/src/otlp_populate_attribute_utils.cc +++ b/exporters/otlp/src/otlp_populate_attribute_utils.cc @@ -40,7 +40,8 @@ const int kOwnedAttributeValueSize = 15; void OtlpPopulateAttributeUtils::PopulateAnyValue( opentelemetry::proto::common::v1::AnyValue *proto_value, - const opentelemetry::common::AttributeValue &value) noexcept + const opentelemetry::common::AttributeValue &value, + bool allow_bytes) noexcept { if (nullptr == proto_value) { @@ -89,10 +90,19 @@ void OtlpPopulateAttributeUtils::PopulateAnyValue( } else if (nostd::holds_alternative>(value)) { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + if (allow_bytes) { - array_value->add_values()->set_int_value(val); + 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)) @@ -156,7 +166,8 @@ void OtlpPopulateAttributeUtils::PopulateAnyValue( void OtlpPopulateAttributeUtils::PopulateAnyValue( opentelemetry::proto::common::v1::AnyValue *proto_value, - const opentelemetry::sdk::common::OwnedAttributeValue &value) noexcept + const opentelemetry::sdk::common::OwnedAttributeValue &value, + bool allow_bytes) noexcept { if (nullptr == proto_value) { @@ -194,6 +205,23 @@ void OtlpPopulateAttributeUtils::PopulateAnyValue( { proto_value->set_double_value(nostd::get(value)); } + else if (nostd::holds_alternative>(value)) + { + if (allow_bytes) + { + const std::vector &byte_array = nostd::get>(value); + proto_value->set_bytes_value(reinterpret_cast(byte_array.data()), + byte_array.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)) { proto_value->set_string_value(nostd::get(value)); @@ -261,7 +289,8 @@ void OtlpPopulateAttributeUtils::PopulateAnyValue( void OtlpPopulateAttributeUtils::PopulateAttribute( opentelemetry::proto::common::v1::KeyValue *attribute, nostd::string_view key, - const opentelemetry::common::AttributeValue &value) noexcept + const opentelemetry::common::AttributeValue &value, + bool allow_bytes) noexcept { if (nullptr == attribute) { @@ -275,14 +304,15 @@ void OtlpPopulateAttributeUtils::PopulateAttribute( "AttributeValue contains unknown type"); attribute->set_key(key.data(), key.size()); - PopulateAnyValue(attribute->mutable_value(), value); + PopulateAnyValue(attribute->mutable_value(), value, allow_bytes); } /** Maps from C++ attribute into OTLP proto attribute. */ void OtlpPopulateAttributeUtils::PopulateAttribute( opentelemetry::proto::common::v1::KeyValue *attribute, nostd::string_view key, - const opentelemetry::sdk::common::OwnedAttributeValue &value) noexcept + const opentelemetry::sdk::common::OwnedAttributeValue &value, + bool allow_bytes) noexcept { if (nullptr == attribute) { @@ -296,7 +326,7 @@ void OtlpPopulateAttributeUtils::PopulateAttribute( "OwnedAttributeValue contains unknown type"); attribute->set_key(key.data(), key.size()); - PopulateAnyValue(attribute->mutable_value(), value); + PopulateAnyValue(attribute->mutable_value(), value, allow_bytes); } void OtlpPopulateAttributeUtils::PopulateAttribute( @@ -310,7 +340,8 @@ void OtlpPopulateAttributeUtils::PopulateAttribute( for (const auto &kv : resource.GetAttributes()) { - OtlpPopulateAttributeUtils::PopulateAttribute(proto->add_attributes(), kv.first, kv.second); + OtlpPopulateAttributeUtils::PopulateAttribute(proto->add_attributes(), kv.first, kv.second, + false); } } @@ -321,7 +352,8 @@ void OtlpPopulateAttributeUtils::PopulateAttribute( { for (const auto &kv : instrumentation_scope.GetAttributes()) { - OtlpPopulateAttributeUtils::PopulateAttribute(proto->add_attributes(), kv.first, kv.second); + OtlpPopulateAttributeUtils::PopulateAttribute(proto->add_attributes(), kv.first, kv.second, + false); } } diff --git a/exporters/otlp/src/otlp_recordable.cc b/exporters/otlp/src/otlp_recordable.cc index 411c0db355..3564448787 100644 --- a/exporters/otlp/src/otlp_recordable.cc +++ b/exporters/otlp/src/otlp_recordable.cc @@ -121,7 +121,7 @@ void OtlpRecordable::SetAttribute(nostd::string_view key, const common::AttributeValue &value) noexcept { auto *attribute = span_.add_attributes(); - OtlpPopulateAttributeUtils::PopulateAttribute(attribute, key, value); + OtlpPopulateAttributeUtils::PopulateAttribute(attribute, key, value, false); } void OtlpRecordable::AddEvent(nostd::string_view name, @@ -133,7 +133,7 @@ void OtlpRecordable::AddEvent(nostd::string_view name, event->set_time_unix_nano(timestamp.time_since_epoch().count()); attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { - OtlpPopulateAttributeUtils::PopulateAttribute(event->add_attributes(), key, value); + OtlpPopulateAttributeUtils::PopulateAttribute(event->add_attributes(), key, value, false); return true; }); } @@ -148,7 +148,7 @@ void OtlpRecordable::AddLink(const trace::SpanContext &span_context, trace::SpanId::kSize); link->set_trace_state(span_context.trace_state()->ToHeader()); attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { - OtlpPopulateAttributeUtils::PopulateAttribute(link->add_attributes(), key, value); + OtlpPopulateAttributeUtils::PopulateAttribute(link->add_attributes(), key, value, false); return true; }); } diff --git a/exporters/otlp/test/otlp_log_recordable_test.cc b/exporters/otlp/test/otlp_log_recordable_test.cc index 5c1032912d..488d8f12dc 100644 --- a/exporters/otlp/test/otlp_log_recordable_test.cc +++ b/exporters/otlp/test/otlp_log_recordable_test.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -73,6 +74,16 @@ TEST(OtlpLogRecordable, Basic) EXPECT_EQ(rec.log_record().body().string_value(), name); EXPECT_EQ(rec.log_record().trace_id(), expected_trace_id_bytes); EXPECT_EQ(rec.log_record().span_id(), expected_span_id_bytes); + + // Test bytes body + uint8_t byte_arr[] = {'T', 'e', '\0', 's', 't'}; + common::AttributeValue byte_val( + nostd::span{reinterpret_cast(byte_arr), 5}); + rec.SetBody(byte_val); + EXPECT_TRUE(0 == + memcmp(reinterpret_cast(rec.log_record().body().bytes_value().data()), + reinterpret_cast(byte_arr), 5)); + EXPECT_EQ(rec.log_record().body().bytes_value().size(), 5); } TEST(OtlpLogRecordable, GetResource) @@ -111,6 +122,12 @@ TEST(OtlpLogRecordable, SetSingleAttribute) common::AttributeValue str_val(nostd::string_view("Test")); rec.SetAttribute(str_key, str_val); + nostd::string_view byte_key = "byte_attr"; + uint8_t byte_arr[] = {'T', 'e', 's', 't'}; + common::AttributeValue byte_val( + nostd::span{reinterpret_cast(byte_arr), 4}); + rec.SetAttribute(byte_key, byte_val); + int checked_attributes = 0; for (auto &attribute : rec.log_record().attributes()) { @@ -129,8 +146,16 @@ TEST(OtlpLogRecordable, SetSingleAttribute) ++checked_attributes; EXPECT_EQ(attribute.value().string_value(), nostd::get(str_val).data()); } + else if (attribute.key() == byte_key) + { + ++checked_attributes; + EXPECT_TRUE(0 == + memcmp(reinterpret_cast(attribute.value().bytes_value().data()), + reinterpret_cast(byte_arr), 4)); + EXPECT_EQ(attribute.value().bytes_value().size(), 4); + } } - EXPECT_EQ(3, checked_attributes); + EXPECT_EQ(4, checked_attributes); } // Test non-int array types. Int array types are tested using templates (see IntAttributeTest) diff --git a/exporters/otlp/test/otlp_recordable_test.cc b/exporters/otlp/test/otlp_recordable_test.cc index e0dacf38ec..70016de15a 100644 --- a/exporters/otlp/test/otlp_recordable_test.cc +++ b/exporters/otlp/test/otlp_recordable_test.cc @@ -2,8 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 #include -#include #include +#include +#include #include #include #include @@ -256,22 +257,98 @@ TEST(OtlpRecordable, AddLink) TEST(OtlpRecordable, SetResource) { OtlpRecordable rec; - const std::string service_name_key = "service.name"; - std::string service_name = "test-otlp"; - auto resource = resource::Resource::Create({{service_name_key, service_name}}); + bool array_bool[] = {true, false, true}; + int32_t array_int[] = {1, 2, 3}; + double array_double[] = {1.1, 2.2, 3.3}; + opentelemetry::nostd::string_view array_string[] = {"str1", "str2", "str3"}; + + resource::ResourceAttributes attributes{ + {"service.name", opentelemetry::nostd::string_view{"test-otlp"}}, + {"bool_value", true}, + {"int_value", 3}, + {"double_value", static_cast(1.4)}, + {"bytes_value", + opentelemetry::nostd::span{reinterpret_cast("\1\0\3abc"), + 6}}, + {"bool_array", opentelemetry::nostd::span{array_bool}}, + {"int_array", opentelemetry::nostd::span{array_int}}, + {"double_array", opentelemetry::nostd::span{array_double}}, + {"string_array", + opentelemetry::nostd::span{array_string}}}; + + auto resource = resource::Resource::Create(attributes); rec.SetResource(resource); - auto proto_resource = rec.ProtoResource(); - bool found_service_name = false; + auto proto_resource = rec.ProtoResource(); + size_t found_attribute_count = 0; for (int i = 0; i < proto_resource.attributes_size(); i++) { const auto &attr = proto_resource.attributes(static_cast(i)); - if (attr.key() == service_name_key && attr.value().string_value() == service_name) + if (attr.key() == "service.name") { - found_service_name = true; + EXPECT_EQ(attr.value().string_value(), std::string{"test-otlp"}); + ++found_attribute_count; + } + else if (attr.key() == "bool_value") + { + EXPECT_EQ(attr.value().bool_value(), true); + ++found_attribute_count; + } + else if (attr.key() == "int_value") + { + EXPECT_EQ(attr.value().int_value(), 3); + ++found_attribute_count; + } + else if (attr.key() == "double_value") + { + EXPECT_EQ(attr.value().double_value(), static_cast(1.4)); + ++found_attribute_count; + } + else if (attr.key() == "bytes_value") + { + EXPECT_EQ(attr.value().array_value().values_size(), 6); + EXPECT_EQ(attr.value().array_value().values(0).int_value(), 1); + EXPECT_EQ(attr.value().array_value().values(1).int_value(), 0); + EXPECT_EQ(attr.value().array_value().values(2).int_value(), 3); + EXPECT_EQ(attr.value().array_value().values(3).int_value(), static_cast('a')); + EXPECT_EQ(attr.value().array_value().values(4).int_value(), static_cast('b')); + EXPECT_EQ(attr.value().array_value().values(5).int_value(), static_cast('c')); + ++found_attribute_count; + } + else if (attr.key() == "bool_array") + { + EXPECT_EQ(attr.value().array_value().values_size(), 3); + EXPECT_EQ(attr.value().array_value().values(0).bool_value(), true); + EXPECT_EQ(attr.value().array_value().values(1).bool_value(), false); + EXPECT_EQ(attr.value().array_value().values(2).bool_value(), true); + ++found_attribute_count; + } + else if (attr.key() == "int_array") + { + EXPECT_EQ(attr.value().array_value().values_size(), 3); + EXPECT_EQ(attr.value().array_value().values(0).int_value(), 1); + EXPECT_EQ(attr.value().array_value().values(1).int_value(), 2); + EXPECT_EQ(attr.value().array_value().values(2).int_value(), 3); + ++found_attribute_count; + } + else if (attr.key() == "double_array") + { + EXPECT_EQ(attr.value().array_value().values_size(), 3); + EXPECT_EQ(attr.value().array_value().values(0).double_value(), 1.1); + EXPECT_EQ(attr.value().array_value().values(1).double_value(), 2.2); + EXPECT_EQ(attr.value().array_value().values(2).double_value(), 3.3); + ++found_attribute_count; + } + else if (attr.key() == "string_array") + { + EXPECT_EQ(attr.value().array_value().values_size(), 3); + EXPECT_EQ(attr.value().array_value().values(0).string_value(), "str1"); + EXPECT_EQ(attr.value().array_value().values(1).string_value(), "str2"); + EXPECT_EQ(attr.value().array_value().values(2).string_value(), "str3"); + ++found_attribute_count; } } - EXPECT_TRUE(found_service_name); + EXPECT_EQ(found_attribute_count, attributes.size()); } TEST(OtlpRecordable, SetResourceWithSchemaURL) @@ -303,6 +380,12 @@ TEST(OtlpRecordable, SetSingleAttribute) common::AttributeValue str_val(nostd::string_view("Test")); rec.SetAttribute(str_key, str_val); + nostd::string_view byte_key = "byte_attr"; + uint8_t byte_arr[] = {'T', 'e', 's', 't'}; + common::AttributeValue byte_val( + nostd::span{reinterpret_cast(byte_arr), 4}); + rec.SetAttribute(byte_key, byte_val); + EXPECT_EQ(rec.span().attributes(0).key(), bool_key); EXPECT_EQ(rec.span().attributes(0).value().bool_value(), nostd::get(bool_val)); @@ -312,6 +395,17 @@ TEST(OtlpRecordable, SetSingleAttribute) EXPECT_EQ(rec.span().attributes(2).key(), str_key); EXPECT_EQ(rec.span().attributes(2).value().string_value(), nostd::get(str_val).data()); + + EXPECT_EQ(rec.span().attributes(3).key(), byte_key); + EXPECT_EQ(rec.span().attributes(3).value().array_value().values_size(), 4); + EXPECT_EQ(rec.span().attributes(3).value().array_value().values(0).int_value(), + static_cast('T')); + EXPECT_EQ(rec.span().attributes(3).value().array_value().values(1).int_value(), + static_cast('e')); + EXPECT_EQ(rec.span().attributes(3).value().array_value().values(2).int_value(), + static_cast('s')); + EXPECT_EQ(rec.span().attributes(3).value().array_value().values(3).int_value(), + static_cast('t')); } // Test non-int array types. Int array types are tested using templates (see IntAttributeTest)