diff --git a/exporters/otlp/BUILD b/exporters/otlp/BUILD index 1106cad98f..18bb155676 100644 --- a/exporters/otlp/BUILD +++ b/exporters/otlp/BUILD @@ -175,6 +175,16 @@ cc_test( ], ) +cc_test( + name = "otlp_log_recordable_test", + srcs = ["test/otlp_log_recordable_test.cc"], + deps = [ + ":otlp_recordable", + "@com_github_opentelemetry_proto//:logs_service_proto_cc", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "otlp_grpc_exporter_test", srcs = ["test/otlp_grpc_exporter_test.cc"], diff --git a/exporters/otlp/CMakeLists.txt b/exporters/otlp/CMakeLists.txt index 9bd0c5ad7c..705fc97042 100755 --- a/exporters/otlp/CMakeLists.txt +++ b/exporters/otlp/CMakeLists.txt @@ -111,6 +111,18 @@ if(BUILD_TESTING) TARGET otlp_recordable_test TEST_PREFIX exporter.otlp. TEST_LIST otlp_recordable_test) + + if(WITH_LOGS_PREVIEW) + add_executable(otlp_log_recordable_test test/otlp_log_recordable_test.cc) + target_link_libraries( + otlp_log_recordable_test ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_otlp_recordable) + gtest_add_tests( + TARGET otlp_log_recordable_test + TEST_PREFIX exporter.otlp. + TEST_LIST otlp_log_recordable_test) + endif() + if(MSVC) # Explicitly specify that we consume GTest from shared library. The rest of # code logic below determines whether we link Release or Debug flavor of the diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_log_recordable.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_log_recordable.h index 63b696b399..368505ca2a 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_log_recordable.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_log_recordable.h @@ -13,6 +13,7 @@ # include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" // clang-format on +# include "opentelemetry/sdk/common/attribute_utils.h" # include "opentelemetry/sdk/logs/recordable.h" OPENTELEMETRY_BEGIN_NAMESPACE @@ -95,7 +96,7 @@ class OtlpLogRecordable final : public opentelemetry::sdk::logs::Recordable private: proto::logs::v1::LogRecord log_record_; - proto::resource::v1::Resource private_resource_; + opentelemetry::sdk::common::AttributeMap resource_attributes_; // TODO shared resource // const opentelemetry::sdk::resource::Resource *resource_ = nullptr; // TODO InstrumentationLibrary diff --git a/exporters/otlp/src/otlp_log_recordable.cc b/exporters/otlp/src/otlp_log_recordable.cc index 1950a31ab8..5276b62690 100644 --- a/exporters/otlp/src/otlp_log_recordable.cc +++ b/exporters/otlp/src/otlp_log_recordable.cc @@ -17,8 +17,10 @@ namespace otlp proto::resource::v1::Resource OtlpLogRecordable::ProtoResource() const noexcept { - // TODO Populate shared resource - return private_resource_; + proto::resource::v1::Resource proto; + OtlpRecordableUtils::PopulateAttribute( + &proto, opentelemetry::sdk::resource::Resource::Create(resource_attributes_)); + return proto; } void OtlpLogRecordable::SetTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept @@ -171,7 +173,7 @@ void OtlpLogRecordable::SetBody(nostd::string_view message) noexcept void OtlpLogRecordable::SetResource(nostd::string_view key, const opentelemetry::common::AttributeValue &value) noexcept { - OtlpRecordableUtils::PopulateAttribute(private_resource_.add_attributes(), key, value); + resource_attributes_.SetAttribute(key, value); } void OtlpLogRecordable::SetAttribute(nostd::string_view key, diff --git a/exporters/otlp/test/otlp_log_recordable_test.cc b/exporters/otlp/test/otlp_log_recordable_test.cc new file mode 100644 index 0000000000..808d04fafe --- /dev/null +++ b/exporters/otlp/test/otlp_log_recordable_test.cc @@ -0,0 +1,237 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_LOGS_PREVIEW + +# include + +# include "opentelemetry/exporters/otlp/otlp_log_recordable.h" +# include "opentelemetry/sdk/resource/experimental_semantic_conventions.h" +# include "opentelemetry/sdk/resource/resource.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ +namespace resource = opentelemetry::sdk::resource; +namespace proto = opentelemetry::proto; + +TEST(OtlpLogRecordable, SetName) +{ + OtlpLogRecordable rec; + nostd::string_view name = "Test Log Name"; + rec.SetName(name); + EXPECT_EQ(rec.log_record().name(), name); +} + +TEST(OtlpLogRecordable, SetTimestamp) +{ + OtlpLogRecordable rec; + std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); + common::SystemTimestamp start_timestamp(now); + + uint64_t unix_start = + std::chrono::duration_cast(now.time_since_epoch()).count(); + + rec.SetTimestamp(start_timestamp); + EXPECT_EQ(rec.log_record().time_unix_nano(), unix_start); +} + +TEST(OtlpLogRecordable, SetSeverity) +{ + OtlpLogRecordable rec; + rec.SetSeverity(opentelemetry::logs::Severity::kError); + + EXPECT_EQ(rec.log_record().severity_number(), proto::logs::v1::SEVERITY_NUMBER_ERROR); + EXPECT_EQ(rec.log_record().severity_text(), "ERROR"); +} + +TEST(OtlpLogRecordable, SetBody) +{ + OtlpLogRecordable rec; + nostd::string_view name = "Test Log Message"; + rec.SetBody(name); + EXPECT_EQ(rec.log_record().body().string_value(), name); +} + +TEST(OtlpLogRecordable, SetTraceId) +{ + OtlpLogRecordable rec; + uint8_t trace_id_bin[opentelemetry::trace::TraceId::kSize] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + std::string expected_bytes; + expected_bytes.assign( + reinterpret_cast(trace_id_bin), + reinterpret_cast(trace_id_bin) + opentelemetry::trace::TraceId::kSize); + rec.SetTraceId(opentelemetry::trace::TraceId{trace_id_bin}); + EXPECT_EQ(rec.log_record().trace_id(), expected_bytes); +} + +TEST(OtlpLogRecordable, SetSpanId) +{ + OtlpLogRecordable rec; + uint8_t span_id_bin[opentelemetry::trace::SpanId::kSize] = {'7', '6', '5', '4', + '3', '2', '1', '0'}; + std::string expected_bytes; + expected_bytes.assign( + reinterpret_cast(span_id_bin), + reinterpret_cast(span_id_bin) + opentelemetry::trace::SpanId::kSize); + rec.SetSpanId(opentelemetry::trace::SpanId{span_id_bin}); + EXPECT_EQ(rec.log_record().span_id(), expected_bytes); +} + +TEST(OtlpLogRecordable, SetResource) +{ + OtlpLogRecordable rec; + const std::string service_name_key = "service.name"; + std::string service_name = "test-otlp"; + rec.SetResource(service_name_key, service_name); + + auto proto_resource = rec.ProtoResource(); + bool found_service_name = false; + for (int i = 0; i < proto_resource.attributes_size(); i++) + { + auto attr = proto_resource.attributes(static_cast(i)); + if (attr.key() == service_name_key && attr.value().string_value() == service_name) + { + found_service_name = true; + } + } + EXPECT_TRUE(found_service_name); +} + +TEST(OtlpLogRecordable, DefaultResource) +{ + OtlpLogRecordable rec; + + auto proto_resource = rec.ProtoResource(); + int found_resource_count = 0; + for (int i = 0; i < proto_resource.attributes_size(); i++) + { + auto attr = proto_resource.attributes(static_cast(i)); + if (attr.key() == + opentelemetry::sdk::resource::attr(OTEL_CPP_CONST_HASHCODE(AttrTelemetrySdkLanguage))) + { + EXPECT_EQ(attr.value().string_value(), "cpp"); + ++found_resource_count; + } + else if (attr.key() == + opentelemetry::sdk::resource::attr(OTEL_CPP_CONST_HASHCODE(AttrTelemetrySdkName))) + { + EXPECT_EQ(attr.value().string_value(), "opentelemetry"); + ++found_resource_count; + } + else if (attr.key() == + opentelemetry::sdk::resource::attr(OTEL_CPP_CONST_HASHCODE(AttrTelemetrySdkVersion))) + { + EXPECT_EQ(attr.value().string_value(), OPENTELEMETRY_SDK_VERSION); + ++found_resource_count; + } + } + EXPECT_EQ(3, found_resource_count); +} + +// Test non-int single types. Int single types are tested using templates (see IntAttributeTest) +TEST(OtlpLogRecordable, SetSingleAttribute) +{ + OtlpLogRecordable rec; + nostd::string_view bool_key = "bool_attr"; + common::AttributeValue bool_val(true); + rec.SetAttribute(bool_key, bool_val); + + nostd::string_view double_key = "double_attr"; + common::AttributeValue double_val(3.3); + rec.SetAttribute(double_key, double_val); + + nostd::string_view str_key = "str_attr"; + common::AttributeValue str_val(nostd::string_view("Test")); + rec.SetAttribute(str_key, str_val); + + EXPECT_EQ(rec.log_record().attributes(0).key(), bool_key); + EXPECT_EQ(rec.log_record().attributes(0).value().bool_value(), nostd::get(bool_val)); + + EXPECT_EQ(rec.log_record().attributes(1).key(), double_key); + EXPECT_EQ(rec.log_record().attributes(1).value().double_value(), nostd::get(double_val)); + + EXPECT_EQ(rec.log_record().attributes(2).key(), str_key); + EXPECT_EQ(rec.log_record().attributes(2).value().string_value(), + nostd::get(str_val).data()); +} + +// Test non-int array types. Int array types are tested using templates (see IntAttributeTest) +TEST(OtlpLogRecordable, SetArrayAttribute) +{ + OtlpLogRecordable rec; + const int kArraySize = 3; + + bool bool_arr[kArraySize] = {true, false, true}; + nostd::span bool_span(bool_arr); + rec.SetAttribute("bool_arr_attr", bool_span); + + double double_arr[kArraySize] = {22.3, 33.4, 44.5}; + nostd::span double_span(double_arr); + rec.SetAttribute("double_arr_attr", double_span); + + nostd::string_view str_arr[kArraySize] = {"Hello", "World", "Test"}; + nostd::span str_span(str_arr); + rec.SetAttribute("str_arr_attr", str_span); + + for (int i = 0; i < kArraySize; i++) + { + EXPECT_EQ(rec.log_record().attributes(0).value().array_value().values(i).bool_value(), + bool_span[i]); + EXPECT_EQ(rec.log_record().attributes(1).value().array_value().values(i).double_value(), + double_span[i]); + EXPECT_EQ(rec.log_record().attributes(2).value().array_value().values(i).string_value(), + str_span[i]); + } +} + +/** + * AttributeValue can contain different int types, such as int, int64_t, + * unsigned int, and uint64_t. To avoid writing test cases for each, we can + * use a template approach to test all int types. + */ +template +struct OtlpLogRecordableIntAttributeTest : public testing::Test +{ + using IntParamType = T; +}; + +using IntTypes = testing::Types; +TYPED_TEST_SUITE(OtlpLogRecordableIntAttributeTest, IntTypes); + +TYPED_TEST(OtlpLogRecordableIntAttributeTest, SetIntSingleAttribute) +{ + using IntType = typename TestFixture::IntParamType; + IntType i = 2; + common::AttributeValue int_val(i); + + OtlpLogRecordable rec; + rec.SetAttribute("int_attr", int_val); + EXPECT_EQ(rec.log_record().attributes(0).value().int_value(), nostd::get(int_val)); +} + +TYPED_TEST(OtlpLogRecordableIntAttributeTest, SetIntArrayAttribute) +{ + using IntType = typename TestFixture::IntParamType; + + const int kArraySize = 3; + IntType int_arr[kArraySize] = {4, 5, 6}; + nostd::span int_span(int_arr); + + OtlpLogRecordable rec; + rec.SetAttribute("int_arr_attr", int_span); + + for (int i = 0; i < kArraySize; i++) + { + EXPECT_EQ(rec.log_record().attributes(0).value().array_value().values(i).int_value(), + int_span[i]); + } +} +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE + +#endif diff --git a/exporters/otlp/test/otlp_recordable_test.cc b/exporters/otlp/test/otlp_recordable_test.cc index bd7b0be9cc..b6e50836e6 100644 --- a/exporters/otlp/test/otlp_recordable_test.cc +++ b/exporters/otlp/test/otlp_recordable_test.cc @@ -221,7 +221,7 @@ TEST(OtlpRecordable, SetResourceWithSchemaURL) } // Test non-int single types. Int single types are tested using templates (see IntAttributeTest) -TEST(OtlpRecordable, SetSingleAtrribute) +TEST(OtlpRecordable, SetSingleAttribute) { OtlpRecordable rec; nostd::string_view bool_key = "bool_attr"; @@ -248,7 +248,7 @@ TEST(OtlpRecordable, SetSingleAtrribute) } // Test non-int array types. Int array types are tested using templates (see IntAttributeTest) -TEST(OtlpRecordable, SetArrayAtrribute) +TEST(OtlpRecordable, SetArrayAttribute) { OtlpRecordable rec; const int kArraySize = 3; diff --git a/sdk/include/opentelemetry/sdk/logs/recordable.h b/sdk/include/opentelemetry/sdk/logs/recordable.h index cbc8fa1c85..db730752d2 100644 --- a/sdk/include/opentelemetry/sdk/logs/recordable.h +++ b/sdk/include/opentelemetry/sdk/logs/recordable.h @@ -8,6 +8,9 @@ # include "opentelemetry/common/key_value_iterable.h" # include "opentelemetry/common/timestamp.h" # include "opentelemetry/logs/severity.h" +# include "opentelemetry/sdk/common/empty_attributes.h" +# include "opentelemetry/sdk/instrumentationlibrary/instrumentation_library.h" +# include "opentelemetry/sdk/resource/resource.h" # include "opentelemetry/trace/span.h" # include "opentelemetry/trace/span_context.h" # include "opentelemetry/trace/span_id.h"