Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SDK] Fix lifetime of GlobalLogHandler #3221

Merged
52 changes: 21 additions & 31 deletions sdk/include/opentelemetry/sdk/common/global_log_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,37 +99,28 @@ class GlobalLogHandler
*
* By default, a default LogHandler is returned.
*/
static inline const nostd::shared_ptr<LogHandler> &GetLogHandler() noexcept
{
return GetHandlerAndLevel().first;
}
static nostd::shared_ptr<LogHandler> 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<LogHandler> &eh) noexcept
{
GetHandlerAndLevel().first = eh;
}
static void SetLogHandler(const nostd::shared_ptr<LogHandler> &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<nostd::shared_ptr<LogHandler>, LogLevel> &GetHandlerAndLevel() noexcept;
static void SetLogLevel(LogLevel level) noexcept;
};

} // namespace internal_log
Expand All @@ -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<LogHandler> &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<LogHandler> 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
Expand Down
72 changes: 68 additions & 4 deletions sdk/src/common/global_log_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,38 @@ namespace common
namespace internal_log
{

namespace
{
struct GlobalLogHandlerData
{
nostd::shared_ptr<LogHandler> handler;
LogLevel log_level;

GlobalLogHandlerData()
: handler(nostd::shared_ptr<LogHandler>(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,
Expand Down Expand Up @@ -57,11 +89,43 @@ void NoopLogHandler::Handle(LogLevel,
const sdk::common::AttributeMap &) noexcept
{}

std::pair<nostd::shared_ptr<LogHandler>, LogLevel> &GlobalLogHandler::GetHandlerAndLevel() noexcept
nostd::shared_ptr<LogHandler> GlobalLogHandler::GetLogHandler() noexcept
{
if OPENTELEMETRY_UNLIKELY_CONDITION (GlobalLogHandlerData::is_singleton_destroyed)
{
return nostd::shared_ptr<LogHandler>();
}

return GlobalLogHandlerData::Instance().handler;
}

void GlobalLogHandler::SetLogHandler(const nostd::shared_ptr<LogHandler> &eh) noexcept
{
if OPENTELEMETRY_UNLIKELY_CONDITION (GlobalLogHandlerData::is_singleton_destroyed)
{
return;
}

GlobalLogHandlerData::Instance().handler = eh;
}

LogLevel GlobalLogHandler::GetLogLevel() noexcept
{
static std::pair<nostd::shared_ptr<LogHandler>, LogLevel> handler_and_level{
nostd::shared_ptr<LogHandler>(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
Expand Down
14 changes: 14 additions & 0 deletions sdk/test/common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
1 change: 1 addition & 0 deletions sdk/test/common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
107 changes: 107 additions & 0 deletions sdk/test/common/global_log_handle_singleton_lifetime_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#include <gtest/gtest.h>
#include <cstdlib>
#include <cstring>
#include <iostream>

#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<LogHandler>(new CustomLogHandler{});
opentelemetry::sdk::common::internal_log::GlobalLogHandler::SetLogHandler(custom_log_handler);
auto before_count = static_cast<CustomLogHandler *>(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<CustomLogHandler *>(custom_log_handler.get())->count);

{
auto handle = GlobalLogHandler::GetLogHandler();
EXPECT_TRUE(!!handle);
}
}
Loading