From f5438d823290a704802bf06edb458e44f045dc88 Mon Sep 17 00:00:00 2001 From: KjellKod Date: Sat, 8 Mar 2014 23:33:49 -0700 Subject: [PATCH] Thanks to Rajesh to push for g3log support for "runtime loading of dynamic libraries" (that will use g3log, initiated in main) Thanks to Dmitry (d-led) for this working proof-of-concept of this at: https://github.com/d-led/g2log-dll --HG-- rename : g2log/src/g2LogMessageBuilder.cpp => g2log/src/g2logmessagecapture.cpp rename : g2log/src/g2LogMessageBuilder.hpp => g2log/src/g2logmessagecapture.hpp --- g2log/CMakeLists.txt | 43 +++--- g2log/src/crashhandler_unix.cpp | 39 +++-- g2log/src/crashhandler_win.cpp | 144 +++++++++--------- g2log/src/g2LogMessageBuilder.cpp | 99 ------------ g2log/src/g2LogMessageBuilder.hpp | 51 ------- g2log/src/g2log.cpp | 85 +++++++---- g2log/src/g2log.hpp | 95 ++++++------ g2log/src/g2loglevels.cpp | 79 +++++----- g2log/src/g2loglevels.hpp | 75 +++++---- g2log/src/g2logmessage.cpp | 32 +--- g2log/src/g2logmessagecapture.cpp | 13 ++ g2log/src/g2logmessagecapture.hpp | 92 +++++++++++ .../test_linux_dynamic_loaded_sharedlib.cpp | 43 ++++++ g2log/test_unit/tester_sharedlib.cpp | 28 ++++ g2log/test_unit/tester_sharedlib.h | 14 ++ g2log/test_unit/testing_helpers.cpp | 77 ++++------ 16 files changed, 500 insertions(+), 509 deletions(-) delete mode 100644 g2log/src/g2LogMessageBuilder.cpp delete mode 100644 g2log/src/g2LogMessageBuilder.hpp create mode 100644 g2log/src/g2logmessagecapture.cpp create mode 100644 g2log/src/g2logmessagecapture.hpp create mode 100644 g2log/test_unit/test_linux_dynamic_loaded_sharedlib.cpp create mode 100644 g2log/test_unit/tester_sharedlib.cpp create mode 100644 g2log/test_unit/tester_sharedlib.h diff --git a/g2log/CMakeLists.txt b/g2log/CMakeLists.txt index b192381..cd8ec6f 100644 --- a/g2log/CMakeLists.txt +++ b/g2log/CMakeLists.txt @@ -135,9 +135,10 @@ ENDIF() # Create the g2log library include_directories(${LOG_SRC}) MESSAGE(" g3logger files: [${SRC_FILES}]") - add_library(lib_g3logger ${SRC_FILES}) - set_target_properties(lib_g3logger PROPERTIES LINKER_LANGUAGE CXX) - + add_library(g3logger ${SRC_FILES}) + set_target_properties(g3logger PROPERTIES LINKER_LANGUAGE CXX) + add_library(g3logger_shared SHARED ${SRC_FILES}) + set_target_properties(g3logger_shared PROPERTIES LINKER_LANGUAGE CXX) # ============================================================================ @@ -197,8 +198,8 @@ ENDIF() include_directories (${DIR_EXAMPLE}) add_executable(g2log-FATAL-contract ${DIR_EXAMPLE}/main_contract.cpp) add_executable(g2log-FATAL-sigsegv ${DIR_EXAMPLE}/main_sigsegv.cpp) - target_link_libraries(g2log-FATAL-contract lib_g3logger ${PLATFORM_LINK_LIBRIES}) - target_link_libraries(g2log-FATAL-sigsegv lib_g3logger ${PLATFORM_LINK_LIBRIES}) + target_link_libraries(g2log-FATAL-contract g3logger ${PLATFORM_LINK_LIBRIES}) + target_link_libraries(g2log-FATAL-sigsegv g3logger ${PLATFORM_LINK_LIBRIES}) endif (USE_SIMPLE_EXAMPLE) @@ -213,13 +214,13 @@ ENDIF() ${DIR_PERFORMANCE}/main_threaded_mean.cpp ${DIR_PERFORMANCE}/performance.h) # Turn on G2LOG performance flag set_target_properties(g2log-performance-threaded_mean PROPERTIES COMPILE_DEFINITIONS "G2LOG_PERFORMANCE=1") - target_link_libraries(g2log-performance-threaded_mean lib_g3logger ${PLATFORM_LINK_LIBRIES}) + target_link_libraries(g2log-performance-threaded_mean g3logger ${PLATFORM_LINK_LIBRIES}) # WORST CASE PERFORMANCE TEST add_executable(g2log-performance-threaded_worst ${DIR_PERFORMANCE}/main_threaded_worst.cpp ${DIR_PERFORMANCE}/performance.h) # Turn on G2LOG performance flag set_target_properties(g2log-performance-threaded_worst PROPERTIES COMPILE_DEFINITIONS "G2LOG_PERFORMANCE=1") - target_link_libraries(g2log-performance-threaded_worst lib_g3logger ${PLATFORM_LINK_LIBRIES}) + target_link_libraries(g2log-performance-threaded_worst g3logger ${PLATFORM_LINK_LIBRIES}) endif (USE_G2LOG_PERFORMANCE) @@ -277,11 +278,6 @@ ENDIF() SET(all_tests ${all_tests} ${DIR_UNIT_TEST}/${test}.cpp ) IF(${test} STREQUAL "test_filechange") add_executable(${test} ${DIR_UNIT_TEST}/${test}.cpp ${helper}) - - #ELSEIF(${test} STREQUAL "test_sink") - # add_executable(${test} ${DIR_UNIT_TEST}/${test}.cpp ${helper}) - # MESSAGE("*****************************************************") - ELSE() add_executable(${test} ../test_main/test_main.cpp ${DIR_UNIT_TEST}/${test}.cpp ${helper}) ENDIF(${test} STREQUAL "test_filechange") @@ -291,21 +287,24 @@ ENDIF() IF( NOT(MSVC)) set_target_properties(${test} PROPERTIES COMPILE_FLAGS "-isystem -pthread ") ENDIF( NOT(MSVC)) - target_link_libraries(${test} lib_g3logger gtest_170_lib ${PLATFORM_LINK_LIBRIES}) + target_link_libraries(${test} g3logger gtest_170_lib ${PLATFORM_LINK_LIBRIES}) ENDFOREACH(test) + + # + # Test for Linux, runtime loading of dynamic libraries + # + if(NOT WIN32) + add_library(tester_sharedlib SHARED ${DIR_UNIT_TEST}/tester_sharedlib.h ${DIR_UNIT_TEST}/tester_sharedlib.cpp) + target_link_libraries(tester_sharedlib g3logger_shared) + add_executable(test_dynamic_loaded_shared_lib ../test_main/test_main.cpp ${DIR_UNIT_TEST}/test_linux_dynamic_loaded_sharedlib.cpp) + set_target_properties(test_dynamic_loaded_shared_lib PROPERTIES COMPILE_DEFINITIONS "GTEST_HAS_TR1_TUPLE=0") + set_target_properties(test_dynamic_loaded_shared_lib PROPERTIES COMPILE_DEFINITIONS "GTEST_HAS_RTTI=0") + target_link_libraries(test_dynamic_loaded_shared_lib -ldl g3logger_shared gtest_170_lib ) + endif (NOT WIN32) - - - -# ... in progress -# add_executable(test_ALL ${all_tests}) -# set_target_properties(g2log-unit_test_all PROPERTIES COMPILE_DEFINITIONS "GTEST_HAS_TR1_TUPLE=0") -# set_target_properties(g2log-unit_test_all PROPERTIES COMPILE_DEFINITIONS "GTEST_HAS_RTTI=0") -# target_link_libraries(${test_ALL} lib_g3logger gtest_170_lib ${PLATFORM_LINK_LIBRIES}) - endif (USE_G2LOG_UNIT_TEST) diff --git a/g2log/src/crashhandler_unix.cpp b/g2log/src/crashhandler_unix.cpp index 214ef46..96e8df3 100644 --- a/g2log/src/crashhandler_unix.cpp +++ b/g2log/src/crashhandler_unix.cpp @@ -6,7 +6,7 @@ #include "crashhandler.hpp" #include "g2logmessage.hpp" -#include "g2LogMessageBuilder.hpp" +#include "g2logmessagecapture.hpp" #include #include @@ -20,7 +20,7 @@ #include #include #include - +#include "g2loglevels.hpp" #ifdef __clang__ #include @@ -33,21 +33,23 @@ namespace { // Dump of stack,. then exit through g2log background worker // ALL thanks to this thread at StackOverflow. Pretty much borrowed from: // Ref: http://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashes + void crashHandler(int signal_number, siginfo_t *info, void *unused_context) { using namespace g2::internal; std::ostringstream oss; oss << "Received fatal signal: " << g2::internal::signalName(signal_number); oss << "(" << signal_number << ")\tPID: " << getpid() << std::endl; - oss << stackdump(); + //oss << stackdump(); { // Local scope, trigger send std::ostringstream fatal_stream; fatal_stream << oss.str() << std::endl; - fatal_stream << "\n***** SIGNAL " << signalName(signal_number) << "(" << signal_number << ")" << std::endl; - g2::FatalMessageBuilder trigger(fatal_stream.str(), signal_number); - } // message sent to g2LogWorker by FatalMessageBuilder - // wait to die -- will be inside the FatalMessageBuilder + fatal_stream << "\n***** SIGNAL " << signalName(signal_number) << "(" << signal_number << ")" << std::endl; + LogCapture trigger(FATAL_SIGNAL, signal_number); + trigger.stream() << fatal_stream.str(); + } // message sent to g2LogWorker + // wait to die } } // end anonymous namespace @@ -71,6 +73,7 @@ namespace g2 { // ,plenty of examples when both or either are used // http://stackoverflow.com/questions/6878546/why-doesnt-parent-process-return-to-the-exact-location-after-handling-signal_number namespace internal { + std::string stackdump() { const size_t max_dump_size = 50; void* dump[max_dump_size]; @@ -121,14 +124,18 @@ namespace g2 { return oss.str(); } - std::string signalName(int signal_number) { switch (signal_number) { - case SIGABRT: return "SIGABRT"; break; - case SIGFPE: return "SIGFPE"; break; - case SIGSEGV: return "SIGSEGV"; break; - case SIGILL: return "SIGILL"; break; - case SIGTERM: return "SIGTERM"; break; + case SIGABRT: return "SIGABRT"; + break; + case SIGFPE: return "SIGFPE"; + break; + case SIGSEGV: return "SIGSEGV"; + break; + case SIGILL: return "SIGILL"; + break; + case SIGTERM: return "SIGTERM"; + break; default: std::ostringstream oss; oss << "UNKNOWN SIGNAL(" << signal_number << ")"; @@ -139,10 +146,11 @@ namespace g2 { // Triggered by g2log->g2LogWorker after receiving a FATAL trigger // which is LOG(FATAL), CHECK(false) or a fatal signal our signalhandler caught. // --- If LOG(FATAL) or CHECK(false) the signal_number will be SIGABRT + void exitWithDefaultSignalHandler(int signal_number) { std::cerr << "Exiting - FATAL SIGNAL: " << signal_number << " " << std::flush; struct sigaction action; - memset(&action, 0, sizeof (action)); // + memset(&action, 0, sizeof (action)); // sigemptyset(&action.sa_mask); action.sa_handler = SIG_DFL; // take default action for the signal sigaction(signal_number, &action, NULL); @@ -151,12 +159,11 @@ namespace g2 { } } // end g2::internal - void installSignalHandler() { struct sigaction action; memset(&action, 0, sizeof (action)); sigemptyset(&action.sa_mask); - action.sa_sigaction = &crashHandler; // callback to crashHandler for fatal signals + action.sa_sigaction = &crashHandler; // callback to crashHandler for fatal signals // sigaction to use sa_sigaction file. ref: http://www.linuxprogrammingblog.com/code-examples/sigaction action.sa_flags = SA_SIGINFO; diff --git a/g2log/src/crashhandler_win.cpp b/g2log/src/crashhandler_win.cpp index a3aaff3..d79abfe 100644 --- a/g2log/src/crashhandler_win.cpp +++ b/g2log/src/crashhandler_win.cpp @@ -1,12 +1,12 @@ /** ========================================================================== -* 2011 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes -* with no warranties. This code is yours to share, use and modify with no -* strings attached and no restrictions or obligations. -* ============================================================================*/ + * 2011 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes + * with no warranties. This code is yours to share, use and modify with no + * strings attached and no restrictions or obligations. + * ============================================================================*/ #include "crashhandler.hpp" #include "g2logmessage.hpp" -#include "g2LogMessageBuilder.hpp" +#include "g2logmessagecapture.hpp" #include #include @@ -19,83 +19,79 @@ #include // getpid #define getpid _getpid -namespace -{ -void crashHandler(int signal_number) -{ - using namespace g2::internal; - std::ostringstream fatal_stream; - fatal_stream << "\n\n***** FATAL TRIGGER RECEIVED ******* " << std::endl; - fatal_stream << "\n***** RETHROWING SIGNAL " << signalName(signal_number) << "(" << signal_number << ")" << std::endl; +namespace { - //FatalMessage fatal_message(fatal_stream.str(),FatalMessage::kReasonOS_FATAL_SIGNAL, signal_number); - g2::FatalMessageBuilder trigger(fatal_stream.str(), signal_number); - //std::ostringstream oss; - //std::cerr << fatal_message.message_ << std::endl << std::flush; -} // scope exit - message sent to LogWorker, wait to die... + void crashHandler(int signal_number) { + using namespace g2::internal; + std::ostringstream fatal_stream; + fatal_stream << "\n\n***** FATAL TRIGGER RECEIVED ******* " << std::endl; + fatal_stream << "\n***** RETHROWING SIGNAL " << signalName(signal_number) << "(" << signal_number << ")" << std::endl; + + LogCapture trigger(FATAL_SIGNAL, signal_number); + trigger.stream() << fatal_stream.str(); + } // scope exit - message sent to LogWorker, wait to die... } // end anonymous namespace -namespace g2 -{ -namespace internal -{ -std::string stackdump() -{ - std::string temp; - return temp; -} +namespace g2 { + namespace internal { -std::string signalName(int signal_number) -{ - switch(signal_number) - { - case SIGABRT: return "SIGABRT";break; - case SIGFPE: return "SIGFPE"; break; - case SIGSEGV: return "SIGSEGV"; break; - case SIGILL: return "SIGILL"; break; - case SIGTERM: return "SIGTERM"; break; -default: - std::ostringstream oss; - oss << "UNKNOWN SIGNAL(" << signal_number << ")"; - return oss.str(); - } -} + std::string stackdump() { + std::string temp; + return temp; + } + + std::string signalName(int signal_number) { + switch (signal_number) { + case SIGABRT: return "SIGABRT"; + break; + case SIGFPE: return "SIGFPE"; + break; + case SIGSEGV: return "SIGSEGV"; + break; + case SIGILL: return "SIGILL"; + break; + case SIGTERM: return "SIGTERM"; + break; + default: + std::ostringstream oss; + oss << "UNKNOWN SIGNAL(" << signal_number << ")"; + return oss.str(); + } + } -// Triggered by g2log::LogWorker after receiving a FATAL trigger -// which is LOG(FATAL), CHECK(false) or a fatal signal our signalhandler caught. -// --- If LOG(FATAL) or CHECK(false) the signal_number will be SIGABRT -void exitWithDefaultSignalHandler(int signal_number) -{ - // Restore our signalhandling to default - if(SIG_ERR == signal (SIGABRT, SIG_DFL)) - perror("signal - SIGABRT"); - if(SIG_ERR == signal (SIGFPE, SIG_DFL)) - perror("signal - SIGABRT"); - if(SIG_ERR == signal (SIGSEGV, SIG_DFL)) - perror("signal - SIGABRT"); - if(SIG_ERR == signal (SIGILL, SIG_DFL)) - perror("signal - SIGABRT"); - if(SIG_ERR == signal (SIGTERM, SIG_DFL)) - perror("signal - SIGABRT"); + // Triggered by g2log::LogWorker after receiving a FATAL trigger + // which is LOG(FATAL), CHECK(false) or a fatal signal our signalhandler caught. + // --- If LOG(FATAL) or CHECK(false) the signal_number will be SIGABRT - raise(signal_number); -} -} // end g2::internal + void exitWithDefaultSignalHandler(int signal_number) { + // Restore our signalhandling to default + if (SIG_ERR == signal(SIGABRT, SIG_DFL)) + perror("signal - SIGABRT"); + if (SIG_ERR == signal(SIGFPE, SIG_DFL)) + perror("signal - SIGABRT"); + if (SIG_ERR == signal(SIGSEGV, SIG_DFL)) + perror("signal - SIGABRT"); + if (SIG_ERR == signal(SIGILL, SIG_DFL)) + perror("signal - SIGABRT"); + if (SIG_ERR == signal(SIGTERM, SIG_DFL)) + perror("signal - SIGABRT"); + raise(signal_number); + } + } // end g2::internal -void installSignalHandler() -{ - if(SIG_ERR == signal (SIGABRT, crashHandler)) - perror("signal - SIGABRT"); - if(SIG_ERR == signal (SIGFPE, crashHandler)) - perror("signal - SIGFPE"); - if(SIG_ERR == signal (SIGSEGV, crashHandler)) - perror("signal - SIGSEGV"); - if(SIG_ERR == signal (SIGILL, crashHandler)) - perror("signal - SIGILL"); - if(SIG_ERR == signal (SIGTERM, crashHandler)) - perror("signal - SIGTERM"); -} + void installSignalHandler() { + if (SIG_ERR == signal(SIGABRT, crashHandler)) + perror("signal - SIGABRT"); + if (SIG_ERR == signal(SIGFPE, crashHandler)) + perror("signal - SIGFPE"); + if (SIG_ERR == signal(SIGSEGV, crashHandler)) + perror("signal - SIGSEGV"); + if (SIG_ERR == signal(SIGILL, crashHandler)) + perror("signal - SIGILL"); + if (SIG_ERR == signal(SIGTERM, crashHandler)) + perror("signal - SIGTERM"); + } } // end namespace g2 diff --git a/g2log/src/g2LogMessageBuilder.cpp b/g2log/src/g2LogMessageBuilder.cpp deleted file mode 100644 index 829d87e..0000000 --- a/g2log/src/g2LogMessageBuilder.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/** ========================================================================== -* 2013 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes -* with no warranties. This code is yours to share, use and modify with no -* strings attached and no restrictions or obligations. -* ============================================================================*/ - -#include "g2LogMessageBuilder.hpp" -#include "g2logmessage.hpp" -#include "g2log.hpp" -#include "std2_make_unique.hpp" -#include "crashhandler.hpp" -#include - -#include -namespace { - const int kMaxMessageSize = 2048; - const std::string kTruncatedWarningText = "[...truncated...]"; -} -namespace g2 { - using namespace internal; - - LogMessageBuilder::LogMessageBuilder(const std::string& file, const int line, - const std::string& function, const LEVELS& level) - : _message(std2::make_unique(file, line, function, level)) { - - if (_message.get()->wasFatal()) { - addStackTrace(); - } - } - - LogMessageBuilder::~LogMessageBuilder() { - _message.get()->write().append(stream().str()); - if (_message.get()->wasFatal()) { - _message.get()->write().append(_stackTrace); // empty or not - FatalMessageBuilder trigger(_message, SIGABRT); - return; // FatalMessageBuilder will send to worker at scope exit - } - - saveMessage(_message); // message saved to g2LogWorker - } - - LogMessageBuilder& LogMessageBuilder::setExpression(const std::string& boolean_expression) { - _message.get()->setExpression(boolean_expression); - return *this; - } - - LogMessageBuilder& LogMessageBuilder::addStackTrace() { - _stackTrace = {"\n*******\tSTACKDUMP *******\n"}; - _stackTrace.append(internal::stackdump()); - return *this; - } - - - std::ostringstream& LogMessageBuilder::stream() { - return _stream; - } - - void LogMessageBuilder::messageSave(const char *printf_like_message, ...) { - char finished_message[kMaxMessageSize]; - va_list arglist; - va_start(arglist, printf_like_message); - const int nbrcharacters = vsnprintf(finished_message, sizeof (finished_message), printf_like_message, arglist); - va_end(arglist); - if (nbrcharacters <= 0) { - stream() << "\n\tERROR LOG MSG NOTIFICATION: Failure to parse successfully the message"; - stream() << '"' << printf_like_message << '"' << std::endl; - } else if (nbrcharacters > kMaxMessageSize) { - stream() << finished_message << kTruncatedWarningText; - } else { - stream() << finished_message; - } - } - - - - - - /// FatalMessageBuilder - - FatalMessageBuilder::FatalMessageBuilder(const std::string& exit_message, int signal_id) - : _fatal_message(std2::make_unique(LogMessage{exit_message}, signal_id)) - { } - - - FatalMessageBuilder:: FatalMessageBuilder(LogMessagePtr details, int signal_id) - : _fatal_message(std2::make_unique(*(details._move_only.get()), signal_id)) - {} - - - FatalMessageBuilder::~FatalMessageBuilder() { - // At destruction, flushes fatal message to g2LogWorker - // either we will stay here until the background worker has received the fatal - // message, flushed the crash message to the sinks and exits with the same fatal signal - //..... OR it's in unit-test mode then we throw a std::runtime_error (and never hit sleep) - - fatalCall(_fatal_message); - - } -} // g2 diff --git a/g2log/src/g2LogMessageBuilder.hpp b/g2log/src/g2LogMessageBuilder.hpp deleted file mode 100644 index 75ceebf..0000000 --- a/g2log/src/g2LogMessageBuilder.hpp +++ /dev/null @@ -1,51 +0,0 @@ -/** ========================================================================== -* 2013 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes -* with no warranties. This code is yours to share, use and modify with no -* strings attached and no restrictions or obligations. -* ============================================================================*/ - -#pragma once -#include -#include -#include - -#include "g2loglevels.hpp" -#include "g2logmessage.hpp" - -namespace g2 { - - // At RAII scope end this struct will trigger a FatalMessage sending - struct FatalMessageBuilder { - //explicit FatalMessageBuilder(const FatalMessage& exit_message); - FatalMessageBuilder(const std::string& exit_message, int fatal_signal); - FatalMessageBuilder(LogMessagePtr details, int signal_id); - virtual ~FatalMessageBuilder(); - - FatalMessagePtr _fatal_message; - int _fatal_signal; - }; - - struct LogMessageBuilder { - LogMessageBuilder(const std::string& file, const int line, const std::string& function, const LEVELS& level); - virtual ~LogMessageBuilder(); - - LogMessageBuilder& setExpression(const std::string& boolean_expression); - LogMessageBuilder& addStackTrace(); - - std::ostringstream& stream(); - - - // Use "-Wall" to generate warnings in case of illegal printf format. - // Ref: http://www.unixwiz.net/techtips/gnu-c-attributes.html -#ifndef __GNUC__ -#define __attribute__(x) // Disable 'attributes' if compiler does not support 'em -#endif - void messageSave(const char *printf_like_message, ...) - __attribute__((format(printf, 2, 3))); // ref: http://www.codemaestro.com/reviews/18 - - private: - LogMessagePtr _message; - std::ostringstream _stream; - std::string _stackTrace; - }; -} // g2 diff --git a/g2log/src/g2log.cpp b/g2log/src/g2log.cpp index 2bd0577..54e2389 100644 --- a/g2log/src/g2log.cpp +++ b/g2log/src/g2log.cpp @@ -48,20 +48,21 @@ namespace g2 { // for unit testing purposes the initializeLogging might be called // several times... // for all other practical use, it shouldn't! + void initializeLogging(LogWorker *bgworker) { std::call_once(g_initialize_flag, []() { installSignalHandler(); }); std::lock_guard lock(g_logging_init_mutex); CHECK(!internal::isLoggingInitialized()); CHECK(bgworker != nullptr); - - // Save the first uninitialized message, if any + + // Save the first uninitialized message, if any std::call_once(g_save_first_unintialized_flag, [&bgworker] { if (g_first_unintialized_msg) { bgworker->save(LogMessagePtr{std::move(g_first_unintialized_msg)}); - } + } }); - + g_logger_instance = bgworker; } @@ -75,35 +76,58 @@ namespace g2 { return g_logger_instance != nullptr; } - - /** - * Shutdown the logging by making the pointer to the background logger to nullptr. The object is not deleted - * that is the responsibility of its owner. * - */ + /** + * Shutdown the logging by making the pointer to the background logger to nullptr. The object is not deleted + * that is the responsibility of its owner. * + */ void shutDownLogging() { std::lock_guard lock(g_logging_init_mutex); g_logger_instance = nullptr; } - /** Same as the Shutdown above but called by the destructor of the LogWorker, thus ensuring that no further - * LOG(...) calls can happen to a non-existing LogWorker. - * @param active MUST BE the LogWorker initialized for logging. If it is not then this call is just ignored - * and the logging continues to be active. - * @return true if the correct worker was given,. and shutDownLogging was called - */ + /** Same as the Shutdown above but called by the destructor of the LogWorker, thus ensuring that no further + * LOG(...) calls can happen to a non-existing LogWorker. + * @param active MUST BE the LogWorker initialized for logging. If it is not then this call is just ignored + * and the logging continues to be active. + * @return true if the correct worker was given,. and shutDownLogging was called + */ bool shutDownLoggingForActiveOnly(LogWorker* active) { - if(isLoggingInitialized() && nullptr != active && - (dynamic_cast(active) != dynamic_cast(g_logger_instance))) { - LOG(WARNING) << "\n\t\tShutting down logging, but the ID of the Logger is not the one that is active." - << "\n\t\tHaving multiple instances of the g2::LogWorker is likely a BUG" - << "\n\t\tEither way, this call to shutDownLogging was ignored"; - return false; + if (isLoggingInitialized() && nullptr != active && + (dynamic_cast (active) != dynamic_cast (g_logger_instance))) { + LOG(WARNING) << "\n\t\tShutting down logging, but the ID of the Logger is not the one that is active." + << "\n\t\tHaving multiple instances of the g2::LogWorker is likely a BUG" + << "\n\t\tEither way, this call to shutDownLogging was ignored"; + return false; } shutDownLogging(); return true; } + + // explicits copy of all input. This is makes it possibly to use g3log across dynamically loaded libraries + // i.e. (dlopen + dlsym) + + void saveMessage(const char* entry, const char* file, int line, const char* function, const LEVELS& level, + const char* boolean_expression, int fatal_signal, const char* stack_trace) { + LEVELS msgLevel{level}; + LogMessagePtr message{std2::make_unique(file, line, function, msgLevel)}; + message.get()->write().append(entry); + message.get()->setExpression(boolean_expression); + + if (internal::wasFatal(level)) { + message.get()->write().append(stack_trace); + FatalMessagePtr fatal_message{std2::make_unique(*(message._move_only.get()), fatal_signal)}; + // At destruction, flushes fatal message to g2LogWorker + // either we will stay here until the background worker has received the fatal + // message, flushed the crash message to the sinks and exits with the same fatal signal + //..... OR it's in unit-test mode then we throw a std::runtime_error (and never hit sleep) + fatalCall(fatal_message); + } else { + pushMessageToLogger(message); + } + } + /** * save the message to the logger. In case of called before the logger is instantiated * the first message will be saved. Any following subsequent unitnialized log calls @@ -112,32 +136,31 @@ namespace g2 { * The first initialized log entry will also save the first uninitialized log message, if any * @param log_entry to save to logger */ - void saveMessage(LogMessagePtr incoming) { + void pushMessageToLogger(LogMessagePtr incoming) { // todo rename to Push SavedMessage To Worker // Uninitialized messages are ignored but does not CHECK/crash the logger if (!internal::isLoggingInitialized()) { std::call_once(g_set_first_uninitialized_flag, [&] { g_first_unintialized_msg = incoming.release(); std::string err = {"LOGGER NOT INITIALIZED:\n\t\t"}; err.append(g_first_unintialized_msg->message()); - std::string& str = g_first_unintialized_msg->write(); - str.clear(); - str.append(err); // replace content - std::cerr << str << std::endl; + std::string& str = g_first_unintialized_msg->write(); + str.clear(); + str.append(err); // replace content + std::cerr << str << std::endl; }); return; } - + // logger is initialized g_logger_instance->save(incoming); } - /** Fatal call saved to logger. This will trigger SIGABRT or other fatal signal * to exit the program. After saving the fatal message the calling thread * will sleep forever (i.e. until the background thread catches up, saves the fatal * message and kills the software with the fatal signal. */ - void fatalCallToLogger(FatalMessagePtr message) { + void fatalCallToLogger(FatalMessagePtr message) { if (!isLoggingInitialized()) { std::ostringstream error; error << "FATAL CALL but logger is NOT initialized\n" @@ -146,9 +169,7 @@ namespace g2 { std::cerr << error.str() << std::flush; internal::exitWithDefaultSignalHandler(message.get()->_signal_id); } - g_logger_instance->fatal(message); - while (true) { std::this_thread::sleep_for(std::chrono::seconds(1)); } @@ -158,7 +179,6 @@ namespace g2 { // By default this function pointer goes to \ref fatalCallToLogger; std::function g_fatal_to_g2logworker_function_ptr = fatalCallToLogger; - /** The default, initial, handling to send a 'fatal' event to g2logworker * the caller will stay here, eternally, until the software is aborted * ... in the case of unit testing it is the given "Mock" fatalCall that will @@ -168,7 +188,6 @@ namespace g2 { g_fatal_to_g2logworker_function_ptr(FatalMessagePtr{std::move(message)}); } - /** REPLACE fatalCallToLogger for fatalCallForUnitTest * This function switches the function pointer so that only * 'unitTest' mock-fatal calls are made. diff --git a/g2log/src/g2log.hpp b/g2log/src/g2log.hpp index f69be2a..515f07f 100644 --- a/g2log/src/g2log.hpp +++ b/g2log/src/g2log.hpp @@ -24,7 +24,7 @@ #include #include "g2loglevels.hpp" -#include "g2LogMessageBuilder.hpp" +#include "g2logmessagecapture.hpp" #include "g2logmessage.hpp" #if !(defined(__PRETTY_FUNCTION__)) @@ -45,65 +45,60 @@ * --- Thanks for a great 2011 and good luck with 'g2' --- KjellKod */ namespace g2 { -class LogWorker; -struct LogMessage; -struct FatalMessage; + class LogWorker; + struct LogMessage; + struct FatalMessage; -/** Should be called at very first startup of the software with \ref g2LogWorker - * pointer. Ownership of the \ref g2LogWorker is the responsibilkity of the caller */ -void initializeLogging(LogWorker *logger); + /** Should be called at very first startup of the software with \ref g2LogWorker + * pointer. Ownership of the \ref g2LogWorker is the responsibilkity of the caller */ + void initializeLogging(LogWorker *logger); + + + namespace internal { + /// @returns true if logger is initialized + bool isLoggingInitialized(); + + // Save the created LogMessage to any existing sinks + void saveMessage(const char* message, const char* file, int line, const char* function, const LEVELS& level, + const char* boolean_expression, int fatal_signal, const char* stack_trace); + + void pushMessageToLogger(LogMessagePtr log_entry); + + // Save the created FatalMessage to any existing sinks and exit with + // the originating fatal signal,. or SIGABRT if it originated from a broken contract + void fatalCall(FatalMessagePtr message); + + + // Shuts down logging. No object cleanup but further LOG(...) calls will be ignored. + void shutDownLogging(); + + // Shutdown logging, but ONLY if the active logger corresponds to the one currently initialized + bool shutDownLoggingForActiveOnly(LogWorker* active); - - - -namespace internal { -/// @returns true if logger is initialized -bool isLoggingInitialized(); - -// Save the created LogMessage to any existing sinks -void saveMessage(LogMessagePtr log_entry); - -// Save the created FatalMessage to any existing sinks and exit with -// the originating fatal signal,. or SIGABRT if it originated from a broken contract -void fatalCall(FatalMessagePtr message); - - -// Shuts down logging. No object cleanup but further LOG(...) calls will be ignored. -void shutDownLogging(); - -// Shutdown logging, but ONLY if the active logger corresponds to the one currently initialized -bool shutDownLoggingForActiveOnly(LogWorker* active); - - - -/** By default the g2log will call g2LogWorker::fatal(...) which will - * abort() the system after flushing the logs to file. This makes unit - * test of FATAL level cumbersome. A work around is to change the - * 'fatal call' which can be done here - * - * The bool return values in the fatal_call is whether or not the fatal_call should - * - */ -void changeFatalInitHandlerForUnitTesting(std::function fatal_call); -} // internal + /** By default the g2log will call g2LogWorker::fatal(...) which will + * abort() the system after flushing the logs to file. This makes unit + * test of FATAL level cumbersome. A work around is to change the + * 'fatal call' which can be done here + * + * The bool return values in the fatal_call is whether or not the fatal_call should + * + */ + void changeFatalInitHandlerForUnitTesting(std::function fatal_call); + } // internal } // g2 - - - - -#define INTERNAL_LOG_MESSAGE(level) g2::LogMessageBuilder(__FILE__, __LINE__, __PRETTY_FUNCTION__, level) - +#define INTERNAL_LOG_MESSAGE(level) LogCapture(__FILE__, __LINE__, __PRETTY_FUNCTION__, level) #define INTERNAL_CONTRACT_MESSAGE(boolean_expression) \ - g2::LogMessageBuilder(__FILE__, __LINE__, __PRETTY_FUNCTION__, g2::internal::CONTRACT).setExpression(boolean_expression).addStackTrace() + LogCapture(__FILE__, __LINE__, __PRETTY_FUNCTION__, g2::internal::CONTRACT, boolean_expression) // LOG(level) is the API for the stream log #define LOG(level) if(g2::logLevel(level)) INTERNAL_LOG_MESSAGE(level).stream() + // 'Conditional' stream log #define LOG_IF(level, boolean_expression) \ if(true == boolean_expression) \ @@ -165,16 +160,16 @@ And here is possible output : Width trick: 10 : A string \endverbatim */ #define LOGF(level, printf_like_message, ...) \ - if(g2::logLevel(level)) INTERNAL_LOG_MESSAGE(level).messageSave(printf_like_message, ##__VA_ARGS__) + if(g2::logLevel(level)) INTERNAL_LOG_MESSAGE(level).capturef(printf_like_message, ##__VA_ARGS__) // Conditional log printf syntax #define LOGF_IF(level,boolean_expression, printf_like_message, ...) \ if(true == boolean_expression) \ - if(g2::logLevel(level)) INTERNAL_LOG_MESSAGE(level).messageSave(printf_like_message, ##__VA_ARGS__) + if(g2::logLevel(level)) INTERNAL_LOG_MESSAGE(level).capturef(printf_like_message, ##__VA_ARGS__) // Design By Contract, printf-like API syntax with variadic input parameters. // Throws std::runtime_eror if contract breaks #define CHECK_F(boolean_expression, printf_like_message, ...) \ - if (false == (boolean_expression)) INTERNAL_CONTRACT_MESSAGE(#boolean_expression).messageSave(printf_like_message, ##__VA_ARGS__) + if (false == (boolean_expression)) INTERNAL_CONTRACT_MESSAGE(#boolean_expression).capturef(printf_like_message, ##__VA_ARGS__) diff --git a/g2log/src/g2loglevels.cpp b/g2log/src/g2loglevels.cpp index ec88aef..33734eb 100644 --- a/g2log/src/g2loglevels.cpp +++ b/g2log/src/g2loglevels.cpp @@ -1,57 +1,48 @@ /** ========================================================================== -* 2012 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes -* with no warranties. This code is yours to share, use and modify with no -* strings attached and no restrictions or obligations. -* ============================================================================ -* Filename:g2loglevels.cpp Part of Framework for Logging and Design By Contract -* Created: 2012 by Kjell Hedström -* -* PUBLIC DOMAIN and Not copywrited. First published at KjellKod.cc -* ********************************************* */ + * 2012 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes + * with no warranties. This code is yours to share, use and modify with no + * strings attached and no restrictions or obligations. + * ============================================================================ + * Filename:g2loglevels.cpp Part of Framework for Logging and Design By Contract + * Created: 2012 by Kjell Hedström + * + * PUBLIC DOMAIN and Not copywrited. First published at KjellKod.cc + * ********************************************* */ #include "g2loglevels.hpp" #include "g2log.hpp" #include #include -namespace g2 -{ - namespace internal { - bool wasFatal(const LEVELS& level) { - return level.value >= FATAL.value; - } - - // All levels are by default ON: i.e. for DEBUG, INFO, WARNING, FATAL - constexpr const int g_level_size{ FATAL.value + 1 }; - std::atomic g_log_level_status[4]{{true}, {true}, {true},{true}}; - - #ifndef _MSC_VER - static_assert(4 == g_level_size, "Mismatch between number of logging levels and their use"); - #endif - } // internal - +namespace g2 { + namespace internal { + bool wasFatal(const LEVELS& level) { + return level.value >= FATAL.value; + } #ifdef G2_DYNAMIC_LOGGING - void setLogLevel(LEVELS log_level, bool enabled) - { - // MSC: remove when constexpr available. see static_assert above - assert(internal::g_level_size == 4 && "Mismatch between number of logging levels and their use"); - int level = log_level.value; - CHECK((level >= DEBUG.value) && (level <= FATAL.value)); - internal::g_log_level_status[level].store(enabled, std::memory_order_release); - } -#endif + // All levels are by default ON: i.e. for DEBUG, INFO, WARNING, FATAL + const int g_level_size{FATAL.value + 1}; + std::atomic g_log_level_status[4]{{true},{true},{true},{true}}; +#endif + } // internal - - bool logLevel(LEVELS log_level) - { #ifdef G2_DYNAMIC_LOGGING - int level = log_level.value; - CHECK((level >= DEBUG.value) && (level <= FATAL.value)); - bool status = (internal::g_log_level_status[level].load(std::memory_order_acquire)); - return status; + + void setLogLevel(LEVELS log_level, bool enabled) { + assert(internal::g_level_size == 4 && "Mismatch between number of logging levels and their use"); + int level = log_level.value; + CHECK((level >= DEBUG.value) && (level <= FATAL.value)); + internal::g_log_level_status[level].store(enabled, std::memory_order_release); + } #endif - return true; - } - + bool logLevel(LEVELS log_level) { +#ifdef G2_DYNAMIC_LOGGING + int level = log_level.value; + CHECK((level >= DEBUG.value) && (level <= FATAL.value)); + bool status = (internal::g_log_level_status[level].load(std::memory_order_acquire)); + return status; +#endif + return true; + } } // g2 diff --git a/g2log/src/g2loglevels.hpp b/g2log/src/g2loglevels.hpp index fc46ce6..5a0942a 100644 --- a/g2log/src/g2loglevels.hpp +++ b/g2log/src/g2loglevels.hpp @@ -1,56 +1,49 @@ /** ========================================================================== -* 2012 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes -* with no warranties. This code is yours to share, use and modify with no -* strings attached and no restrictions or obligations. -* ============================================================================ -* Filename:g2loglevels.hpp Part of Framework for Logging and Design By Contract -* Created: 2012 by Kjell Hedström -* -* PUBLIC DOMAIN and Not copywrited. First published at KjellKod.cc -* ********************************************* */ + * 2012 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes + * with no warranties. This code is yours to share, use and modify with no + * strings attached and no restrictions or obligations. + * ============================================================================ + * Filename:g2loglevels.hpp Part of Framework for Logging and Design By Contract + * Created: 2012 by Kjell Hedström + * + * PUBLIC DOMAIN and Not copywrited. First published at KjellKod.cc + * ********************************************* */ -#ifndef G2_LOG_LEVELS_HPP_ -#define G2_LOG_LEVELS_HPP_ +#pragma once #include // Levels for logging, made so that it would be easy to change, remove, add levels -- KjellKod -struct LEVELS -{ - const int value; - const char* text; +struct LEVELS { + // force internal copy of the const char*. This is a simple safeguard for when g3log is used in a + // "dynamic, runtime loading of shared libraries" + + LEVELS(const LEVELS& other) + : value(other.value), text(other.text.c_str()) {} + + LEVELS(int id, const char* idtext) : value(id), text(idtext) {} + const int value; + const std::string text; }; -#if _MSC_VER -// Visual Studio does not support constexpr -// what is below is ugly and not right but it works in this purpose. -// if there are any issues with it then just please remove all instances of constexpr - #ifndef constexpr - #define constexpr - #endif -#endif -constexpr const LEVELS DEBUG{0, "DEBUG"}, - INFO{DEBUG.value+1, "INFO"}, - WARNING{INFO.value+1, "WARNING"}, - // Insert here *any* extra logging levels that is needed - // 1) Remember to update the FATAL initialization below - // 2) Remember to update the initialization of "g2loglevels.cpp/g_log_level_status" - FATAL{WARNING.value+1, "FATAL"}; +const LEVELS DEBUG{0, {"DEBUG"}}, INFO{DEBUG.value + 1, {"INFO"}}, +WARNING{INFO.value + 1, {"WARNING"}}, +// Insert here *any* extra logging levels that is needed +// 1) Remember to update the FATAL initialization below +// 2) Remember to update the initialization of "g2loglevels.cpp/g_log_level_status" +FATAL{WARNING.value + 1, {"FATAL"}}; + namespace g2 { -namespace internal { - constexpr const LEVELS CONTRACT{100, "CONTRACT"}, FATAL_SIGNAL{101, "FATAL_SIGNAL"}; - - bool wasFatal(const LEVELS& level); -} + namespace internal { + const LEVELS CONTRACT{100, {"CONTRACT"}}, FATAL_SIGNAL{101, {"FATAL_SIGNAL"}}; + bool wasFatal(const LEVELS& level); + } #ifdef G2_DYNAMIC_LOGGING - // Enable/Disable a log level {DEBUG,INFO,WARNING,FATAL} - void setLogLevel(LEVELS level, bool enabled_status); + // Enable/Disable a log level {DEBUG,INFO,WARNING,FATAL} + void setLogLevel(LEVELS level, bool enabled_status); #endif - bool logLevel(LEVELS level); - + bool logLevel(LEVELS level); } // g2 - -#endif // G2_LOG_LEVELS_HPP_ diff --git a/g2log/src/g2logmessage.cpp b/g2log/src/g2logmessage.cpp index fb4a30c..843f054 100644 --- a/g2log/src/g2logmessage.cpp +++ b/g2log/src/g2logmessage.cpp @@ -110,37 +110,7 @@ namespace g2 { , _level(other._level) , _expression(std::move(other._expression)) , _message(std::move(other._message)) { - } - - -// LogMessage& LogMessage::operator=(const LogMessage& other) { -// _timestamp = other._timestamp; -// _microseconds = other._microseconds; -// _file = other._file; -// _line=other._line; -// _function=other._function; -// _level.value = other._level.value; -// _level.text = other._level.text; -// _expression= other._expression; -// stream().str(other.stream().str()); -// } - - -// LogMessage& LogMessage::operator=(LogMessage&& other) { -// _timestamp = other._timestamp; -// _microseconds = other._microseconds; -// _file = std::move(other._file); -// _line = other._line; -// _function = std::move(other._function); -// _level = other._level; -// _expression = std::move(other._expression); -// std::move(_stream, other._stream); -// return *this; -// } -// -// -// - + } FatalMessage::FatalMessage(const LogMessage& details, int signal_id) : LogMessage(details), _signal_id(signal_id) { } diff --git a/g2log/src/g2logmessagecapture.cpp b/g2log/src/g2logmessagecapture.cpp new file mode 100644 index 0000000..e804660 --- /dev/null +++ b/g2log/src/g2logmessagecapture.cpp @@ -0,0 +1,13 @@ +/** ========================================================================== + * 2013 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes + * with no warranties. This code is yours to share, use and modify with no + * strings attached and no restrictions or obligations. + * ============================================================================*/ + +#include "g2logmessagecapture.hpp" +#include "g2log.hpp" + +LogCapture::~LogCapture() { + using namespace g2::internal; + saveMessage(_stream.str().c_str(), _file, _line, _function, _level, _expression, _fatal_signal, _stack_trace.c_str()); +} diff --git a/g2log/src/g2logmessagecapture.hpp b/g2log/src/g2logmessagecapture.hpp new file mode 100644 index 0000000..42fea5d --- /dev/null +++ b/g2log/src/g2logmessagecapture.hpp @@ -0,0 +1,92 @@ +/** ========================================================================== + * 2013 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes + * with no warranties. This code is yours to share, use and modify with no + * strings attached and no restrictions or obligations. + * ============================================================================*/ + +#pragma once +#include +#include +#include + +#include "g2loglevels.hpp" +#include "g2logmessage.hpp" +#include "crashhandler.hpp" +#include "g2log.hpp" + +struct LogCapture { + /// Called from crash handler when a fatal signal has occurred (SIGSEGV etc) + LogCapture(const LEVELS& level, int fatal_signal) + : LogCapture("", 0, "", level, "", fatal_signal) { + } + + /** + * Simple struct for capturing log/fatal entries. At destruction the captured message is forwarded to background worker. + * -- As a safety precaution: No memory allocated here will be moved into the background worker in case of dynamic loaded library reasons + * -- instead the arguments are copied inside of g2log.cpp::saveMessage + * @file, line, function are given in g2log.hpp from macros + * @level INFO/DEBUG/WARNING/FATAL + * @expression for CHECK calls + * @fatal_signal for failed CHECK:SIGABRT or fatal signal caught in the signal handler + */ + LogCapture(const char* file, const int line, const char* function, const LEVELS& level, const char* expression = "", int fatal_signal = SIGABRT) + : _file(file), _line(line), _function(function), _level(level), _expression(expression), _fatal_signal(fatal_signal) { + + if (g2::internal::wasFatal(level)) { + _stack_trace = {"\n*******\tSTACKDUMP *******\n"}; + _stack_trace.append(g2::internal::stackdump()); + } + } + + + // At destruction the message will be forwarded to the g2log worker. + // in case of dynamically (at runtime) loaded libraries the important thing to know is that + // all strings are copied so the original are not destroyed at the receiving end, only the copy + ~LogCapture(); + + + + + // Use "-Wall" to generate warnings in case of illegal printf format. + // Ref: http://www.unixwiz.net/techtips/gnu-c-attributes.html +#ifndef __GNUC__ +#define __attribute__(x) // Disable 'attributes' if compiler does not support 'em +#endif + + void capturef(const char *printf_like_message, ...) __attribute__((format(printf, 2, 3))) // ref: http://www.codemaestro.com/reviews/18 + { + static const int kMaxMessageSize = 2048; + static const std::string kTruncatedWarningText = "[...truncated...]"; + char finished_message[kMaxMessageSize]; + va_list arglist; + va_start(arglist, printf_like_message); + const int nbrcharacters = vsnprintf(finished_message, sizeof (finished_message), printf_like_message, arglist); + va_end(arglist); + + if (nbrcharacters <= 0) { + stream() << "\n\tERROR LOG MSG NOTIFICATION: Failure to parse successfully the message"; + stream() << '"' << printf_like_message << '"' << std::endl; + } else if (nbrcharacters > kMaxMessageSize) { + stream() << finished_message << kTruncatedWarningText; + } else { + stream() << finished_message; + } + } + + std::ostringstream& stream() { + return _stream; + } + + + + std::ostringstream _stream; + std::string _stack_trace; + const char* _file; + const int _line; + const char* _function; + const LEVELS& _level; + const char* _expression; + const int _fatal_signal; + +}; +//} // g2 diff --git a/g2log/test_unit/test_linux_dynamic_loaded_sharedlib.cpp b/g2log/test_unit/test_linux_dynamic_loaded_sharedlib.cpp new file mode 100644 index 0000000..26d49bc --- /dev/null +++ b/g2log/test_unit/test_linux_dynamic_loaded_sharedlib.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include +#include "std2_make_unique.hpp" +#include "tester_sharedlib.h" +#include + +struct LogMessageCounter { + std::vector& bank; + LogMessageCounter(std::vector& storeMessages) : bank(storeMessages) { + } + + void countMessages(std::string msg) { + bank.push_back(msg); + } +}; + +TEST(DynamicLoadOfLibrary, JustLoadAndExit) { + std::vector receiver; + + { // scope to flush logs at logworker exit + auto worker = g2::LogWorker::createWithNoSink(); + auto handle = worker->addSink(std2::make_unique(std::ref(receiver)), &LogMessageCounter::countMessages); + g2::initializeLogging(worker.get()); + + void* libHandle = dlopen("libtester_sharedlib.so", RTLD_LAZY | RTLD_GLOBAL); + EXPECT_FALSE(nullptr == libHandle); + LibraryFactory* factory = reinterpret_cast ((dlsym(libHandle, "testRealFactory"))); + EXPECT_FALSE(nullptr == factory); + SomeLibrary* loadedLibrary = factory->CreateLibrary(); + + for (size_t i = 0; i < 300; ++i) { + loadedLibrary->action(); + } + + delete loadedLibrary; + dlclose(libHandle); + } // scope exit. All log entries must be flushed now + const int numberOfMessages = 2 + 300 + 1; // 2 library construction, 300 loop, 1 destoyed library + EXPECT_EQ(receiver.size(), numberOfMessages); +} diff --git a/g2log/test_unit/tester_sharedlib.cpp b/g2log/test_unit/tester_sharedlib.cpp new file mode 100644 index 0000000..7d1477d --- /dev/null +++ b/g2log/test_unit/tester_sharedlib.cpp @@ -0,0 +1,28 @@ + +#include +#include "tester_sharedlib.h" + +struct RuntimeLoadedLib : public SomeLibrary { + + RuntimeLoadedLib() { + LOG(INFO) << "Library was created"; + LOGF(INFO, "Ready for testing"); + } + + ~RuntimeLoadedLib() { + LOG(DEBUG) << "Library destroyed"; + } + + void action() { + LOG(WARNING) << "Action, action, action. Safe for LOG calls by runtime dynamically loaded libraries"; + } +}; + +struct RealLibraryFactory : public LibraryFactory { + SomeLibrary* CreateLibrary() { + return new RuntimeLoadedLib; + } +}; + +RealLibraryFactory testRealFactory; + diff --git a/g2log/test_unit/tester_sharedlib.h b/g2log/test_unit/tester_sharedlib.h new file mode 100644 index 0000000..f9e5d59 --- /dev/null +++ b/g2log/test_unit/tester_sharedlib.h @@ -0,0 +1,14 @@ +#pragma once + +struct SomeLibrary { + + SomeLibrary() {}; + + virtual ~SomeLibrary() {}; + virtual void action() = 0; +}; + +class LibraryFactory { +public: + virtual SomeLibrary* CreateLibrary() = 0; +}; diff --git a/g2log/test_unit/testing_helpers.cpp b/g2log/test_unit/testing_helpers.cpp index 1eb1f2b..d621853 100644 --- a/g2log/test_unit/testing_helpers.cpp +++ b/g2log/test_unit/testing_helpers.cpp @@ -1,8 +1,8 @@ /** ========================================================================== -* 2013 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes -* with no warranties. This code is yours to share, use and modify with no -* strings attached and no restrictions or obligations. -* ============================================================================*/ + * 2013 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes + * with no warranties. This code is yours to share, use and modify with no + * strings attached and no restrictions or obligations. + * ============================================================================*/ #include @@ -23,50 +23,42 @@ namespace testing_helpers { int g_mockFatal_signal = -1; bool g_mockFatalWasCalled = false; - std::string mockFatalMessage() { return g_mockFatal_message; } - int mockFatalSignal() { return g_mockFatal_signal; } - bool mockFatalWasCalled() { return g_mockFatalWasCalled; } - void mockFatalCall(FatalMessagePtr fatal_message) { g_mockFatal_message = fatal_message.get()->toString(); g_mockFatal_signal = fatal_message.get()->_signal_id; g_mockFatalWasCalled = true; LogMessagePtr message{fatal_message.release()}; - g2::internal::saveMessage(message); //fatal_message.copyToLogMessage()); + g2::internal::pushMessageToLogger(message); //fatal_message.copyToLogMessage()); } - void clearMockFatal() { g_mockFatal_message.clear(); g_mockFatal_signal = -1; g_mockFatalWasCalled = false; } - bool removeFile(std::string path_to_file) { return (0 == std::remove(path_to_file.c_str())); } - bool verifyContent(const std::string &total_text, std::string msg_to_find) { std::string content(total_text); size_t location = content.find(msg_to_find); return (location != std::string::npos); } - std::string readFileToText(std::string filename) { std::ifstream in; in.open(filename.c_str(), std::ios_base::in); @@ -80,12 +72,11 @@ namespace testing_helpers { return oss.str(); } - size_t LogFileCleaner::size() { return logs_to_clean_.size(); } - + LogFileCleaner::~LogFileCleaner() { std::lock_guard lock(g_mutex); { @@ -98,7 +89,6 @@ namespace testing_helpers { } // mutex } - void LogFileCleaner::addLogToClean(std::string path_to_log) { std::lock_guard lock(g_mutex); { @@ -107,11 +97,12 @@ namespace testing_helpers { } } + ScopedLogger::ScopedLogger() : _currentWorker(g2::LogWorker::createWithNoSink()) {} + ScopedLogger::~ScopedLogger() {} - ScopedLogger::ScopedLogger() : _currentWorker(g2::LogWorker::createWithNoSink()) { } - ScopedLogger::~ScopedLogger() { } - g2::LogWorker* ScopedLogger::get() { return _currentWorker.get(); } - + g2::LogWorker* ScopedLogger::get() { + return _currentWorker.get(); + } RestoreFileLogger::RestoreFileLogger(std::string directory) : _scope(new ScopedLogger), _handle(_scope->get()->addSink(std2::make_unique("UNIT_TEST_LOGGER", directory), &g2::FileSink::fileWrite)) { @@ -123,16 +114,15 @@ namespace testing_helpers { auto filename = _handle->call(&FileSink::fileName); if (!filename.valid()) ADD_FAILURE(); _log_file = filename.get(); - + #ifdef G2_DYNAMIC_LOGGING - g2::setLogLevel(INFO, true); - g2::setLogLevel(DEBUG, true); - g2::setLogLevel(WARNING, true); - g2::setLogLevel(FATAL, true); + g2::setLogLevel(INFO, true); + g2::setLogLevel(DEBUG, true); + g2::setLogLevel(WARNING, true); + g2::setLogLevel(FATAL, true); #endif } - RestoreFileLogger::~RestoreFileLogger() { g2::internal::shutDownLogging(); reset(); @@ -141,21 +131,20 @@ namespace testing_helpers { ADD_FAILURE(); } - std::string RestoreFileLogger::logFile() { - if (_scope) { - // beware for race condition - // example: - // LOG(INFO) << ... - // auto file = logger.logFile() - // auto content = ReadContentFromFile(file) - // ... it is not guaranteed that the content will contain (yet) the LOG(INFO) - std::future filename = _handle->call(&g2::FileSink::fileName); - _log_file = filename.get(); - } - return _log_file; - } - + if (_scope) { + // beware for race condition + // example: + // LOG(INFO) << ... + // auto file = logger.logFile() + // auto content = ReadContentFromFile(file) + // ... it is not guaranteed that the content will contain (yet) the LOG(INFO) + std::future filename = _handle->call(&g2::FileSink::fileName); + _log_file = filename.get(); + } + return _log_file; + } + // Beware of race between LOG(...) and this function. // since LOG(...) passes two queues but the handle::call only passes one queue // the handle::call can happen faster @@ -166,12 +155,4 @@ namespace testing_helpers { auto file = filename.get(); return readFileToText(file); } - - - - - - - - } // testing_helpers