diff --git a/CHANGELOG.md b/CHANGELOG.md index 819a741b73..93774e4a06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ Increment the: ## [Unreleased] +* [CONFIGURATION] Implement EventToSpanEventBridge log record processor for + declarative configuration + [#4309](https://github.com/open-telemetry/opentelemetry-cpp/pull/4309) + * [CODE HEALTH] Move SDK trace and metrics test helpers into anonymous namespaces [#4303](https://github.com/open-telemetry/opentelemetry-cpp/pull/4303) diff --git a/sdk/include/opentelemetry/sdk/configuration/configuration_parser.h b/sdk/include/opentelemetry/sdk/configuration/configuration_parser.h index 84f5811874..93903c756c 100644 --- a/sdk/include/opentelemetry/sdk/configuration/configuration_parser.h +++ b/sdk/include/opentelemetry/sdk/configuration/configuration_parser.h @@ -36,6 +36,7 @@ #include "opentelemetry/sdk/configuration/double_array_attribute_value_configuration.h" #include "opentelemetry/sdk/configuration/double_attribute_value_configuration.h" #include "opentelemetry/sdk/configuration/drop_aggregation_configuration.h" +#include "opentelemetry/sdk/configuration/event_to_span_event_bridge_log_record_processor_configuration.h" #include "opentelemetry/sdk/configuration/exemplar_filter.h" #include "opentelemetry/sdk/configuration/explicit_bucket_histogram_aggregation_configuration.h" #include "opentelemetry/sdk/configuration/extension_log_record_exporter_configuration.h" @@ -167,6 +168,10 @@ class ConfigurationParser std::unique_ptr ParseSimpleLogRecordProcessorConfiguration( const std::unique_ptr &node) const; + std::unique_ptr + ParseEventToSpanEventBridgeLogRecordProcessorConfiguration( + const std::unique_ptr &node) const; + std::unique_ptr ParseExtensionLogRecordProcessorConfiguration(const std::string &name, std::unique_ptr node) const; diff --git a/sdk/include/opentelemetry/sdk/configuration/event_to_span_event_bridge_log_record_processor_configuration.h b/sdk/include/opentelemetry/sdk/configuration/event_to_span_event_bridge_log_record_processor_configuration.h new file mode 100644 index 0000000000..687b416e30 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/configuration/event_to_span_event_bridge_log_record_processor_configuration.h @@ -0,0 +1,29 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "opentelemetry/sdk/configuration/log_record_processor_configuration.h" +#include "opentelemetry/sdk/configuration/log_record_processor_configuration_visitor.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace configuration +{ + +// YAML-SCHEMA: schema/logger_provider.json +// YAML-NODE: ExperimentalEventToSpanEventBridgeLogRecordProcessor +class EventToSpanEventBridgeLogRecordProcessorConfiguration : public LogRecordProcessorConfiguration +{ +public: + void Accept(LogRecordProcessorConfigurationVisitor *visitor) const override + { + visitor->VisitEventToSpanEventBridge(this); + } +}; + +} // namespace configuration +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/configuration/log_record_processor_configuration_visitor.h b/sdk/include/opentelemetry/sdk/configuration/log_record_processor_configuration_visitor.h index ac4dd52d23..320a8fe4b1 100644 --- a/sdk/include/opentelemetry/sdk/configuration/log_record_processor_configuration_visitor.h +++ b/sdk/include/opentelemetry/sdk/configuration/log_record_processor_configuration_visitor.h @@ -13,6 +13,7 @@ namespace configuration class BatchLogRecordProcessorConfiguration; class SimpleLogRecordProcessorConfiguration; +class EventToSpanEventBridgeLogRecordProcessorConfiguration; class ExtensionLogRecordProcessorConfiguration; class LogRecordProcessorConfigurationVisitor @@ -27,8 +28,10 @@ class LogRecordProcessorConfigurationVisitor const LogRecordProcessorConfigurationVisitor &other) = default; virtual ~LogRecordProcessorConfigurationVisitor() = default; - virtual void VisitBatch(const BatchLogRecordProcessorConfiguration *model) = 0; - virtual void VisitSimple(const SimpleLogRecordProcessorConfiguration *model) = 0; + virtual void VisitBatch(const BatchLogRecordProcessorConfiguration *model) = 0; + virtual void VisitSimple(const SimpleLogRecordProcessorConfiguration *model) = 0; + virtual void VisitEventToSpanEventBridge( + const EventToSpanEventBridgeLogRecordProcessorConfiguration *model) = 0; virtual void VisitExtension(const ExtensionLogRecordProcessorConfiguration *model) = 0; }; diff --git a/sdk/include/opentelemetry/sdk/configuration/sdk_builder.h b/sdk/include/opentelemetry/sdk/configuration/sdk_builder.h index 6897c54c9f..3507367fe8 100644 --- a/sdk/include/opentelemetry/sdk/configuration/sdk_builder.h +++ b/sdk/include/opentelemetry/sdk/configuration/sdk_builder.h @@ -249,6 +249,11 @@ class SdkBuilder std::unique_ptr CreateSimpleLogRecordProcessor( const opentelemetry::sdk::configuration::SimpleLogRecordProcessorConfiguration *model) const; + std::unique_ptr + CreateEventToSpanEventBridgeLogRecordProcessor( + const opentelemetry::sdk::configuration::EventToSpanEventBridgeLogRecordProcessorConfiguration + *model) const; + std::unique_ptr CreateExtensionLogRecordProcessor( const opentelemetry::sdk::configuration::ExtensionLogRecordProcessorConfiguration *model) const; diff --git a/sdk/include/opentelemetry/sdk/logs/event_to_span_event_bridge_processor.h b/sdk/include/opentelemetry/sdk/logs/event_to_span_event_bridge_processor.h new file mode 100644 index 0000000000..8eba69894a --- /dev/null +++ b/sdk/include/opentelemetry/sdk/logs/event_to_span_event_bridge_processor.h @@ -0,0 +1,58 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +#include "opentelemetry/sdk/logs/processor.h" +#include "opentelemetry/sdk/logs/recordable.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace logs +{ + +/** + * A log record processor that bridges log records representing events (i.e. log records with a + * non-empty event name) onto the current active span as span events. + * + * A log record is bridged to a span event if and only if: + * - the log record has a non-empty event name, + * - the log record carries a valid trace id and span id, + * - the current active span (from the ambient context at the time OnEmit is called) is + * recording, and + * - the current active span's trace id and span id match those on the log record. + * + * This processor does not export log records and does not stop the log record from continuing + * through the rest of the configured processing pipeline. + */ +class EventToSpanEventBridgeProcessor : public LogRecordProcessor +{ +public: + EventToSpanEventBridgeProcessor() = default; + EventToSpanEventBridgeProcessor(const EventToSpanEventBridgeProcessor &) = delete; + EventToSpanEventBridgeProcessor(EventToSpanEventBridgeProcessor &&) = delete; + EventToSpanEventBridgeProcessor &operator=(const EventToSpanEventBridgeProcessor &) = delete; + EventToSpanEventBridgeProcessor &operator=(EventToSpanEventBridgeProcessor &&) = delete; + ~EventToSpanEventBridgeProcessor() override = default; + + std::unique_ptr MakeRecordable() noexcept override; + + void OnEmit(std::unique_ptr &&record) noexcept override; + + bool ForceFlush( + std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override; + + bool Shutdown( + std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override; + + bool HasEnabledFilter() const noexcept override { return false; } +}; + +} // namespace logs +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/logs/event_to_span_event_bridge_processor_factory.h b/sdk/include/opentelemetry/sdk/logs/event_to_span_event_bridge_processor_factory.h new file mode 100644 index 0000000000..d5a1b7378b --- /dev/null +++ b/sdk/include/opentelemetry/sdk/logs/event_to_span_event_bridge_processor_factory.h @@ -0,0 +1,31 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "opentelemetry/sdk/logs/processor.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace logs +{ + +/** + * Factory class for EventToSpanEventBridgeProcessor. + */ +class OPENTELEMETRY_EXPORT EventToSpanEventBridgeProcessorFactory +{ +public: + /** + * Create an EventToSpanEventBridgeProcessor. + */ + static std::unique_ptr Create(); +}; + +} // namespace logs +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/configuration/configuration_parser.cc b/sdk/src/configuration/configuration_parser.cc index bfe4dfa373..3cf0f68230 100644 --- a/sdk/src/configuration/configuration_parser.cc +++ b/sdk/src/configuration/configuration_parser.cc @@ -49,6 +49,7 @@ #include "opentelemetry/sdk/configuration/double_array_attribute_value_configuration.h" #include "opentelemetry/sdk/configuration/double_attribute_value_configuration.h" #include "opentelemetry/sdk/configuration/drop_aggregation_configuration.h" +#include "opentelemetry/sdk/configuration/event_to_span_event_bridge_log_record_processor_configuration.h" #include "opentelemetry/sdk/configuration/exemplar_filter.h" #include "opentelemetry/sdk/configuration/explicit_bucket_histogram_aggregation_configuration.h" #include "opentelemetry/sdk/configuration/extension_log_record_exporter_configuration.h" @@ -570,6 +571,13 @@ ConfigurationParser::ParseSimpleLogRecordProcessorConfiguration( return model; } +std::unique_ptr +ConfigurationParser::ParseEventToSpanEventBridgeLogRecordProcessorConfiguration( + const std::unique_ptr & /* node */) const +{ + return std::make_unique(); +} + std::unique_ptr ConfigurationParser::ParseExtensionLogRecordProcessorConfiguration( const std::string &name, @@ -615,6 +623,10 @@ ConfigurationParser::ParseLogRecordProcessorConfiguration( { model = ParseSimpleLogRecordProcessorConfiguration(child); } + else if (name == "event_to_span_event_bridge/development") + { + model = ParseEventToSpanEventBridgeLogRecordProcessorConfiguration(child); + } else { model = ParseExtensionLogRecordProcessorConfiguration(name, std::move(child)); diff --git a/sdk/src/configuration/sdk_builder.cc b/sdk/src/configuration/sdk_builder.cc index b94ccd2799..7c1a2edf12 100644 --- a/sdk/src/configuration/sdk_builder.cc +++ b/sdk/src/configuration/sdk_builder.cc @@ -137,6 +137,7 @@ #include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" #include "opentelemetry/sdk/logs/batch_log_record_processor_factory.h" #include "opentelemetry/sdk/logs/batch_log_record_processor_options.h" +#include "opentelemetry/sdk/logs/event_to_span_event_bridge_processor_factory.h" #include "opentelemetry/sdk/logs/exporter.h" #include "opentelemetry/sdk/logs/log_record_limits.h" #include "opentelemetry/sdk/logs/logger_config.h" @@ -756,6 +757,13 @@ class LogRecordProcessorBuilder processor = sdk_builder_->CreateSimpleLogRecordProcessor(model); } + void VisitEventToSpanEventBridge( + const opentelemetry::sdk::configuration::EventToSpanEventBridgeLogRecordProcessorConfiguration + *model) override + { + processor = sdk_builder_->CreateEventToSpanEventBridgeLogRecordProcessor(model); + } + void VisitExtension( const opentelemetry::sdk::configuration::ExtensionLogRecordProcessorConfiguration *model) override @@ -1930,6 +1938,14 @@ SdkBuilder::CreateSimpleLogRecordProcessor( return sdk; } +std::unique_ptr +SdkBuilder::CreateEventToSpanEventBridgeLogRecordProcessor( + const opentelemetry::sdk::configuration::EventToSpanEventBridgeLogRecordProcessorConfiguration + * /* model */) const +{ + return opentelemetry::sdk::logs::EventToSpanEventBridgeProcessorFactory::Create(); +} + std::unique_ptr SdkBuilder::CreateExtensionLogRecordProcessor( const opentelemetry::sdk::configuration::ExtensionLogRecordProcessorConfiguration *model) const diff --git a/sdk/src/logs/CMakeLists.txt b/sdk/src/logs/CMakeLists.txt index 338fa8e8f1..e28f4e1638 100644 --- a/sdk/src/logs/CMakeLists.txt +++ b/sdk/src/logs/CMakeLists.txt @@ -13,6 +13,8 @@ add_library( event_logger.cc simple_log_record_processor.cc simple_log_record_processor_factory.cc + event_to_span_event_bridge_processor.cc + event_to_span_event_bridge_processor_factory.cc batch_log_record_processor.cc batch_log_record_processor_options.cc batch_log_record_processor_factory.cc diff --git a/sdk/src/logs/event_to_span_event_bridge_processor.cc b/sdk/src/logs/event_to_span_event_bridge_processor.cc new file mode 100644 index 0000000000..9cb34f6240 --- /dev/null +++ b/sdk/src/logs/event_to_span_event_bridge_processor.cc @@ -0,0 +1,234 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "opentelemetry/common/attribute_value.h" +#include "opentelemetry/common/key_value_iterable.h" +#include "opentelemetry/common/timestamp.h" +#include "opentelemetry/nostd/function_ref.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/logs/event_to_span_event_bridge_processor.h" +#include "opentelemetry/sdk/logs/read_write_log_record.h" +#include "opentelemetry/sdk/logs/recordable.h" +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/span_context.h" +#include "opentelemetry/trace/span_id.h" +#include "opentelemetry/trace/trace_id.h" +#include "opentelemetry/trace/tracer.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace logs +{ + +namespace +{ + +// Converts a single OwnedAttributeValue into the corresponding AttributeValue and forwards it +// to the KeyValueIterable callback. Array-of-string and array-of-bool attributes need a +// temporary contiguous buffer (std::vector does not decay to a span of +// string_view, and std::vector is not backed by a real bool array), so those buffers are +// kept alive for the duration of a single callback invocation only. +class EventAttributeEmitter +{ +public: + EventAttributeEmitter( + nostd::string_view key, + nostd::function_ref callback, + bool *keep_going) + : key_(key), callback_(callback), keep_going_(keep_going) + {} + + void operator()(bool v) { Emit(v); } + void operator()(int32_t v) { Emit(v); } + void operator()(uint32_t v) { Emit(v); } + void operator()(int64_t v) { Emit(v); } + void operator()(uint64_t v) { Emit(v); } + void operator()(double v) { Emit(v); } + void operator()(const std::string &v) { Emit(nostd::string_view(v)); } + + void operator()(const std::vector &v) + { + std::unique_ptr buffer(new bool[v.size()]); + for (std::size_t i = 0; i < v.size(); ++i) + { + buffer[i] = v[i]; + } + Emit(nostd::span(buffer.get(), v.size())); + } + + void operator()(const std::vector &v) + { + Emit(nostd::span(v.data(), v.size())); + } + + void operator()(const std::vector &v) + { + Emit(nostd::span(v.data(), v.size())); + } + + void operator()(const std::vector &v) + { + Emit(nostd::span(v.data(), v.size())); + } + + void operator()(const std::vector &v) + { + Emit(nostd::span(v.data(), v.size())); + } + + void operator()(const std::vector &v) + { + Emit(nostd::span(v.data(), v.size())); + } + + void operator()(const std::vector &v) + { + Emit(nostd::span(v.data(), v.size())); + } + + void operator()(const std::vector &v) + { + std::vector views(v.begin(), v.end()); + Emit(nostd::span(views.data(), views.size())); + } + +private: + template + void Emit(const T &value) + { + if (*keep_going_) + { + *keep_going_ = callback_(key_, opentelemetry::common::AttributeValue(value)); + } + } + + nostd::string_view key_; + nostd::function_ref callback_; + bool *keep_going_; +}; + +class EventAttributesKeyValueIterable final : public opentelemetry::common::KeyValueIterable +{ +public: + explicit EventAttributesKeyValueIterable( + const std::unordered_map + &attributes) + : attributes_(&attributes) + {} + + bool ForEachKeyValue( + nostd::function_ref callback) + const noexcept override + { + // Building temporary buffers for array-of-bool and array-of-string attributes (see + // EventAttributeEmitter above) can allocate, so guard against a thrown bad_alloc escaping + // this noexcept override; conservatively stop iterating rather than terminate the process. +#if OPENTELEMETRY_HAVE_EXCEPTIONS + try + { +#endif + bool keep_going = true; + for (const auto &kv : *attributes_) + { + if (!keep_going) + { + break; + } + EventAttributeEmitter emitter(kv.first, callback, &keep_going); + opentelemetry::nostd::visit(emitter, kv.second); + } + return keep_going; +#if OPENTELEMETRY_HAVE_EXCEPTIONS + } + catch (...) + { + return false; + } +#endif + } + + std::size_t size() const noexcept override { return attributes_->size(); } + +private: + const std::unordered_map + *attributes_; +}; + +} // namespace + +std::unique_ptr EventToSpanEventBridgeProcessor::MakeRecordable() noexcept +{ + return std::unique_ptr(new ReadWriteLogRecord()); +} + +void EventToSpanEventBridgeProcessor::OnEmit(std::unique_ptr &&record) noexcept +{ + std::unique_ptr log_record( + static_cast(std::move(record).release())); + if (!log_record) + { + return; + } + + nostd::string_view event_name = log_record->GetEventName(); + if (event_name.empty()) + { + return; + } + + const opentelemetry::trace::TraceId &log_trace_id = log_record->GetTraceId(); + const opentelemetry::trace::SpanId &log_span_id = log_record->GetSpanId(); + if (!log_trace_id.IsValid() || !log_span_id.IsValid()) + { + return; + } + + auto current_span = opentelemetry::trace::Tracer::GetCurrentSpan(); + if (!current_span || !current_span->IsRecording()) + { + return; + } + + opentelemetry::trace::SpanContext current_context = current_span->GetContext(); + if (!(current_context.trace_id() == log_trace_id) || !(current_context.span_id() == log_span_id)) + { + return; + } + + opentelemetry::common::SystemTimestamp event_timestamp = log_record->GetTimestamp(); + if (event_timestamp.time_since_epoch().count() == 0) + { + event_timestamp = log_record->GetObservedTimestamp(); + } + + EventAttributesKeyValueIterable attributes(log_record->GetAttributes()); + current_span->AddEvent(event_name, event_timestamp, attributes); +} + +bool EventToSpanEventBridgeProcessor::ForceFlush(std::chrono::microseconds /* timeout */) noexcept +{ + return true; +} + +bool EventToSpanEventBridgeProcessor::Shutdown(std::chrono::microseconds /* timeout */) noexcept +{ + return true; +} + +} // namespace logs +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/logs/event_to_span_event_bridge_processor_factory.cc b/sdk/src/logs/event_to_span_event_bridge_processor_factory.cc new file mode 100644 index 0000000000..4c339aa9e8 --- /dev/null +++ b/sdk/src/logs/event_to_span_event_bridge_processor_factory.cc @@ -0,0 +1,25 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "opentelemetry/sdk/logs/event_to_span_event_bridge_processor.h" +#include "opentelemetry/sdk/logs/event_to_span_event_bridge_processor_factory.h" +#include "opentelemetry/sdk/logs/processor.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace logs +{ + +std::unique_ptr EventToSpanEventBridgeProcessorFactory::Create() +{ + std::unique_ptr processor(new EventToSpanEventBridgeProcessor()); + return processor; +} + +} // namespace logs +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/test/configuration/yaml_logs_test.cc b/sdk/test/configuration/yaml_logs_test.cc index f885f85434..f2d0d83e7e 100644 --- a/sdk/test/configuration/yaml_logs_test.cc +++ b/sdk/test/configuration/yaml_logs_test.cc @@ -9,6 +9,7 @@ #include "opentelemetry/sdk/configuration/batch_log_record_processor_configuration.h" #include "opentelemetry/sdk/configuration/configuration.h" +#include "opentelemetry/sdk/configuration/event_to_span_event_bridge_log_record_processor_configuration.h" #include "opentelemetry/sdk/configuration/grpc_tls_configuration.h" #include "opentelemetry/sdk/configuration/headers_configuration.h" #include "opentelemetry/sdk/configuration/http_tls_configuration.h" @@ -100,6 +101,27 @@ file_format: "1.0-logs" ASSERT_NE(exporter, nullptr); } +TEST(YamlLogs, event_to_span_event_bridge_processor) +{ + std::string yaml = R"( +file_format: "1.0-logs" +logger_provider: + processors: + - event_to_span_event_bridge/development: +)"; + + auto config = DoParse(yaml); + ASSERT_NE(config, nullptr); + ASSERT_NE(config->logger_provider, nullptr); + ASSERT_EQ(config->logger_provider->processors.size(), 1); + auto *processor = config->logger_provider->processors[0].get(); + ASSERT_NE(processor, nullptr); + auto *bridge = reinterpret_cast< + opentelemetry::sdk::configuration::EventToSpanEventBridgeLogRecordProcessorConfiguration *>( + processor); + ASSERT_NE(bridge, nullptr); +} + TEST(YamlLogs, default_batch_processor) { std::string yaml = R"( diff --git a/sdk/test/logs/BUILD b/sdk/test/logs/BUILD index 8e2198c318..4e9ffa0da6 100644 --- a/sdk/test/logs/BUILD +++ b/sdk/test/logs/BUILD @@ -66,6 +66,24 @@ cc_test( ], ) +cc_test( + name = "event_to_span_event_bridge_processor_test", + srcs = [ + "event_to_span_event_bridge_processor_test.cc", + ], + tags = [ + "logs", + "test", + ], + deps = [ + "//exporters/memory:in_memory_span_exporter", + "//sdk/src/logs", + "//sdk/src/resource", + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "log_record_test", srcs = [ diff --git a/sdk/test/logs/CMakeLists.txt b/sdk/test/logs/CMakeLists.txt index 1fcbc3bb27..e0de53ed30 100644 --- a/sdk/test/logs/CMakeLists.txt +++ b/sdk/test/logs/CMakeLists.txt @@ -20,3 +20,18 @@ foreach( TEST_PREFIX logs. TEST_LIST ${testname}) endforeach() + +add_executable(event_to_span_event_bridge_processor_test + event_to_span_event_bridge_processor_test.cc) +target_link_libraries( + event_to_span_event_bridge_processor_test + ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_logs + opentelemetry_trace + opentelemetry_resources + opentelemetry_exporter_in_memory) +gtest_add_tests( + TARGET event_to_span_event_bridge_processor_test + TEST_PREFIX logs. + TEST_LIST event_to_span_event_bridge_processor_test) diff --git a/sdk/test/logs/event_to_span_event_bridge_processor_test.cc b/sdk/test/logs/event_to_span_event_bridge_processor_test.cc new file mode 100644 index 0000000000..80a43ac740 --- /dev/null +++ b/sdk/test/logs/event_to_span_event_bridge_processor_test.cc @@ -0,0 +1,133 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include + +#include "opentelemetry/exporters/memory/in_memory_span_data.h" +#include "opentelemetry/exporters/memory/in_memory_span_exporter.h" +#include "opentelemetry/sdk/logs/event_to_span_event_bridge_processor.h" +#include "opentelemetry/sdk/logs/read_write_log_record.h" +#include "opentelemetry/sdk/logs/recordable.h" +#include "opentelemetry/sdk/trace/exporter.h" +#include "opentelemetry/sdk/trace/processor.h" +#include "opentelemetry/sdk/trace/simple_processor.h" +#include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/sdk/trace/tracer.h" +#include "opentelemetry/sdk/trace/tracer_context.h" +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/span_context.h" +#include "opentelemetry/trace/tracer.h" + +using namespace opentelemetry::sdk::logs; +using opentelemetry::exporter::memory::InMemorySpanData; +using opentelemetry::exporter::memory::InMemorySpanExporter; + +namespace trace_sdk = opentelemetry::sdk::trace; +namespace trace_api = opentelemetry::trace; + +namespace +{ + +std::shared_ptr MakeTracer(std::shared_ptr *out_span_data) +{ + InMemorySpanExporter *exporter = new InMemorySpanExporter(); + *out_span_data = exporter->GetData(); + + auto processor = std::unique_ptr( + new trace_sdk::SimpleSpanProcessor(std::unique_ptr(exporter))); + std::vector> processors; + processors.push_back(std::move(processor)); + auto context = std::make_shared(std::move(processors)); + return std::shared_ptr(new trace_sdk::Tracer(context)); +} + +} // namespace + +TEST(EventToSpanEventBridgeProcessorTest, BridgesEventOntoCurrentMatchingSpan) +{ + std::shared_ptr span_data; + auto tracer = MakeTracer(&span_data); + + auto span = tracer->StartSpan("test-span"); + auto scope = tracer->WithActiveSpan(span); + + EventToSpanEventBridgeProcessor processor; + auto recordable = processor.MakeRecordable(); + auto *log_record = static_cast(recordable.get()); + + trace_api::SpanContext span_context = span->GetContext(); + log_record->SetTraceId(span_context.trace_id()); + log_record->SetSpanId(span_context.span_id()); + log_record->SetEventId(0, "my.event"); + log_record->SetAttribute("key1", "value1"); + + processor.OnEmit(std::move(recordable)); + + span->End(); + + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + ASSERT_EQ(1, spans.at(0)->GetEvents().size()); + EXPECT_EQ("my.event", spans.at(0)->GetEvents().at(0).GetName()); +} + +TEST(EventToSpanEventBridgeProcessorTest, IgnoresLogRecordWithoutEventName) +{ + std::shared_ptr span_data; + auto tracer = MakeTracer(&span_data); + + auto span = tracer->StartSpan("test-span"); + auto scope = tracer->WithActiveSpan(span); + + EventToSpanEventBridgeProcessor processor; + auto recordable = processor.MakeRecordable(); + auto *log_record = static_cast(recordable.get()); + + trace_api::SpanContext span_context = span->GetContext(); + log_record->SetTraceId(span_context.trace_id()); + log_record->SetSpanId(span_context.span_id()); + + processor.OnEmit(std::move(recordable)); + + span->End(); + + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + EXPECT_EQ(0, spans.at(0)->GetEvents().size()); +} + +TEST(EventToSpanEventBridgeProcessorTest, IgnoresEventFromDifferentSpan) +{ + std::shared_ptr span_data; + auto tracer = MakeTracer(&span_data); + + auto span = tracer->StartSpan("test-span"); + auto other_span = tracer->StartSpan("other-span"); + auto scope = tracer->WithActiveSpan(span); + + EventToSpanEventBridgeProcessor processor; + auto recordable = processor.MakeRecordable(); + auto *log_record = static_cast(recordable.get()); + + // The log record belongs to other_span, not the current active span. + trace_api::SpanContext other_context = other_span->GetContext(); + log_record->SetTraceId(other_context.trace_id()); + log_record->SetSpanId(other_context.span_id()); + log_record->SetEventId(0, "my.event"); + + processor.OnEmit(std::move(recordable)); + + span->End(); + other_span->End(); + + auto spans = span_data->GetSpans(); + ASSERT_EQ(2, spans.size()); + for (auto &exported_span : spans) + { + EXPECT_EQ(0, exported_span->GetEvents().size()); + } +}