diff --git a/sdk/include/opentelemetry/sdk/common/global_log_handler.h b/sdk/include/opentelemetry/sdk/common/global_log_handler.h index eadd408762..d11e1e0b7d 100644 --- a/sdk/include/opentelemetry/sdk/common/global_log_handler.h +++ b/sdk/include/opentelemetry/sdk/common/global_log_handler.h @@ -99,37 +99,28 @@ class GlobalLogHandler * * By default, a default LogHandler is returned. */ - static inline const nostd::shared_ptr &GetLogHandler() noexcept - { - return GetHandlerAndLevel().first; - } + static nostd::shared_ptr GetLogHandler() noexcept; /** * Changes the singleton LogHandler. * This should be called once at the start of application before creating any Provider * instance. */ - static inline void SetLogHandler(const nostd::shared_ptr &eh) noexcept - { - GetHandlerAndLevel().first = eh; - } + static void SetLogHandler(const nostd::shared_ptr &eh) noexcept; /** * Returns the singleton log level. * * By default, a default log level is returned. */ - static inline LogLevel GetLogLevel() noexcept { return GetHandlerAndLevel().second; } + static LogLevel GetLogLevel() noexcept; /** * Changes the singleton Log level. * This should be called once at the start of application before creating any Provider * instance. */ - static inline void SetLogLevel(LogLevel level) noexcept { GetHandlerAndLevel().second = level; } - -private: - static std::pair, LogLevel> &GetHandlerAndLevel() noexcept; + static void SetLogLevel(LogLevel level) noexcept; }; } // namespace internal_log @@ -142,24 +133,23 @@ OPENTELEMETRY_END_NAMESPACE * To ensure that GlobalLogHandler is the first one to be initialized (and so last to be * destroyed), it is first used inside the constructors of TraceProvider, MeterProvider * and LoggerProvider for debug logging. */ -#define OTEL_INTERNAL_LOG_DISPATCH(level, message, attributes) \ - do \ - { \ - using opentelemetry::sdk::common::internal_log::GlobalLogHandler; \ - using opentelemetry::sdk::common::internal_log::LogHandler; \ - if (level > GlobalLogHandler::GetLogLevel()) \ - { \ - break; \ - } \ - const opentelemetry::nostd::shared_ptr &log_handler = \ - GlobalLogHandler::GetLogHandler(); \ - if (!log_handler) \ - { \ - break; \ - } \ - std::stringstream tmp_stream; \ - tmp_stream << message; \ - log_handler->Handle(level, __FILE__, __LINE__, tmp_stream.str().c_str(), attributes); \ +#define OTEL_INTERNAL_LOG_DISPATCH(level, message, attributes) \ + do \ + { \ + using opentelemetry::sdk::common::internal_log::GlobalLogHandler; \ + using opentelemetry::sdk::common::internal_log::LogHandler; \ + if (level > GlobalLogHandler::GetLogLevel()) \ + { \ + break; \ + } \ + opentelemetry::nostd::shared_ptr log_handler = GlobalLogHandler::GetLogHandler(); \ + if (!log_handler) \ + { \ + break; \ + } \ + std::stringstream tmp_stream; \ + tmp_stream << message; \ + log_handler->Handle(level, __FILE__, __LINE__, tmp_stream.str().c_str(), attributes); \ } while (false); #define OTEL_INTERNAL_LOG_GET_3RD_ARG(arg1, arg2, arg3, ...) arg3 diff --git a/sdk/src/common/global_log_handler.cc b/sdk/src/common/global_log_handler.cc index f60527eee8..0883e5dfab 100644 --- a/sdk/src/common/global_log_handler.cc +++ b/sdk/src/common/global_log_handler.cc @@ -13,6 +13,38 @@ namespace common namespace internal_log { +namespace +{ +struct GlobalLogHandlerData +{ + nostd::shared_ptr handler; + LogLevel log_level; + + GlobalLogHandlerData() + : handler(nostd::shared_ptr(new DefaultLogHandler)), log_level(LogLevel::Warning) + {} + ~GlobalLogHandlerData() { is_singleton_destroyed = true; } + + GlobalLogHandlerData(const GlobalLogHandlerData &) = delete; + GlobalLogHandlerData(GlobalLogHandlerData &&) = delete; + + GlobalLogHandlerData &operator=(const GlobalLogHandlerData &) = delete; + GlobalLogHandlerData &operator=(GlobalLogHandlerData &&) = delete; + + static GlobalLogHandlerData &Instance() noexcept; + static bool is_singleton_destroyed; +}; + +bool GlobalLogHandlerData::is_singleton_destroyed = false; + +GlobalLogHandlerData &GlobalLogHandlerData::Instance() noexcept +{ + static GlobalLogHandlerData instance; + return instance; +} + +} // namespace + LogHandler::~LogHandler() {} void DefaultLogHandler::Handle(LogLevel level, @@ -57,11 +89,43 @@ void NoopLogHandler::Handle(LogLevel, const sdk::common::AttributeMap &) noexcept {} -std::pair, LogLevel> &GlobalLogHandler::GetHandlerAndLevel() noexcept +nostd::shared_ptr GlobalLogHandler::GetLogHandler() noexcept +{ + if OPENTELEMETRY_UNLIKELY_CONDITION (GlobalLogHandlerData::is_singleton_destroyed) + { + return nostd::shared_ptr(); + } + + return GlobalLogHandlerData::Instance().handler; +} + +void GlobalLogHandler::SetLogHandler(const nostd::shared_ptr &eh) noexcept +{ + if OPENTELEMETRY_UNLIKELY_CONDITION (GlobalLogHandlerData::is_singleton_destroyed) + { + return; + } + + GlobalLogHandlerData::Instance().handler = eh; +} + +LogLevel GlobalLogHandler::GetLogLevel() noexcept { - static std::pair, LogLevel> handler_and_level{ - nostd::shared_ptr(new DefaultLogHandler), LogLevel::Warning}; - return handler_and_level; + if OPENTELEMETRY_UNLIKELY_CONDITION (GlobalLogHandlerData::is_singleton_destroyed) + { + return LogLevel::None; + } + + return GlobalLogHandlerData::Instance().log_level; +} + +void GlobalLogHandler::SetLogLevel(LogLevel level) noexcept +{ + if OPENTELEMETRY_UNLIKELY_CONDITION (GlobalLogHandlerData::is_singleton_destroyed) + { + return; + } + GlobalLogHandlerData::Instance().log_level = level; } } // namespace internal_log diff --git a/sdk/test/common/BUILD b/sdk/test/common/BUILD index b08bcc0976..de4c02ec56 100644 --- a/sdk/test/common/BUILD +++ b/sdk/test/common/BUILD @@ -160,6 +160,20 @@ cc_test( ], ) +cc_test( + name = "global_log_handle_singleton_lifetime_test", + srcs = [ + "global_log_handle_singleton_lifetime_test.cc", + ], + tags = ["test"], + deps = [ + "//api", + "//sdk:headers", + "//sdk/src/common:global_log_handler", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "attributemap_hash_test", srcs = [ diff --git a/sdk/test/common/CMakeLists.txt b/sdk/test/common/CMakeLists.txt index 00ad67a5bf..fab78de90e 100644 --- a/sdk/test/common/CMakeLists.txt +++ b/sdk/test/common/CMakeLists.txt @@ -11,6 +11,7 @@ foreach( attribute_utils_test attributemap_hash_test global_log_handle_test + global_log_handle_singleton_lifetime_test env_var_test) add_executable(${testname} "${testname}.cc") diff --git a/sdk/test/common/global_log_handle_singleton_lifetime_test.cc b/sdk/test/common/global_log_handle_singleton_lifetime_test.cc new file mode 100644 index 0000000000..5b163dc6b9 --- /dev/null +++ b/sdk/test/common/global_log_handle_singleton_lifetime_test.cc @@ -0,0 +1,107 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include + +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/sdk/common/attribute_utils.h" +#include "opentelemetry/sdk/common/global_log_handler.h" + +class GlobalLogHandlerChecker +{ +public: + GlobalLogHandlerChecker() {} + ~GlobalLogHandlerChecker() + { + using opentelemetry::sdk::common::internal_log::GlobalLogHandler; + auto handle = GlobalLogHandler::GetLogHandler(); + if (handle && custom_handler_destroyed) + { + OTEL_INTERNAL_LOG_ERROR("GlobalLogHandler should be destroyed here"); + abort(); + } + std::cout << "GlobalLogHandlerChecker destroyed and check pass.\n"; + } + + void Print() { std::cout << "GlobalLogHandlerChecker constructed\n"; } + + GlobalLogHandlerChecker(const GlobalLogHandlerChecker &) = delete; + GlobalLogHandlerChecker(GlobalLogHandlerChecker &&) = delete; + GlobalLogHandlerChecker &operator=(const GlobalLogHandlerChecker &) = delete; + GlobalLogHandlerChecker &operator=(GlobalLogHandlerChecker &&) = delete; + + static bool custom_handler_destroyed; +}; +bool GlobalLogHandlerChecker::custom_handler_destroyed = false; + +namespace +{ +static GlobalLogHandlerChecker &ConstructChecker() +{ + static GlobalLogHandlerChecker checker; + return checker; +} +} // namespace + +class CustomLogHandler : public opentelemetry::sdk::common::internal_log::LogHandler +{ +public: + ~CustomLogHandler() override + { + GlobalLogHandlerChecker::custom_handler_destroyed = true; + std::cout << "Custom Gobal Log Handle destroyed\n"; + } + + void Handle(opentelemetry::sdk::common::internal_log::LogLevel level, + const char *, + int, + const char *msg, + const opentelemetry::sdk::common::AttributeMap &) noexcept override + { + if (level == opentelemetry::sdk::common::internal_log::LogLevel::Debug) + { + std::cout << "Custom Gobal Log Handle[Debug]: " << msg << "\n"; + } + else if (level == opentelemetry::sdk::common::internal_log::LogLevel::Error) + { + std::cout << "Custom Gobal Log Handle[Error]: " << msg << "\n"; + } + else if (level == opentelemetry::sdk::common::internal_log::LogLevel::Info) + { + std::cout << "Custom Gobal Log Handle[Info]: " << msg << "\n"; + } + else if (level == opentelemetry::sdk::common::internal_log::LogLevel::Warning) + { + std::cout << "Custom Gobal Log Handle[Warning]: " << msg << "\n"; + } + ++count; + } + + size_t count = 0; +}; + +TEST(GlobalLogHandleSingletonTest, Lifetime) +{ + // Setup a new static variable which will be destroyed after the global handle + ConstructChecker().Print(); + + using opentelemetry::sdk::common::internal_log::GlobalLogHandler; + using opentelemetry::sdk::common::internal_log::LogHandler; + + auto custom_log_handler = opentelemetry::nostd::shared_ptr(new CustomLogHandler{}); + opentelemetry::sdk::common::internal_log::GlobalLogHandler::SetLogHandler(custom_log_handler); + auto before_count = static_cast(custom_log_handler.get())->count; + opentelemetry::sdk::common::AttributeMap attributes = { + {"url", "https://opentelemetry.io/"}, {"content-length", 0}, {"content-type", "text/html"}}; + OTEL_INTERNAL_LOG_ERROR("Error message"); + OTEL_INTERNAL_LOG_DEBUG("Debug message. Headers:", attributes); + EXPECT_EQ(before_count + 1, static_cast(custom_log_handler.get())->count); + + { + auto handle = GlobalLogHandler::GetLogHandler(); + EXPECT_TRUE(!!handle); + } +}