diff --git a/src/eckit/log/Channel.cc b/src/eckit/log/Channel.cc index 0a3d776a8..4ceeec7a1 100644 --- a/src/eckit/log/Channel.cc +++ b/src/eckit/log/Channel.cc @@ -17,18 +17,17 @@ namespace eckit { //---------------------------------------------------------------------------------------------------------------------- -Channel::Channel(LogTarget* target) : - std::ostream(new ChannelBuffer()), buffer_(dynamic_cast(rdbuf())) { +Channel::Channel(ChannelBuffer* buffer): std::ostream(buffer), buffer_(buffer) { } + +Channel::Channel(LogTarget* target): Channel(new ChannelBuffer()) { ASSERT(buffer_); - if (target) { - buffer_->setTarget(target); - } - // std::cerr << "Channel::Channel()" << std::endl; + if (target) { buffer_->setTarget(target); } } +Channel::Channel(VoidBuffer* dummy): std::ostream(nullptr), buffer_(dummy) { } Channel::~Channel() { - // std::cerr << "Channel::~Channel() " << *this << std::endl; + buffer_->sync(); delete buffer_; } @@ -69,7 +68,6 @@ void Channel::addStream(std::ostream& out) { buffer_->addStream(out); } - void Channel::setFile(const std::string& path) { buffer_->setFile(path); } @@ -96,4 +94,8 @@ void Channel::unindent() { //---------------------------------------------------------------------------------------------------------------------- +EmptyChannel::EmptyChannel(): Channel(new VoidBuffer()) { } + +//---------------------------------------------------------------------------------------------------------------------- + } // namespace eckit diff --git a/src/eckit/log/Channel.h b/src/eckit/log/Channel.h index 9b8e1792f..c0c3f2f1c 100644 --- a/src/eckit/log/Channel.h +++ b/src/eckit/log/Channel.h @@ -20,6 +20,7 @@ namespace eckit { +class VoidBuffer; class ChannelBuffer; class LogTarget; @@ -29,8 +30,7 @@ typedef void (*channel_callback_t)(void* data, const char* msg); /// Output channel that is an std::ostream but more functional -class Channel : public std::ostream, private NonCopyable { - +class Channel: public std::ostream, private NonCopyable { public: // methods Channel(LogTarget* = 0); @@ -39,7 +39,7 @@ class Channel : public std::ostream, private NonCopyable { bool operator!() const; operator bool() const; - void indent(const char* prefix = ""); + void indent(const char* space = ""); void unindent(); void setStream(std::ostream& out); @@ -51,40 +51,56 @@ class Channel : public std::ostream, private NonCopyable { void setCallback(channel_callback_t cb, void* data = 0); void addCallback(channel_callback_t cb, void* data = 0); - void setTarget(LogTarget*); - void addTarget(LogTarget*); + void setTarget(LogTarget* target); + void addTarget(LogTarget* target); void reset(); -private: // members +protected: // methods + explicit Channel(VoidBuffer* dummy); + +private: // methods + explicit Channel(ChannelBuffer* buffer); + + virtual void print(std::ostream& s) const; + friend std::ostream& operator<<(std::ostream& os, const Channel& c) { c.print(os); return os; } - void print(std::ostream& s) const; - - ChannelBuffer* buffer_; +private: // members + ChannelBuffer* buffer_ {nullptr}; friend class Log; }; - //---------------------------------------------------------------------------------------------------------------------- - class AutoIndent { - Channel& channel_; public: - AutoIndent(Channel& channel, const char* prefix = "") : - channel_(channel) { channel_.indent(prefix); } + AutoIndent(Channel& channel, const char* prefix = ""): channel_(channel) { channel_.indent(prefix); } ~AutoIndent() { channel_.unindent(); } }; //---------------------------------------------------------------------------------------------------------------------- +class EmptyChannel: public Channel { +public: // methods + EmptyChannel(); + + ~EmptyChannel() = default; + +private: // methods + void print(std::ostream& s) const override { s << "EmptyChannel()"; } + + friend class Log; +}; + +//---------------------------------------------------------------------------------------------------------------------- + } // namespace eckit #endif diff --git a/src/eckit/log/ChannelBuffer.cc b/src/eckit/log/ChannelBuffer.cc index d5b20cef4..098bece6b 100644 --- a/src/eckit/log/ChannelBuffer.cc +++ b/src/eckit/log/ChannelBuffer.cc @@ -23,20 +23,25 @@ namespace eckit { //---------------------------------------------------------------------------------------------------------------------- -ChannelBuffer::ChannelBuffer(std::size_t size) : std::streambuf(), target_(0), buffer_(size) { - ASSERT(size); +ChannelBuffer::ChannelBuffer(const std::size_t size): buffer_(size) { + init(); +} + +void ChannelBuffer::init() { setp(buffer_.data(), buffer_.data() + buffer_.size()); } ChannelBuffer::~ChannelBuffer() { - reset(); + if (target_) { + target_->detach(); + target_ = nullptr; + } } bool ChannelBuffer::active() const { return target_ != 0; } - void ChannelBuffer::setTarget(LogTarget* target) { ASSERT(target); @@ -72,7 +77,7 @@ bool ChannelBuffer::dumpBuffer() { // Explicitly check that `pptr()` is not larger than end of buffer. Racecondition can end up adding larger values. target_->write(buffer_.data(), std::min(pptr(), buffer_.data() + buffer_.size())); } - setp(buffer_.data(), buffer_.data() + buffer_.size()); + init(); return true; } @@ -108,11 +113,11 @@ void ChannelBuffer::setStream(std::ostream& out) { setTarget(new OStreamTarget(out)); } -void ChannelBuffer::addFile(const std::string& path, size_t bufferSize) { +void ChannelBuffer::addFile(const std::string& path, const std::size_t bufferSize) { setTarget(new TeeTarget(target_, new FileTarget(path, bufferSize))); } -void ChannelBuffer::setFile(const std::string& path, size_t bufferSize) { +void ChannelBuffer::setFile(const std::string& path, const std::size_t bufferSize) { setTarget(new FileTarget(path, bufferSize)); } @@ -144,4 +149,26 @@ void ChannelBuffer::print(std::ostream& s) const { //---------------------------------------------------------------------------------------------------------------------- +VoidBuffer::VoidBuffer(): ChannelBuffer(0) { } + +VoidBuffer::~VoidBuffer() = default; + +bool VoidBuffer::dumpBuffer() { + return true; +} + +std::streambuf::int_type VoidBuffer::overflow(std::streambuf::int_type ch) { + return ch; +} + +std::streambuf::int_type VoidBuffer::sync() { + return 0; +} + +void VoidBuffer::print(std::ostream& os) const { + os << "VoidBuffer"; +} + +//---------------------------------------------------------------------------------------------------------------------- + } // namespace eckit diff --git a/src/eckit/log/ChannelBuffer.h b/src/eckit/log/ChannelBuffer.h index 20990d305..2265d4c3e 100644 --- a/src/eckit/log/ChannelBuffer.h +++ b/src/eckit/log/ChannelBuffer.h @@ -16,7 +16,10 @@ #ifndef eckit_log_ChannelBuffer_h #define eckit_log_ChannelBuffer_h +#include +#include #include +#include #include #include "eckit/log/Channel.h" @@ -28,15 +31,12 @@ namespace eckit { class LogTarget; -/// Stream buffer to be usedby Channel +/// Stream buffer to be used by Channel class ChannelBuffer : public std::streambuf, private NonCopyable { +private: // types + static constexpr const std::size_t DEFAULT_SIZE = 1024; private: // methods - /// constructor, taking ownership of stream - ChannelBuffer(std::size_t size = 1024); - - ~ChannelBuffer() override; - bool active() const; void reset(); @@ -50,13 +50,19 @@ class ChannelBuffer : public std::streambuf, private NonCopyable { void setStream(std::ostream& out); void addStream(std::ostream& out); - void setFile(const std::string& path, size_t bufferSize = 4 * 1024); - void addFile(const std::string& path, size_t bufferSize = 4 * 1024); + void setFile(const std::string& path, std::size_t bufferSize = 4 * 1024); + void addFile(const std::string& path, std::size_t bufferSize = 4 * 1024); void setCallback(channel_callback_t cb, void* data = 0); void addCallback(channel_callback_t cb, void* data = 0); protected: // methods + ChannelBuffer(std::size_t size = DEFAULT_SIZE); + + ~ChannelBuffer() override; + + void init(); + /// override this to change buffer behavior /// @returns true if no error occured virtual bool dumpBuffer(); @@ -69,24 +75,45 @@ class ChannelBuffer : public std::streambuf, private NonCopyable { /// @see dumpBuffer int_type sync() override; -protected: // members - LogTarget* target_; - - std::vector buffer_; - -private: friend std::ostream& operator<<(std::ostream& os, const ChannelBuffer& c) { c.print(os); return os; } - void print(std::ostream& s) const; + virtual void print(std::ostream& s) const; + +protected: // members + LogTarget* target_ {nullptr}; + + std::vector buffer_; friend class Channel; }; //---------------------------------------------------------------------------------------------------------------------- +/// Channel buffer that voidify output streams +class VoidBuffer: public ChannelBuffer { +private: // methods + VoidBuffer(); + + ~VoidBuffer(); + +protected: // methods + bool dumpBuffer() override; + + int_type overflow(int_type ch) override; + + int_type sync() override; + +private: // methods + void print(std::ostream& os) const override; + + friend class EmptyChannel; +}; + +//---------------------------------------------------------------------------------------------------------------------- + } // namespace eckit #endif diff --git a/src/eckit/log/Log.cc b/src/eckit/log/Log.cc index acb07dc24..ce7dd22c5 100644 --- a/src/eckit/log/Log.cc +++ b/src/eckit/log/Log.cc @@ -8,11 +8,12 @@ * does it submit to any jurisdiction. */ -#include +#include #include +#include +#include +#include -#include "eckit/config/LibEcKit.h" -#include "eckit/exception/Exceptions.h" #include "eckit/log/Channel.h" #include "eckit/log/FileTarget.h" #include "eckit/log/Log.h" @@ -24,7 +25,6 @@ #include "eckit/runtime/Main.h" #include "eckit/system/Library.h" #include "eckit/system/LibraryManager.h" -#include "eckit/thread/AutoLock.h" #include "eckit/thread/ThreadSingleton.h" #include "eckit/utils/Translator.h" @@ -81,6 +81,12 @@ static void handle_strerror_r(std::ostream& s, int e, ...) { //---------------------------------------------------------------------------------------------------------------------- +namespace { +thread_local EmptyChannel emptyChannel; +} // namespace + +//---------------------------------------------------------------------------------------------------------------------- + struct CreateStatusChannel { Channel* operator()() { return new Channel(new StatusTarget()); } }; @@ -99,105 +105,94 @@ std::ostream& Log::message() { return x.instance(); } - -struct CreateLogChannel { - - virtual Channel* createChannel() = 0; - - Channel* operator()() { - try { - return createChannel(); - } - catch (std::exception& e) { - std::cerr << "Exception caught when creating channel: " << e.what() << std::endl; - return new Channel(new OStreamTarget(std::cout)); - } - } -}; - -struct CreateMetricsChannel : public CreateLogChannel { - virtual Channel* createChannel() { return new Channel(Main::instance().createMetricsLogTarget()); } -}; - Channel& Log::metrics() { if (!Main::ready()) { - static Channel empty(new PrefixTarget("PRE-MAIN-METRICS", new OStreamTarget(std::cout))); - return empty; + thread_local Channel preMainMetrics(new PrefixTarget("PRE-MAIN-METRICS", new OStreamTarget(std::cout))); + return preMainMetrics; } - static ThreadSingleton x; - return x.instance(); -} + if (Main::finalised()) { + thread_local Channel postMainMetrics(new PrefixTarget("POST-MAIN-METRICS", new OStreamTarget(std::cout))); + return postMainMetrics; + } -struct CreateInfoChannel : public CreateLogChannel { - virtual Channel* createChannel() { return new Channel(Main::instance().createInfoLogTarget()); } -}; + thread_local Channel mainMetrics(Main::instance().createMetricsLogTarget()); + return mainMetrics; +} Channel& Log::info() { if (!Main::ready()) { - static Channel empty(new PrefixTarget("PRE-MAIN-INFO", new OStreamTarget(std::cout))); - return empty; + thread_local Channel preMainInfo(new PrefixTarget("PRE-MAIN-INFO", new OStreamTarget(std::cout))); + return preMainInfo; } - static ThreadSingleton x; - return x.instance(); -} -struct CreateErrorChannel : public CreateLogChannel { - virtual Channel* createChannel() { return new Channel(Main::instance().createErrorLogTarget()); } -}; + if (Main::finalised()) { + // This may still cause a SEGFAULT due to missing destruction order at the end of library life time + // - only solution is then to use an empty channel which is harmless in a destructed state + thread_local Channel postMainInfo(new PrefixTarget("POST-MAIN-INFO", new OStreamTarget(std::cout))); + return postMainInfo; + } + + thread_local Channel mainInfo(Main::instance().createInfoLogTarget()); + return mainInfo; +} Channel& Log::error() { if (!Main::ready()) { - static Channel empty(new PrefixTarget("PRE-MAIN-ERROR", new OStreamTarget(std::cout))); - return empty; + thread_local Channel preMainError(new PrefixTarget("PRE-MAIN-ERROR", new OStreamTarget(std::cout))); + return preMainError; } - static ThreadSingleton x; - return x.instance(); -} -struct CreateWarningChannel : public CreateLogChannel { - virtual Channel* createChannel() { return new Channel(Main::instance().createWarningLogTarget()); } -}; + if (Main::finalised()) { + // This may still cause a SEGFAULT due to missing destruction order at the end of library life time + // - only solution is then to use an empty channel which is harmless in a destructed state + thread_local Channel postMainError(new PrefixTarget("POST-MAIN-ERROR", new OStreamTarget(std::cout))); + return postMainError; + } + + thread_local Channel mainError(Main::instance().createErrorLogTarget()); + return mainError; +} Channel& Log::warning() { if (!Main::ready()) { - static Channel empty(new PrefixTarget("PRE-MAIN-WARNING", new OStreamTarget(std::cout))); - return empty; + thread_local Channel preMainWarning(new PrefixTarget("PRE-MAIN-WARNING", new OStreamTarget(std::cout))); + return preMainWarning; } - static ThreadSingleton x; - return x.instance(); -} -struct CreateDebugChannel : public CreateLogChannel { - virtual Channel* createChannel() { return new Channel(Main::instance().createDebugLogTarget()); } -}; - -Channel& Log::debug() { + if (Main::finalised()) { + // This may still cause a SEGFAULT due to missing destruction order at the end of library life time + // - only solution is then to use an empty channel which is harmless in a destructed state + thread_local Channel postMainWarning(new PrefixTarget("POST-MAIN-WARNING", new OStreamTarget(std::cout))); + return postMainWarning; + } + thread_local Channel mainWarning(Main::instance().createWarningLogTarget()); + return mainWarning; +} +Channel& Log::debug() { if (!Main::ready()) { - - const char* e = getenv("DEBUG"); - - if (e && bool(Translator()(e))) { - static Channel empty(new PrefixTarget("PRE-MAIN-DEBUG", new OStreamTarget(std::cout))); - return empty; + if (const char* e = ::getenv("DEBUG"); e && bool(Translator()(e))) { + thread_local Channel preMainDebug(new PrefixTarget("PRE-MAIN-DEBUG", new OStreamTarget(std::cout))); + return preMainDebug; } - static Channel empty; - return empty; + return emptyChannel; } - if (!Main::instance().debug_) { - static ThreadSingleton empty; - return empty.instance(); - } + if (!Main::instance().debug_) { return emptyChannel; } + if (Main::finalised()) { + // This may still cause a SEGFAULT due to missing destruction order at the end of library life time + // - only solution is then to use an empty channel which is harmless in a destructed state + thread_local Channel postMainDebug(new PrefixTarget("POST-MAIN-DEBUG", new OStreamTarget(std::cout))); + return postMainDebug; + } - static ThreadSingleton x; - return x.instance(); + thread_local Channel debugChannel(Main::instance().createDebugLogTarget()); + return debugChannel; } - std::ostream& Log::panic() { try { return Log::error(); diff --git a/src/eckit/log/Log.h b/src/eckit/log/Log.h index 0a4a28bbf..b307dd6bb 100644 --- a/src/eckit/log/Log.h +++ b/src/eckit/log/Log.h @@ -19,6 +19,7 @@ #include "eckit/deprecated.h" #include "eckit/log/Channel.h" #include "eckit/log/UserChannel.h" +#include "eckit/memory/NonCopyable.h" namespace eckit { @@ -29,8 +30,7 @@ typedef void (*channel_callback_t)(void* data, const char* msg); /// Singleton holding global streams for logging /// -class Log { - +class Log: private NonCopyable { public: // types /// Output formats enum @@ -104,9 +104,8 @@ class Log { static void print(std::ostream& os); -private: // methods - Log(); ///< Private, non-instanciatable class - ~Log(); ///< Private, non-instanciatable class + Log() = delete; ///< non-instanciatable class + ~Log() = delete; ///< non-instanciatable class }; //---------------------------------------------------------------------------------------------------------------------- diff --git a/src/eckit/runtime/Main.cc b/src/eckit/runtime/Main.cc index 1cec559c2..103a4e8c6 100644 --- a/src/eckit/runtime/Main.cc +++ b/src/eckit/runtime/Main.cc @@ -12,6 +12,7 @@ #include #include #include +#include #include "eckit/bases/Loader.h" #include "eckit/config/Resource.h" @@ -24,7 +25,6 @@ #include "eckit/system/LibraryManager.h" #include "eckit/system/SystemInfo.h" #include "eckit/thread/AutoLock.h" -#include "eckit/thread/Mutex.h" #include "eckit/thread/StaticMutex.h" #include "eckit/utils/Translator.h" @@ -32,6 +32,11 @@ namespace eckit { //---------------------------------------------------------------------------------------------------------------------- +namespace { +// atomic provides implicit lock for finalised_ only +std::atomic_bool finalised_ {false}; +} // namespace + static StaticMutex local_mutex; static Main* instance_ = nullptr; @@ -39,6 +44,8 @@ static Main* instance_ = nullptr; Main::Main(int argc, char** argv, const char* homeenv) : taskID_(-1), argc_(argc), argv_(argv), home_("/"), debug_(false) { + + std::atexit(finalise); if (instance_) { std::cerr << "Attempting to create a new instance of Main()" << std::endl; @@ -210,6 +217,14 @@ void Main::initialise(int argc, char** argv, const char* homeenv) { } } +bool Main::finalised() { + return finalised_; +} + +void Main::finalise() { + finalised_ = true; +} + bool Main::debug() const { return debug_; } diff --git a/src/eckit/runtime/Main.h b/src/eckit/runtime/Main.h index 629658975..d121fd4a2 100644 --- a/src/eckit/runtime/Main.h +++ b/src/eckit/runtime/Main.h @@ -63,6 +63,14 @@ class Main : private NonCopyable { // To be used before main() to check if the instance is ready static bool ready(); + /// Check if the Main object has been finalised + /// Without explicit finalise(), calling Main::debug() in dtor of static global objects may cause segaults. + static bool finalised(); + + /// Finalise the Main object. + /// Without explicit finalise(), calling Main::debug() in dtor of static global objects may cause segaults. + static void finalise(); + // Check if debugging was set to on (either through environment variable "DEBUG=1", // or command-line argument "--debug" or "-debug". virtual bool debug() const; diff --git a/src/eckit/system/Library.cc b/src/eckit/system/Library.cc index 92ac5e8d6..e05a0db89 100644 --- a/src/eckit/system/Library.cc +++ b/src/eckit/system/Library.cc @@ -14,27 +14,30 @@ #include #include -#include +#include #include "eckit/system/Library.h" +#include "eckit/config/LibEcKit.h" #include "eckit/config/Resource.h" #include "eckit/config/YAMLConfiguration.h" #include "eckit/exception/Exceptions.h" #include "eckit/filesystem/LocalPathName.h" +#include "eckit/log/Channel.h" #include "eckit/log/Log.h" -#include "eckit/log/OStreamTarget.h" #include "eckit/log/PrefixTarget.h" #include "eckit/os/System.h" #include "eckit/system/LibraryManager.h" -#include "eckit/system/SystemInfo.h" #include "eckit/thread/AutoLock.h" #include "eckit/thread/Mutex.h" -#include "eckit/thread/ThreadSingleton.h" #include "eckit/utils/Translator.h" namespace eckit::system { +namespace { +thread_local EmptyChannel emptyChannel; +} // namespace + //---------------------------------------------------------------------------------------------------------------------- Library::Library(const std::string& name) : name_(name), prefix_(name), debug_(false) { @@ -109,22 +112,17 @@ std::string Library::libraryPath() const { return libraryPath_; } - Channel& Library::debugChannel() const { - static ThreadSingleton>> debugChannels; - - auto it = debugChannels.instance().find(this); - if (it != debugChannels.instance().end()) { - return *it->second; + if (const AutoLock lock(mutex_); debug_) { + if (!debugChannel_) { + debugChannel_ = std::make_unique(new PrefixTarget(prefix_ + "_DEBUG")); + } + return *debugChannel_; } - return *debugChannels.instance() - .emplace(this, debug_ ? std::make_unique(new PrefixTarget(prefix_ + "_DEBUG")) // - : std::make_unique()) // - .first->second.get(); + return emptyChannel; } - const Configuration& Library::configuration() const { AutoLock lock(mutex_); diff --git a/src/eckit/system/Library.h b/src/eckit/system/Library.h index 2d5e176e0..e0634d700 100644 --- a/src/eckit/system/Library.h +++ b/src/eckit/system/Library.h @@ -67,7 +67,7 @@ class Library : private eckit::NonCopyable { /// @deprecated Use LibraryManager instead static std::vector list(); /// @deprecated Use LibraryManager instead - static void list(std::ostream& s); + static void list(std::ostream& out); /// @deprecated Use LibraryManager instead static bool exists(const std::string& name); /// @deprecated Use LibraryManager instead @@ -103,6 +103,8 @@ class Library : private eckit::NonCopyable { mutable std::string libraryPath_; mutable std::string prefixDirectory_; + mutable std::unique_ptr debugChannel_; + mutable std::unique_ptr configuration_; }; diff --git a/src/eckit/testing/Test.h b/src/eckit/testing/Test.h index da725534c..4640d5e81 100644 --- a/src/eckit/testing/Test.h +++ b/src/eckit/testing/Test.h @@ -401,6 +401,7 @@ int run_tests_main(std::vector& tests, int argc, char* argv[], bool initEc int failures = run(tests); eckit::Log::info() << failures << " tests failed out of " << tests.size() << "." << std::endl; + return failures; } diff --git a/tests/log/CMakeLists.txt b/tests/log/CMakeLists.txt index 58f9346bc..04b558cf2 100644 --- a/tests/log/CMakeLists.txt +++ b/tests/log/CMakeLists.txt @@ -15,6 +15,11 @@ ecbuild_add_test( TARGET eckit_test_log_threads ENVIRONMENT _TEST_ECKIT_HOME=/tmp/$ENV{USER} LIBS eckit ) +ecbuild_add_test( TARGET eckit_test_log_threads_hammer + SOURCES test_log_threads_hammer.cc + ENVIRONMENT _TEST_ECKIT_HOME=/tmp/$ENV{USER} + LIBS eckit ) + ecbuild_add_test( TARGET eckit_test_log_colour SOURCES test_colour.cc LIBS eckit ) @@ -30,4 +35,4 @@ ecbuild_add_test( TARGET eckit_test_log_user_channels ecbuild_add_test( TARGET eckit_test_syslog SOURCES test_syslog.cc - LIBS eckit ) \ No newline at end of file + LIBS eckit ) diff --git a/tests/log/test_log_threads_hammer.cc b/tests/log/test_log_threads_hammer.cc new file mode 100644 index 000000000..ed41af9d6 --- /dev/null +++ b/tests/log/test_log_threads_hammer.cc @@ -0,0 +1,98 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + +/// @file test_log_threads_hammer.cc +/// @author Metin Cakircali +/// @date Jun 2024 + +#include "eckit/config/LibEcKit.h" +#include "eckit/log/Channel.h" +#include "eckit/log/Log.h" +#include "eckit/testing/Test.h" + +#include +#include +#include +#include + +using namespace eckit; +using namespace eckit::testing; + +namespace eckit::test { + +//---------------------------------------------------------------------------------------------------------------------- +// HELPERS + +namespace { + +class SingletonTester { + // comment out for worst case scenario + // std::ostream& outDebug; + // std::ostream& outDebugLib; + // SingletonTester(): outDebug(Log::debug()), outDebugLib(Log::debug()) { + SingletonTester() { + Log::debug() << "--> SingletonTester()" << std::endl; + Log::debug() << "--> SingletonTester(LIB)" << std::endl; + } + ~SingletonTester() { + Log::debug() << "--> ~SingletonTester()" << std::endl; + Log::debug() << "--> ~SingletonTester(LIB)" << std::endl; + } + +public: + static auto instance() -> SingletonTester& { + static SingletonTester tester; + Log::info() << "SingletonTester::instance()" << std::endl; + Log::warning() << "SingletonTester::instance()" << std::endl; + Log::error() << "SingletonTester::instance()" << std::endl; + Log::debug() << "SingletonTester::instance()" << std::endl; + Log::debug() << "SingletonTester::instance()" << std::endl; + return tester; + } +}; + +auto& tester = SingletonTester::instance(); + +constexpr const auto numThreads = 32; +constexpr const auto logSize = 1000; + +auto makeTestLog(const std::string& channel, const int number) -> std::string { + return "channel: " + channel + " #" + std::to_string(number) + '\n'; +} + +void runLoggers() { + for (auto i = 0; i < logSize; i++) { Log::info() << makeTestLog("info-test", i); } + for (auto i = 0; i < logSize; i++) { Log::warning() << makeTestLog("warn-test", i); } + for (auto i = 0; i < logSize; i++) { Log::error() << makeTestLog("warn-test", i); } + for (auto i = 0; i < logSize; i++) { Log::debug() << makeTestLog("debug-test", i); } + for (auto i = 0; i < logSize; i++) { Log::debug() << makeTestLog("lib-debug-test", i); } +} + +} // namespace + +//---------------------------------------------------------------------------------------------------------------------- + +CASE("Log: output " + std::to_string(logSize) + " logs by " + std::to_string(numThreads) + " threads") { + std::list threads; + + for (auto i = 0; i < numThreads; i++) { EXPECT_NO_THROW(threads.emplace_back(runLoggers)); } + + for (auto&& thread : threads) { thread.join(); } +} + +//---------------------------------------------------------------------------------------------------------------------- + +} // namespace eckit::test + +int main(int argc, char** argv) { + auto count = 0; + for (auto i = 0; i < 5; i++) { count += run_tests(argc, argv); } + return count; +}