// 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/timestamp.h" #include "opentelemetry/logs/event_logger.h" #include "opentelemetry/logs/event_logger_provider.h" #include "opentelemetry/logs/log_record.h" #include "opentelemetry/logs/logger.h" #include "opentelemetry/logs/logger_provider.h" #include "opentelemetry/logs/noop.h" #include "opentelemetry/logs/severity.h" #include "opentelemetry/nostd/shared_ptr.h" #include "opentelemetry/nostd/span.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/nostd/variant.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" #include "opentelemetry/sdk/logs/event_logger_provider.h" #include "opentelemetry/sdk/logs/logger.h" #include "opentelemetry/sdk/logs/logger_config.h" #include "opentelemetry/sdk/logs/logger_provider.h" #include "opentelemetry/sdk/logs/processor.h" #include "opentelemetry/sdk/logs/recordable.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/sdk/trace/processor.h" #include "opentelemetry/sdk/trace/tracer_provider.h" #include "opentelemetry/sdk/trace/tracer_provider_factory.h" #include "opentelemetry/trace/scope.h" #include "opentelemetry/trace/span.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/trace_flags.h" #include "opentelemetry/trace/trace_id.h" #include "opentelemetry/trace/tracer.h" using namespace opentelemetry::sdk::logs; using namespace opentelemetry::sdk::instrumentationscope; namespace logs_api = opentelemetry::logs; namespace nostd = opentelemetry::nostd; TEST(LoggerSDK, LogToNullProcessor) { // Confirm Logger::EmitLogRecord() does not have undefined behavior // even when there is no processor set // since it calls Processor::OnEmit() auto lp = std::shared_ptr(new LoggerProvider()); const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; auto logger = lp->GetLogger("logger", "opentelelemtry_library", "", schema_url); auto sdk_logger = static_cast(logger.get()); ASSERT_EQ(sdk_logger->GetInstrumentationScope().GetName(), "opentelelemtry_library"); ASSERT_EQ(sdk_logger->GetInstrumentationScope().GetVersion(), ""); ASSERT_EQ(sdk_logger->GetInstrumentationScope().GetSchemaURL(), schema_url); // Log a sample log record to a nullptr processor logger->Debug("Test log"); } class MockLogRecordable final : public opentelemetry::sdk::logs::Recordable { public: void SetTimestamp(opentelemetry::common::SystemTimestamp) noexcept override {} void SetObservedTimestamp( opentelemetry::common::SystemTimestamp observed_timestamp) noexcept override { observed_timestamp_ = observed_timestamp; } const opentelemetry::common::SystemTimestamp &GetObservedTimestamp() const noexcept { return observed_timestamp_; } opentelemetry::logs::Severity GetSeverity() const noexcept { return severity_; } void SetSeverity(opentelemetry::logs::Severity severity) noexcept override { severity_ = severity; } nostd::string_view GetBody() const noexcept { return body_; } void SetBody(const opentelemetry::common::AttributeValue &message) noexcept override { if (nostd::holds_alternative(message)) { body_ = nostd::get(message); } else if (nostd::holds_alternative(message)) { body_ = static_cast(nostd::get(message)); } } void SetBody(const std::string &message) noexcept { body_ = message; } void SetEventId(int64_t id, nostd::string_view name) noexcept override { event_id_ = id; log_record_event_name_ = static_cast(name); } void SetTraceId(const opentelemetry::trace::TraceId &trace_id) noexcept override { trace_id_ = trace_id; } inline const opentelemetry::trace::TraceId &GetTraceId() const noexcept { return trace_id_; } void SetSpanId(const opentelemetry::trace::SpanId &span_id) noexcept override { span_id_ = span_id; } inline const opentelemetry::trace::SpanId &GetSpanId() const noexcept { return span_id_; } void SetTraceFlags(const opentelemetry::trace::TraceFlags &trace_flags) noexcept override { trace_flags_ = trace_flags; } inline const opentelemetry::trace::TraceFlags &GetTraceFlags() const noexcept { return trace_flags_; } void SetAttribute(nostd::string_view key, const opentelemetry::common::AttributeValue &value) noexcept override { if (!nostd::holds_alternative(value)) { return; } if (key == "event.domain") { event_domain_ = static_cast(nostd::get(value)); } else if (key == "event.name") { event_name_ = static_cast(nostd::get(value)); } } inline const std::string &GetEventName() const noexcept { return event_name_; } inline const std::string &GetEventDomain() const noexcept { return event_domain_; } void SetResource(const opentelemetry::sdk::resource::Resource &) noexcept override {} void SetInstrumentationScope( const opentelemetry::sdk::instrumentationscope::InstrumentationScope &) noexcept override {} void CopyFrom(const MockLogRecordable &other) { severity_ = other.severity_; body_ = other.body_; trace_id_ = other.trace_id_; span_id_ = other.span_id_; trace_flags_ = other.trace_flags_; event_name_ = other.event_name_; event_domain_ = other.event_domain_; observed_timestamp_ = other.observed_timestamp_; } private: opentelemetry::logs::Severity severity_ = opentelemetry::logs::Severity::kInvalid; std::string body_; int64_t event_id_; std::string log_record_event_name_; opentelemetry::trace::TraceId trace_id_; opentelemetry::trace::SpanId span_id_; opentelemetry::trace::TraceFlags trace_flags_; std::string event_name_; std::string event_domain_; opentelemetry::common::SystemTimestamp observed_timestamp_ = std::chrono::system_clock::from_time_t(0); }; class MockProcessor final : public LogRecordProcessor { private: std::shared_ptr record_received_; public: // A processor used for testing that keeps a track of the recordable it received explicit MockProcessor(std::shared_ptr record_received) noexcept : record_received_(std::move(record_received)) {} std::unique_ptr MakeRecordable() noexcept override { return std::unique_ptr(new MockLogRecordable()); } // OnEmit stores the record it receives into the shared_ptr recordable passed into its // constructor void OnEmit(std::unique_ptr &&record) noexcept override { // Cast the recordable received into a concrete MockLogRecordable type auto copy = std::shared_ptr(static_cast(record.release())); // Copy over the received log record's severity, name, and body fields over to the recordable // passed in the constructor record_received_->CopyFrom(*copy); } bool ForceFlush(std::chrono::microseconds /* timeout */) noexcept override { return true; } bool Shutdown(std::chrono::microseconds /* timeout */) noexcept override { return true; } }; TEST(LoggerSDK, LogToAProcessor) { // Create an API LoggerProvider and logger auto api_lp = std::shared_ptr(new LoggerProvider()); const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; auto logger = api_lp->GetLogger("logger", "opentelelemtry_library", "", schema_url); // Cast the API LoggerProvider to an SDK Logger Provider and assert that it is still the same // LoggerProvider by checking that getting a logger with the same name as the previously defined // logger is the same instance auto lp = static_cast(api_lp.get()); auto logger2 = lp->GetLogger("logger", "opentelelemtry_library", "", schema_url); ASSERT_EQ(logger, logger2); nostd::shared_ptr include_span; { std::vector> span_processors; auto trace_provider = opentelemetry::sdk::trace::TracerProviderFactory::Create(std::move(span_processors)); include_span = trace_provider->GetTracer("logger")->StartSpan("test_logger"); } opentelemetry::trace::Scope trace_scope{include_span}; auto now = std::chrono::system_clock::now(); auto sdk_logger = static_cast(logger.get()); ASSERT_EQ(sdk_logger->GetInstrumentationScope().GetName(), "opentelelemtry_library"); ASSERT_EQ(sdk_logger->GetInstrumentationScope().GetVersion(), ""); ASSERT_EQ(sdk_logger->GetInstrumentationScope().GetSchemaURL(), schema_url); // Set a processor for the LoggerProvider auto shared_recordable = std::shared_ptr(new MockLogRecordable()); lp->AddProcessor(std::unique_ptr( new MockProcessor(shared_recordable))); // Check that the recordable created by the EmitLogRecord() statement is set properly logger->EmitLogRecord(logs_api::Severity::kWarn, "Log Message"); ASSERT_EQ(shared_recordable->GetSeverity(), logs_api::Severity::kWarn); ASSERT_EQ(shared_recordable->GetBody(), "Log Message"); ASSERT_EQ(shared_recordable->GetTraceFlags().flags(), include_span->GetContext().trace_flags().flags()); char trace_id_in_logger[opentelemetry::trace::TraceId::kSize * 2]; char trace_id_in_span[opentelemetry::trace::TraceId::kSize * 2]; char span_id_in_logger[opentelemetry::trace::SpanId::kSize * 2]; char span_id_in_span[opentelemetry::trace::SpanId::kSize * 2]; shared_recordable->GetTraceId().ToLowerBase16(trace_id_in_logger); include_span->GetContext().trace_id().ToLowerBase16(trace_id_in_span); shared_recordable->GetSpanId().ToLowerBase16(span_id_in_logger); include_span->GetContext().span_id().ToLowerBase16(span_id_in_span); std::string trace_id_text_in_logger{trace_id_in_logger, sizeof(trace_id_in_logger)}; std::string trace_id_text_in_span{trace_id_in_span, sizeof(trace_id_in_span)}; std::string span_id_text_in_logger{span_id_in_logger, sizeof(span_id_in_logger)}; std::string span_id_text_in_span{span_id_in_span, sizeof(span_id_in_span)}; ASSERT_EQ(trace_id_text_in_logger, trace_id_text_in_span); ASSERT_EQ(span_id_text_in_logger, span_id_text_in_span); ASSERT_GE( static_cast(shared_recordable->GetObservedTimestamp()), now); } TEST(LoggerSDK, LoggerWithDisabledConfig) { ScopeConfigurator disabled_all_scopes = ScopeConfigurator::Builder(LoggerConfig::Disabled()).Build(); // Set a processor for the LoggerProvider auto shared_recordable = std::shared_ptr(new MockLogRecordable()); auto log_processor = std::unique_ptr(new MockProcessor(shared_recordable)); // Create an API LoggerProvider and logger const auto resource = opentelemetry::sdk::resource::Resource::Create({}); const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; auto api_lp = std::shared_ptr( new LoggerProvider(std::move(log_processor), resource, std::make_unique>(disabled_all_scopes))); auto logger = api_lp->GetLogger("logger", "opentelelemtry_library", "", schema_url); auto noop_logger = logs_api::NoopLogger(); // Test Logger functions for the constructed logger // This logger should behave like a noop logger ASSERT_EQ(logger->GetName(), noop_logger.GetName()); // Since the logger is disabled, when creating a LogRecord, the observed timestamp will not be // set in the underlying LogRecordable auto log_record = logger->CreateLogRecord(); logger->EmitLogRecord(std::move(log_record)); ASSERT_EQ(shared_recordable->GetObservedTimestamp(), std::chrono::system_clock::from_time_t(0)); // Since this logger should behave like a noop logger, no values within the recordable would be // set. logger->EmitLogRecord(logs_api::Severity::kWarn, "Log Message"); ASSERT_EQ(shared_recordable->GetBody(), ""); ASSERT_EQ(shared_recordable->GetSeverity(), opentelemetry::logs::Severity::kInvalid); ASSERT_EQ(shared_recordable->GetObservedTimestamp(), std::chrono::system_clock::from_time_t(0)); } TEST(LoggerSDK, LoggerWithEnabledConfig) { ScopeConfigurator enabled_all_scopes = ScopeConfigurator::Builder(LoggerConfig::Enabled()).Build(); // Set a processor for the LoggerProvider auto shared_recordable = std::shared_ptr(new MockLogRecordable()); auto log_processor = std::unique_ptr(new MockProcessor(shared_recordable)); // Create an API LoggerProvider and logger const auto resource = opentelemetry::sdk::resource::Resource::Create({}); const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; auto api_lp = std::shared_ptr( new LoggerProvider(std::move(log_processor), resource, std::make_unique>(enabled_all_scopes))); auto logger = api_lp->GetLogger("test-logger", "opentelemetry_library", "", schema_url); // Test Logger functions for the constructed logger ASSERT_EQ(logger->GetName(), "test-logger"); // Since the logger is enabled, when creating a LogRecord, the observed timestamp will be set // in the underlying LogRecordable. auto reference_ts = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()); auto log_record = logger->CreateLogRecord(); logger->EmitLogRecord(std::move(log_record)); // Since log_record was created after recording reference timestamp, expect that observed // timestamp is greater ASSERT_GE(shared_recordable->GetObservedTimestamp().time_since_epoch().count(), reference_ts.count()); // Since this logger should behave like a valid logger, values within the recordable would be set. logger->EmitLogRecord(logs_api::Severity::kWarn, "Log Message"); ASSERT_EQ(shared_recordable->GetBody(), "Log Message"); ASSERT_EQ(shared_recordable->GetSeverity(), opentelemetry::logs::Severity::kWarn); ASSERT_GE(shared_recordable->GetObservedTimestamp().time_since_epoch().count(), reference_ts.count()); } static std::unique_ptr create_mock_log_recordable( const std::string &body, opentelemetry::logs::Severity severity) { auto mock_log_recordable = std::make_unique(); mock_log_recordable->SetBody(body); mock_log_recordable->SetSeverity(severity); return mock_log_recordable; } class CustomLogConfiguratorTestData { public: InstrumentationScope instrumentation_scope_; MockLogRecordable test_log_recordable_; MockLogRecordable expected_log_recordable_; bool expected_disabled_for_scope_; CustomLogConfiguratorTestData(const InstrumentationScope &instrumentation_scope, const MockLogRecordable &test_log_recordable, const MockLogRecordable &expected_log_recordable, const bool expected_disabled_for_scope) : instrumentation_scope_(instrumentation_scope), test_log_recordable_(test_log_recordable), expected_log_recordable_(expected_log_recordable), expected_disabled_for_scope_(expected_disabled_for_scope) {} }; // constants used in VerifyCustomConfiguratorBehavior test static auto noop_logger = logs_api::NoopLogger(); const std::string schema{"https://opentelemetry.io/schemas/1.11.0"}; // Generate test case data // Test Case 1 static auto instrumentation_scope_1 = *InstrumentationScope::Create("opentelemetry_library", "1.0.0", schema); static auto test_log_recordable_1 = create_mock_log_recordable("Log Message", opentelemetry::logs::Severity::kWarn); static auto expected_log_recordable_1 = create_mock_log_recordable("Log Message", opentelemetry::logs::Severity::kWarn); static auto custom_log_configurator_test_data_1 = CustomLogConfiguratorTestData(instrumentation_scope_1, *test_log_recordable_1, *expected_log_recordable_1, false); // Test Case 2 static auto instrumentation_scope_2 = *InstrumentationScope::Create("bar_library", "1.0.0", schema); static auto test_log_recordable_2 = create_mock_log_recordable("", opentelemetry::logs::Severity::kDebug); static auto expected_log_recordable_2 = create_mock_log_recordable("", opentelemetry::logs::Severity::kDebug); static auto custom_log_configurator_test_data_2 = CustomLogConfiguratorTestData(instrumentation_scope_2, *test_log_recordable_2, *expected_log_recordable_2, false); // Test Case 3 static auto instrumentation_scope_3 = *InstrumentationScope::Create("foo_library", "", schema); static auto test_log_recordable_3 = create_mock_log_recordable("Info message", opentelemetry::logs::Severity::kInfo); static auto expected_log_recordable_3 = create_mock_log_recordable("", opentelemetry::logs::Severity::kInvalid); static auto custom_log_configurator_test_data_3 = CustomLogConfiguratorTestData(instrumentation_scope_3, *test_log_recordable_3, *expected_log_recordable_3, true); // Test Case 4 static auto instrumentation_scope_4 = *InstrumentationScope::Create("allowed_library", "", schema); static auto test_log_recordable_4 = create_mock_log_recordable("Scope version missing", opentelemetry::logs::Severity::kInfo); static auto expected_log_recordable_4 = create_mock_log_recordable("", opentelemetry::logs::Severity::kInvalid); static auto custom_log_configurator_test_data_4 = CustomLogConfiguratorTestData(instrumentation_scope_4, *test_log_recordable_4, *expected_log_recordable_4, true); // This array could also directly contain the reference types, but that leads to 'uninitialized // value was created by heap allocation' errors in Valgrind memcheck. This is a bug in Googletest // library, see https://github.com/google/googletest/issues/3805#issuecomment-1397301790 for more // details. Using pointers is a workaround to prevent the Valgrind warnings. constexpr std::array log_configurator_test_cases = { &custom_log_configurator_test_data_1, &custom_log_configurator_test_data_2, &custom_log_configurator_test_data_3, &custom_log_configurator_test_data_4}; // Test fixture for VerifyCustomConfiguratorBehavior class CustomLoggerConfiguratorTestFixture : public ::testing::TestWithParam {}; TEST_P(CustomLoggerConfiguratorTestFixture, VerifyCustomConfiguratorBehavior) { // lambda checks if version is present in scope information auto check_if_version_present = [](const InstrumentationScope &scope_info) { return !scope_info.GetVersion().empty(); }; // custom scope configurator that only disables loggers for library name "foo_library" or do not // have version information auto test_scope_configurator = ScopeConfigurator( ScopeConfigurator::Builder(LoggerConfig::Disabled()) .AddConditionNameEquals("foo_library", LoggerConfig::Disabled()) .AddCondition(check_if_version_present, LoggerConfig::Enabled()) .Build()); // Get the test case data from fixture CustomLogConfiguratorTestData *test_case = GetParam(); auto test_instrumentation_scope = test_case->instrumentation_scope_; auto test_log_recordable = test_case->test_log_recordable_; // Set a processor for the LoggerProvider auto shared_recordable_under_test = std::shared_ptr(new MockLogRecordable()); auto log_processor_test = std::unique_ptr(new MockProcessor(shared_recordable_under_test)); // Create an API LoggerProvider and logger const auto resource = opentelemetry::sdk::resource::Resource::Create({}); auto api_lp = std::shared_ptr(new LoggerProvider( std::move(log_processor_test), resource, std::make_unique>(test_scope_configurator))); // Create logger and make assertions auto logger_under_test = api_lp->GetLogger("test-logger", test_instrumentation_scope.GetName(), test_instrumentation_scope.GetVersion(), test_instrumentation_scope.GetSchemaURL()); auto reference_ts = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()); auto log_record = logger_under_test->CreateLogRecord(); logger_under_test->EmitLogRecord(std::move(log_record)); // Test Logger functions for the constructed logger if (test_case->expected_disabled_for_scope_) { ASSERT_EQ(logger_under_test->GetName(), noop_logger.GetName()); ASSERT_EQ(shared_recordable_under_test->GetObservedTimestamp(), std::chrono::system_clock::from_time_t(0)); } else { ASSERT_EQ(logger_under_test->GetName(), "test-logger"); ASSERT_GE(shared_recordable_under_test->GetObservedTimestamp().time_since_epoch().count(), reference_ts.count()); } logger_under_test->EmitLogRecord(test_log_recordable.GetBody(), test_log_recordable.GetSeverity()); ASSERT_EQ(shared_recordable_under_test->GetBody(), test_case->expected_log_recordable_.GetBody()); ASSERT_EQ(shared_recordable_under_test->GetSeverity(), test_case->expected_log_recordable_.GetSeverity()); } INSTANTIATE_TEST_SUITE_P(CustomLogConfiguratorTestData, CustomLoggerConfiguratorTestFixture, ::testing::ValuesIn(log_configurator_test_cases)); #if OPENTELEMETRY_ABI_VERSION_NO < 2 TEST(LoggerSDK, EventLog) { // Create an API LoggerProvider and logger auto api_lp = std::shared_ptr(new LoggerProvider()); const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; auto logger = api_lp->GetLogger("logger", "opentelelemtry_library", "", schema_url); auto api_elp = std::shared_ptr(new EventLoggerProvider()); auto event_logger = api_elp->CreateEventLogger(logger, "otel-cpp.event_domain"); auto sdk_logger = static_cast(logger.get()); ASSERT_EQ(sdk_logger->GetInstrumentationScope().GetName(), "opentelelemtry_library"); ASSERT_EQ(sdk_logger->GetInstrumentationScope().GetVersion(), ""); ASSERT_EQ(sdk_logger->GetInstrumentationScope().GetSchemaURL(), schema_url); // Set a processor for the LoggerProvider auto shared_recordable = std::shared_ptr(new MockLogRecordable()); auto sdk_lp = static_cast(api_lp.get()); sdk_lp->AddProcessor(std::unique_ptr( new MockProcessor(shared_recordable))); // Check that the recordable created by the EmitEvent() statement is set properly event_logger->EmitEvent("otel-cpp.event_name", logs_api::Severity::kWarn, "Event Log Message"); ASSERT_EQ(shared_recordable->GetSeverity(), logs_api::Severity::kWarn); ASSERT_EQ(shared_recordable->GetBody(), "Event Log Message"); ASSERT_EQ(shared_recordable->GetEventName(), "otel-cpp.event_name"); ASSERT_EQ(shared_recordable->GetEventDomain(), "otel-cpp.event_domain"); } #endif