diff --git a/README b/README index 208a98b..fea6c84 100644 --- a/README +++ b/README @@ -1,3 +1,18 @@ +LATEST CHANGES +===================== +2012, Oct 14th +* Complete threadsafe use of localtime for timestamp. Reference g2time +* Runtime change of filename, request of filename. Reference g2logwoker g2future +Tested on +x86 Windows7: Visual Studio 2012 Desktop Express +x86 Ubuntu 11.04: gcc 4.7.3 +x64 Ubuntu 12...: gcc 4.7.2 +x64 Windows7: Visual Studio 2012 Professional + +NOTE: It does NOT work with "Visual Studio 11 Beta" + + + HOW TO BUILD =================== This g2log is a snapshot from KjellKod repository. @@ -5,19 +20,14 @@ It contains what is needed to build example, unit test and performance test of g justthread C++11 thread library is no longer needed. On Windows it is enough to use Visual Studio 11 (2012) -On Linux it is enough to use gcc4.7. -The CMakeFile.txt is updated to reflect that, the justthread parts is +On Linux it is enough to use gcc4.7.2. The CMakeFile.txt +is updated to reflect that, the justthread parts is commented away in case someone still needs that. +If you do not have gcc 4.7.2 If you want to integrate g2log in your own software you need -the following files -g2log/src/ - g2log.cpp/h - g2logworker.cpp/h - crashhandler.h (crashhandler_win.cpp or crashhandler_unix.cpp) - shared_queue.h - active.h/cpp +the files under g2log/src BUILDING g2log: @@ -58,7 +68,7 @@ glog is not included and must be installed if you want to run the comparison tes About the Active Object -------------------- -Is made with standard C++ components with the help of the latest C++0x and std::thread features (thanks to justthread). For more details see www.kjellkod.cc/active-object-with-cpp0x. An example is provided. Other examples on pre C++0x Active Objects can also be found at www.kjellkod.cc (code page) +Is made with standard C++ components with the help of the latest C++11 and std::thread features (thanks to justthread). For more details see www.kjellkod.cc/active-object-with-cpp0x. An example is provided. Other examples on pre C++0x Active Objects can also be found at www.kjellkod.cc (code page) If you like it (or not) it would be nice with some feedback. That way I can improve g2log and it is also nice to see if someone is using it. diff --git a/g2log/CMakeLists.txt b/g2log/CMakeLists.txt index ede7d12..dd94808 100644 --- a/g2log/CMakeLists.txt +++ b/g2log/CMakeLists.txt @@ -1,10 +1,10 @@ # CMakeLists.txt cmake configuration for g2log test # g2log is a KjellKod Logger -# 2011 @author Kjell Hedström, hedstrom@kjellkod.cc +# 2011 @author Kjell Hedström, hedstrom@kjellkod.cc # ================================================================== # 2010 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own -# risk and comes with no warranties. -# +# risk and comes with no warranties. +# # This code is yours to share, use and modify with no strings attached # and no restrictions or obligations. # =================================================================== @@ -19,7 +19,8 @@ # WINDOWS == README: Example how to setup environment + running an example -# 1. please use the "Visual Studio Command Prompt)" +# Below written for VS11 (2012) +# 1. please use the "Visual Studio Command Prompt 11 (2012)" # 2. from the g2log folder # mkdir build # cd build; @@ -51,6 +52,15 @@ MESSAGE(" LOG_SRC = : ${LOG_SRC}") include_directories(${LOG_SRC}) SET(ACTIVE_CPP0xx_DIR "Release") +# Detect 64 or 32 bit +if (CMAKE_SIZEOF_VOID_P EQUAL 8) + # 64-bit project + SET(64_BIT_OS TRUE) + MESSAGE("A 64-bit OS detected") + else() + SET(64_BIT_OS FALSE) + MESSAGE("A 32-bit OS detected") +endif() IF(UNIX) @@ -74,32 +84,32 @@ IF(UNIX) # include_directories("/usr/include/justthread") ENDIF(UNIX) - - if (MSVC) # VC11 bug: http://code.google.com/p/googletest/issues/detail?id=408 # add_definition(-D_VARIADIC_MAX=10) - # https://github.com/anhstudios/swganh/pull/186/files + # https://github.com/anhstudios/swganh/pull/186/files ADD_DEFINITIONS (/D_VARIADIC_MAX=10) MESSAGE(STATUS "- MSVC: Set variadic max to 10 for MSVC compatibility") - # Remember to set set target properties if using GTEST similar to done below on target "unit_test" - # "set_target_properties(unit_test PROPERTIES COMPILE_DEFINITIONS "GTEST_USE_OWN_TR1_TUPLE=0") + # Remember to set set target properties if using GTEST similar to done below on target "unit_test" + # "set_target_properties(unit_test PROPERTIES COMPILE_DEFINITIONS "GTEST_USE_OWN_TR1_TUPLE=0") endif () -#Visual Studio 2010 -- must use justthread -IF(MSVC10) + +#Visual Studio 2010 -- must use justthread. For now hardcoded for x64 +IF(MSVC10) MESSAGE("") MESSAGE("Windows: Please run the command [cmake -DCMAKE_BUILD_TYPE=Release -G \"Visual Studio 10\" ..]") MESSAGE("if cmake finishes OK, do 'msbuild g2log_by_kjellkod.sln /p:Configuration=Release'") MESSAGE("then run 'Release\\g2log-FATAL-example.exe' or whatever performance test you feel like trying") MESSAGE("") set(PLATFORM_LINK_LIBRIES $ENV{PROGRAMFILES}/JustSoftwareSolutions/JustThread/lib/justthread_vc10_mdd.lib) + #set(PLATFORM_LINK_LIBRIES $ENV{PROGRAMFILES}/JustSoftwareSolutions/JustThread/lib/justthread_vc10x64_mdd.lib) set(SRC_PLATFORM_SPECIFIC ${LOG_SRC}/crashhandler_win.cpp) include_directories("$ENV{PROGRAMFILES}/JustSoftwareSolutions/JustThread/include") ENDIF(MSVC10) # Visual Studio 2011 -- std::thread etc are included with the Visual Studio package, so justthread dependencies are removed -IF(MSVC11) +IF(MSVC11) MESSAGE("") MESSAGE("Windows: Please run the command [cmake -DCMAKE_BUILD_TYPE=Release -G \"Visual Studio 11\" ..]") MESSAGE("if cmake finishes OK, do 'msbuild g2log_by_kjellkod.sln /p:Configuration=Release'") @@ -110,17 +120,19 @@ ENDIF(MSVC11) # GENERIC STEPS - # CODE SOURCES these + - set(SRC_CPP ${LOG_SRC}/g2logworker.cpp ${LOG_SRC}/g2log.cpp) - set(SRC_H ${LOG_SRC}/g2logworker.h ${LOG_SRC}/g2log.h ${LOG_SRC}/crashhandler.h) + # CODE SOURCES these + + set(SRC_CPP ${LOG_SRC}/g2logworker.cpp ${LOG_SRC}/g2log.cpp ${LOG_SRC}/g2time.cpp) + set(SRC_H ${LOG_SRC}/g2logworker.h ${LOG_SRC}/g2log.h ${LOG_SRC}/crashhandler.h ${LOG_SRC}/g2time.h ${LOG_SRC}/g2future.h) set(SRC_FILES ${SRC_CPP} ${SRC_H} ${SRC_PLATFORM_SPECIFIC}) - + # add a ActiveObject library add_library(lib_activeobject ${LOG_SRC}/active.cpp ${LOG_SRC}/active.h ${LOG_SRC}/shared_queue.h) set_target_properties(lib_activeobject PROPERTIES LINKER_LANGUAGE CXX) - + + + # add a g2log library include_directories(src) include_directories(${LOG_SRC}) @@ -135,7 +147,7 @@ ENDIF(MSVC11) # ============================================================================ # OPTIONS: Turn OFF the ones that is of no interest to you # ---- by default all is OFF: except 'g2log-FATAL-example ----- - # ---- the reason for this is that + # ---- the reason for this is that # ----- 1) the performance tests were only thoroughly tested on Ubuntu, not windows- # (g2log windows/linux, but Google's glog only on linux) # @@ -143,7 +155,7 @@ ENDIF(MSVC11) # before it can be "cmake'd" and compiled --- leaving it as OFF for now # ============================================================================ # 1. a simple test example 'g2log-FATAL-example' - option (USE_SIMPLE_EXAMPLE + option (USE_SIMPLE_EXAMPLE "Create simple binaries that runs a few LOG calls" ON) @@ -173,16 +185,16 @@ ENDIF(MSVC11) include_directories (${DIR_EXAMPLE}) add_executable(g2log-FATAL-example ${DIR_EXAMPLE}/main.cpp) target_link_libraries(g2log-FATAL-example lib_activeobject lib_g2logger ${PLATFORM_LINK_LIBRIES}) - endif (USE_SIMPLE_EXAMPLE) + endif (USE_SIMPLE_EXAMPLE) # ========================= # 2. create the g2log's performance tests if (USE_G2LOG_PERFORMANCE) MESSAGE(" g2log performance tests option ON") - include_directories (${DIR_PERFORMANCE}) + include_directories (${DIR_PERFORMANCE}) # MEAN PERFORMANCE TEST - add_executable(g2log-performance-threaded_mean + add_executable(g2log-performance-threaded_mean ${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") @@ -199,33 +211,33 @@ ENDIF(MSVC11) # 3. create the Google glog's performance test - if (USE_GOOGLE_GLOG_PERFORMANCE) + if (USE_GOOGLE_GLOG_PERFORMANCE) MESSAGE(" Google's glog performance tests option ON") - include_directories (${DIR_PERFORMANCE}) - #Linux is easy! + include_directories (${DIR_PERFORMANCE}) + #Linux is easy! if(UNIX) set(GLOG_LIB glog) - # create the the GOOGLE MEAN_PERFORMANCE executable + # create the the GOOGLE MEAN_PERFORMANCE executable add_executable(google_glog-performance-threaded_mean ${DIR_PERFORMANCE}/main_threaded_mean.cpp ${DIR_PERFORMANCE}/performance.h) set_target_properties(google_glog-performance-threaded_mean PROPERTIES COMPILE_DEFINITIONS "GOOGLE_GLOG_PERFORMANCE=1") target_link_libraries(google_glog-performance-threaded_mean lib_activeobject ${GLOG_LIB} ${PLATFORM_LINK_LIBRIES}) - + # create the the GOOGLE MEAN_PERFORMANCE executable add_executable(google_glog-performance-threaded_worst ${DIR_PERFORMANCE}/main_threaded_worst.cpp ${DIR_PERFORMANCE}/performance.h) set_target_properties(google_glog-performance-threaded_worst PROPERTIES COMPILE_DEFINITIONS "GOOGLE_GLOG_PERFORMANCE=1") - target_link_libraries(google_glog-performance-threaded_worst lib_activeobject ${GLOG_LIB} ${PLATFORM_LINK_LIBRIES}) - endif(UNIX) - - # GLOG on Linux is easy - but for Windows trickier,. and it doesn't work (as of yet) + target_link_libraries(google_glog-performance-threaded_worst lib_activeobject ${GLOG_LIB} ${PLATFORM_LINK_LIBRIES}) + endif(UNIX) + + # GLOG on Linux is easy - but for Windows trickier,. and it doesn't work (as of yet) if(WIN32) MESSAGE("******************************************************") - MESSAGE("*** SORRY- Google glog on windows is not preconfigured") - MESSAGE("*** You have to do this yourself: ref CMakeLists.txt") + MESSAGE("*** SORRY- Google glog on windows is not preconfigured") + MESSAGE("*** You have to do this yourself: ref CMakeLists.txt") MESSAGE("******************************************************") - MESSAGE("") - #set(GLOG_DIR ../3rdParty/glog/glog-0.3.1) + MESSAGE("") + #set(GLOG_DIR ../3rdParty/glog/glog-0.3.1) #include_directories(${GLOG_DIR}/src/windows) - endif(WIN32) + endif(WIN32) endif (USE_GOOGLE_GLOG_PERFORMANCE) @@ -233,7 +245,7 @@ ENDIF(MSVC11) # 4. create the unit tests for g2log --- ONLY TESTED THE UNIT TEST ON LINUX - if (USE_G2LOG_UNIT_TEST) + if (USE_G2LOG_UNIT_TEST) MESSAGE(" g2log unit testing option ON") # SETUP for GTEST set(GTEST_DIR ../3rdParty/gtest/gtest-1.6.0__stripped) @@ -242,14 +254,23 @@ ENDIF(MSVC11) add_library(gtest_160_lib ${GTEST_DIR}/src/gtest-all.cc ${GTEST_DIR}/src/gtest_main.cc) enable_testing(true) - add_executable(g2log-unit_test ../test_main/test_main.cpp ${DIR_UNIT_TEST}/test_io.cpp) - set_target_properties(g2log-unit_test PROPERTIES COMPILE_DEFINITIONS "_VARIADIC_MAX=10") + add_executable(g2log-unit_test ../test_main/test_main.cpp ${DIR_UNIT_TEST}/test_io.cpp ${DIR_UNIT_TEST}/test_configuration.cpp) + # obs see this: http://stackoverflow.com/questions/9589192/how-do-i-change-the-number-of-template-arguments-supported-by-msvcs-stdtupl + set_target_properties(g2log-unit_test PROPERTIES COMPILE_DEFINITIONS "_VARIADIC_MAX=10") + # and this: http://stackoverflow.com/questions/2257464/google-test-and-visual-studio-2010-rc set_target_properties(g2log-unit_test PROPERTIES COMPILE_DEFINITIONS "GTEST_USE_OWN_TR1_TUPLE=0") target_link_libraries(g2log-unit_test lib_activeobject lib_g2logger gtest_160_lib ${PLATFORM_LINK_LIBRIES}) - endif (USE_G2LOG_UNIT_TEST) + + + add_executable(g2log-unit_test_filechange ${DIR_UNIT_TEST}/test_filechange.cpp) + set_target_properties(g2log-unit_test_filechange PROPERTIES COMPILE_DEFINITIONS "_VARIADIC_MAX=10") + set_target_properties(g2log-unit_test_filechange PROPERTIES COMPILE_DEFINITIONS "GTEST_USE_OWN_TR1_TUPLE=0") + target_link_libraries(g2log-unit_test_filechange lib_activeobject lib_g2logger gtest_160_lib ${PLATFORM_LINK_LIBRIES}) + + endif (USE_G2LOG_UNIT_TEST) + - diff --git a/g2log/src/active.cpp b/g2log/src/active.cpp index 5369a1c..4fe9d5a 100644 --- a/g2log/src/active.cpp +++ b/g2log/src/active.cpp @@ -4,17 +4,17 @@ * strings attached and no restrictions or obligations. * ============================================================================ * -* Example of a Active Object, using C++0x std::thread mechanisms to make it +* Example of a Active Object, using C++11 std::thread mechanisms to make it * safe for thread communication. * * This was originally published at http://sites.google.com/site/kjellhedstrom2/active-object-with-cpp0x -* and inspired from Herb Sutter's C++0x Active Object +* and inspired from Herb Sutter's C++11 Active Object * http://herbsutter.com/2010/07/12/effective-concurrency-prefer-using-active-objects-instead-of-naked-threads * * The code below uses JustSoftware Solutions Inc std::thread implementation * http://www.justsoftwaresolutions.co.uk * -* Last update 2011-06-23, by Kjell Hedstrom, +* Last update 2012-10-10, by Kjell Hedstrom, * e-mail: hedstrom at kjellkod dot cc * linkedin: http://linkedin.com/se/kjellkod */ diff --git a/g2log/src/active.h b/g2log/src/active.h index 7997158..9d51600 100644 --- a/g2log/src/active.h +++ b/g2log/src/active.h @@ -4,17 +4,17 @@ * strings attached and no restrictions or obligations. * ============================================================================ * -* Example of a Active Object, using C++0x std::thread mechanisms to make it +* Example of a Active Object, using C++11 std::thread mechanisms to make it * safe for thread communication. * * This was originally published at http://sites.google.com/site/kjellhedstrom2/active-object-with-cpp0x -* and inspired from Herb Sutter's C++0x Active Object +* and inspired from Herb Sutter's C++11 Active Object * http://herbsutter.com/2010/07/12/effective-concurrency-prefer-using-active-objects-instead-of-naked-threads * * The code below uses JustSoftware Solutions Inc std::thread implementation * http://www.justsoftwaresolutions.co.uk * -* Last update 2011-06-23, by Kjell Hedstrom, +* Last update 2012-10-10, by Kjell Hedstrom, * e-mail: hedstrom at kjellkod dot cc * linkedin: http://linkedin.com/se/kjellkod */ @@ -36,15 +36,15 @@ class Active { private: Active(const Active&); // c++11 feature not yet in vs2010 = delete; Active& operator=(const Active&); // c++11 feature not yet in vs2010 = delete; - - Active(); // Construction ONLY through factory createActive(); - + Active(); // Construction ONLY through factory createActive(); void doDone(){done_ = true;} void run(); + shared_queue mq_; std::thread thd_; bool done_; // finished flag to be set through msg queue by ~Active + public: virtual ~Active(); void send(Callback msg_); diff --git a/g2log/src/g2future.h b/g2log/src/g2future.h new file mode 100644 index 0000000..75aed02 --- /dev/null +++ b/g2log/src/g2future.h @@ -0,0 +1,70 @@ +#ifndef G2FUTURE_H +#define G2FUTURE_H +/** ========================================================================== +* 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:g2future.h +* Helper functionality to put packaged_tasks in standard container. This +* is especially helpful for background thread processing a la async but through +* an actor pattern (active object), thread pool or similar. +* Created: 2012 by Kjell Hedström +* +* COMMUNITY THANKS: +* The code below is in large thanks to exemplifying code snippets from StackOverflow +* question/answer: http://stackoverflow.com/questions/6230893/developing-c-concurrency-library-with-futures-or-similar-paradigm +* and a discussion between Lars Gullik Bjønnes and Jonathan Wakely's at: http://gcc.gnu.org/ml/gcc-help/2011-11/msg00052.html +* +* Both are highly recommended reads if you are interested in c++ concurrency library +* - Kjell, 2012 +* +* PUBLIC DOMAIN and NOT under copywrite protection. +* ********************************************* */ + + + +#include +#include "active.h" + +namespace g2 { + +// A straightforward technique to move around packaged_tasks. +// Instances of std::packaged_task are MoveConstructible and MoveAssignable, but +// not CopyConstructible or CopyAssignable. To put them in a std container they need +// to be wrapped and their internals "moved" when tried to be copied. +template +struct PretendToBeCopyable +{ + explicit PretendToBeCopyable(Moveable&& m) : move_only_(std::move(m)) {} + PretendToBeCopyable(PretendToBeCopyable& p) : move_only_(std::move(p.move_only_)){} + PretendToBeCopyable(PretendToBeCopyable&& p) : move_only_(std::move(p.move_only_)){} // = default; // so far only on gcc + void operator()() { move_only_(); } // execute +private: + Moveable move_only_; +}; + + +// Generic helper function to avoid repeating the steps for managing +// asynchronous task job (by active object) that returns a future results +// could of course be made even more generic if done more in the way of +// std::async, ref: http://en.cppreference.com/w/cpp/thread/async +// +// Example usage: +// std::unique_ptr bgWorker{Active::createActive()}; +// ... +// auto msg_call=[=](){return ("Hello from the Background");}; +// auto future_msg = g2::spawn_task(msg_lambda, bgWorker.get()); +template +std::future::type> spawn_task(Func func, kjellkod::Active* worker) +{ + typedef typename std::result_of::type result_type; + typedef std::packaged_task task_type; + task_type task(std::move(func)); + + std::future result = task.get_future(); + worker->send(PretendToBeCopyable(std::move(task))); + return std::move(result); +} +} // end namespace g2 +#endif // G2FUTURE_H diff --git a/g2log/src/g2log.cpp b/g2log/src/g2log.cpp index f0044d5..8ee6698 100644 --- a/g2log/src/g2log.cpp +++ b/g2log/src/g2log.cpp @@ -7,7 +7,7 @@ * Filename:g2log.cpp Framework for Logging and Design By Contract * Created: 2011 by Kjell Hedström * - * PUBLIC DOMAIN and Not copywrited since it was built on public-domain software and influenced + * PUBLIC DOMAIN and Not copywrited since it was built on public-domain software and at least in "spirit" influenced * from the following sources * 1. kjellkod.cc ;) * 2. Dr.Dobbs, Petru Marginean: http://drdobbs.com/article/printableArticle.jhtml?articleId=201804215&dept_url=/cpp/ diff --git a/g2log/src/g2log.h b/g2log/src/g2log.h index b013a34..f565a8f 100644 --- a/g2log/src/g2log.h +++ b/g2log/src/g2log.h @@ -8,7 +8,7 @@ * Created: 2011 by Kjell Hedström * * PUBLIC DOMAIN and Not copywrited since it was built on public-domain software and influenced - * from the following sources + * at least in "spirit" from the following sources * 1. kjellkod.cc ;) * 2. Dr.Dobbs, Petru Marginean: http://drdobbs.com/article/printableArticle.jhtml?articleId=201804215&dept_url=/cpp/ * 3. Dr.Dobbs, Michael Schulze: http://drdobbs.com/article/printableArticle.jhtml?articleId=225700666&dept_url=/cpp/ @@ -162,14 +162,14 @@ namespace internal { typedef const std::string& LogEntry; - /** 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 */ void changeFatalInitHandlerForUnitTesting(); +// TODO: LogEntry och FatalMessage borde kunna slås ihop till samma! -/** Trigger for flushing the message queue and exiting the applicaition +/** Trigger for flushing the message queue and exiting the application A thread that causes a FatalMessage will sleep forever until the application has exited (after message flush) */ struct FatalMessage @@ -195,22 +195,22 @@ struct FatalTrigger class LogMessage { public: - LogMessage(const std::string &file, const int line, const std::string& function_, const std::string &level); + LogMessage(const std::string &file, const int line, const std::string& function, const std::string &level); virtual ~LogMessage(); // at destruction will flush the message std::ostringstream& messageStream(){return stream_;} // The __attribute__ generates compiler warnings if illegal "printf" format // IMPORTANT: You muse enable the compiler flag '-Wall' for this to work! - // ref: http://www.unixwiz.net/techtips/gnu-c-attributes.html - // - //If the compiler does not support attributes, disable them + // ref: http://www.unixwiz.net/techtips/gnu-c-attributes.html + // + //If the compiler does not support attributes, disable them #ifndef __GNUC__ #define __attribute__(x) #endif // Coder note: Since it's C++ and not C EVERY CLASS FUNCTION always get a first // compiler given argument 'this' this must be supplied as well, hence '2,3' - // ref: http://www.codemaestro.com/reviews/18 -- ref KjellKod + // ref: http://www.codemaestro.com/reviews/18 -- ref KjellKod void messageSave(const char *printf_like_message, ...) __attribute__((format(printf,2,3) )); diff --git a/g2log/src/g2logworker.cpp b/g2log/src/g2logworker.cpp index 6bbcb54..e797d68 100644 --- a/g2log/src/g2logworker.cpp +++ b/g2log/src/g2logworker.cpp @@ -1,65 +1,45 @@ /** ========================================================================== - * 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. - * ============================================================================ - * Filename:g2LogWorker.cpp Framework for Logging and Design By Contract - * Created: 2011 by Kjell Hedström - * - * PUBLIC DOMAIN and Not copywrited. First published at KjellKod.cc - * ********************************************* */ +* 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. +* ============================================================================ +* Filename:g2LogWorker.cpp Framework for Logging and Design By Contract +* Created: 2011 by Kjell Hedström +* +* PUBLIC DOMAIN and Not under copywrite protection. First published at KjellKod.cc +* ********************************************* */ #include "g2logworker.h" -#include -#include -#include -#include #include #include -#include #include -#include -#include +#include +#include +#include +#include +#include + #include "active.h" #include "g2log.h" #include "crashhandler.h" +#include "g2time.h" +#include "g2future.h" +using namespace g2; using namespace g2::internal; namespace { -typedef std::chrono::steady_clock::time_point time_point; -typedef std::chrono::duration > millisecond; -typedef std::chrono::duration > microsecond; - - -struct LogTime -{ - LogTime() - { - time_t current_time = time(nullptr); - ctime(¤t_time); // fill with time right now - struct tm* ptm = ::localtime(¤t_time); // fill time struct with data - year = ptm->tm_year + 1900; - month = (ptm->tm_mon) +1; - day = ptm->tm_mday; - hour = ptm->tm_hour; - minute = ptm->tm_min; - second = ptm->tm_sec; - } - int year; // Year - 1900 - int month; // [1-12] - int day; // [1-31] - int hour; // [0-23] - int minute; // [0-59] - int second; // [0-60], 1 leap second -}; +static const std::string date_formatted = "%Y/%m/%d"; +static const std::string time_formatted = "%H:%M:%S"; +static const std::string file_name_time_formatted = "%Y%m%d-%H%M%S"; // check for filename validity - filename should not be part of PATH bool isValidFilename(const std::string prefix_filename) { + std::string illegal_characters("/,|<>:#$%{}()[]\'\"^!?+* "); size_t pos = prefix_filename.find_first_of(illegal_characters,0); if(pos != std::string::npos) @@ -72,16 +52,78 @@ bool isValidFilename(const std::string prefix_filename) std::cerr << "Empty filename prefix is not allowed" << std::endl; return false; } + return true; } + + +// Clean up the path if put in by mistake in the prefix +std::string prefixSanityFix(const std::string& prefix) +{ + std::string real_prefix = prefix; + std::remove( real_prefix.begin(), real_prefix.end(), '/'); + std::remove( real_prefix.begin(), real_prefix.end(), '\\'); + std::remove( real_prefix.begin(), real_prefix.end(), '.'); + if(!isValidFilename(real_prefix)) + { + return ""; + } + return real_prefix; +} + + +std::string createLogFileName(const std::string& verified_prefix) +{ + std::stringstream oss_name; + oss_name.fill('0'); + oss_name << verified_prefix << ".g2log."; + oss_name << g2::localtime_formatted(g2::systemtime_now(), file_name_time_formatted); + oss_name << ".log"; + return oss_name.str(); +} + + +bool openLogFile(const std::string& complete_file_with_path, std::ofstream& outstream) +{ + std::ios_base::openmode mode = std::ios_base::out; // for clarity: it's really overkill since it's an ofstream + mode |= std::ios_base::trunc; + outstream.open(complete_file_with_path, mode); + if(!outstream.is_open()) + { + std::ostringstream ss_error; + ss_error << "FILE ERROR: could not open log file:[" << complete_file_with_path << "]"; + ss_error << "\n\t\t std::ios_base state = " << outstream.rdstate(); + std::cerr << ss_error.str().c_str() << std::endl << std::flush; + outstream.close(); + return false; + } + std::ostringstream ss_entry; + // Day Month Date Time Year: is written as "%a %b %d %H:%M:%S %Y" and formatted output as : Wed Sep 19 08:28:16 2012 + ss_entry << "\t\tg2log created log file at: "<< g2::localtime_formatted(g2::systemtime_now(), "%a %b %d %H:%M:%S %Y") << "\n"; + ss_entry << "\t\tLOG format: [YYYY/MM/DD hh:mm:ss.uuu* LEVEL FILE:LINE] message\n\n"; // TODO: if(header) + outstream << ss_entry.str() << std::flush; + outstream.fill('0'); + return true; +} + + +std::unique_ptr createLogFile(const std::string& file_with_full_path) +{ + std::unique_ptr out(new std::ofstream); + std::ofstream& stream(*(out.get())); + bool success_with_open_file = openLogFile(file_with_full_path, stream); + if(false == success_with_open_file) + { + out.release(); // nullptr contained ptr signals error in creating the log file + } + return out; +} } // end anonymous namespace - - -/** The actual background worker, while g2LogWorker gives the - * asynchronous API to put job in the background the g2LogWorkerImpl - * does the actual background thread work */ +/** The Real McCoy Background worker, while g2LogWorker gives the +* asynchronous API to put job in the background the g2LogWorkerImpl +* does the actual background thread work */ struct g2LogWorkerImpl { g2LogWorkerImpl(const std::string& log_prefix, const std::string& log_directory); @@ -89,15 +131,19 @@ struct g2LogWorkerImpl void backgroundFileWrite(g2::internal::LogEntry message); void backgroundExitFatal(g2::internal::FatalMessage fatal_message); + std::string backgroundChangeLogFile(const std::string& directory); + std::string backgroundFileName(); std::string log_file_with_path_; + std::string log_prefix_backup_; // needed in case of future log file changes of directory std::unique_ptr bg_; - std::ofstream out; - time_point start_time_; + std::unique_ptr outptr_; + steady_time_point steady_start_time_; private: g2LogWorkerImpl& operator=(const g2LogWorkerImpl&); // c++11 feature not yet in vs2010 = delete; g2LogWorkerImpl(const g2LogWorkerImpl& other); // c++11 feature not yet in vs2010 = delete; + std::ofstream& filestream(){return *(outptr_.get());} }; @@ -106,112 +152,136 @@ private: // Private API implementation : g2LogWorkerImpl g2LogWorkerImpl::g2LogWorkerImpl(const std::string& log_prefix, const std::string& log_directory) : log_file_with_path_(log_directory) + , log_prefix_backup_(log_prefix) , bg_(kjellkod::Active::createActive()) - , start_time_(std::chrono::steady_clock::now()) + , outptr_(new std::ofstream) + , steady_start_time_(std::chrono::steady_clock::now()) // TODO: ha en timer function steadyTimer som har koll på start { - std::string real_prefix = log_prefix; - // if through a debugger the debugger CAN just throw in the whole path - // replace the path delimiters (unix?) - std::remove( real_prefix.begin(), real_prefix.end(), '/'); - std::remove( real_prefix.begin(), real_prefix.end(), '\\'); - if(!isValidFilename(real_prefix)) + log_prefix_backup_ = prefixSanityFix(log_prefix); + if(!isValidFilename(log_prefix_backup_)) { // illegal prefix, refuse to start std::cerr << "g2log: forced abort due to illegal log prefix [" << log_prefix <<"]" << std::endl << std::flush; abort(); } - using namespace std; - LogTime t; - ostringstream oss_name; - oss_name.fill('0'); - oss_name << real_prefix << ".g2log."; - oss_name << t.year << setw(2) << t.month << setw(2) << t.day; - oss_name << "-" << setw(2) << t.hour << setw(2) << t.minute << setw(2) << t.second; - oss_name << ".log"; - log_file_with_path_ += oss_name.str(); - - // open the log file - std::ios_base::openmode mode = std::ios_base::out; // for clarity: it's really overkill since it's an ofstream - mode |= std::ios_base::trunc; - out.open(log_file_with_path_, mode); - if(!out.is_open()) - { - std::ostringstream ss_error; - ss_error << "Fatal error could not open log file:[" << log_file_with_path_ << "]"; - ss_error << "\n\t\t std::ios_base state = " << out.rdstate(); - std::cerr << ss_error.str().c_str() << std::endl << std::flush; - assert(false && "cannot open log file at startup"); - } - std::ostringstream ss_entry; - time_t creation_time; - time(&creation_time); - ss_entry << "\t\tg2log created log file at: " << ctime(&creation_time); - ss_entry << "\t\tLOG format: [YYYY/MM/DD hh:mm:ss.uuu* LEVEL FILE:LINE] message\n\n"; - out << ss_entry.str() << std::flush; - out.fill('0'); + std::string file_name = createLogFileName(log_prefix_backup_); + log_file_with_path_ = log_directory + file_name; + outptr_ = createLogFile(log_file_with_path_); + assert((nullptr != outptr_) && "cannot open log file at startup"); } + g2LogWorkerImpl::~g2LogWorkerImpl() { std::ostringstream ss_exit; - time_t exit_time; - time(&exit_time); bg_.reset(); // flush the log queue - ss_exit << "\n\t\tg2log file shutdown at: " << ctime(&exit_time); - out << ss_exit.str() << std::flush; + ss_exit << "\n\t\tg2log file shutdown at: " << g2::localtime_formatted(g2::systemtime_now(), time_formatted); + filestream() << ss_exit.str() << std::flush; } + void g2LogWorkerImpl::backgroundFileWrite(LogEntry message) { using namespace std; - LogTime t; - auto timesnapshot = chrono::steady_clock::now(); - out << "\n" << t.year << "/" << setw(2) << t.month << "/" << setw(2) << t.day; - out << " " << setw(2) << t.hour << ":"<< setw(2) << t.minute <<":"<< setw(2) << t.second; - out << "." << chrono::duration_cast(timesnapshot - start_time_).count(); //microseconds + std::ofstream& out(filestream()); + auto system_time = g2::systemtime_now(); + auto steady_time = std::chrono::steady_clock::now(); + out << "\n" << g2::localtime_formatted(system_time, date_formatted); + out << " " << g2::localtime_formatted(system_time, time_formatted); // TODO: time kommer från LogEntry + out << "." << chrono::duration_cast(steady_time - steady_start_time_).count(); //microseconds TODO: ta in min g2clocka här StopWatch out << "\t" << message << std::flush; } + void g2LogWorkerImpl::backgroundExitFatal(FatalMessage fatal_message) { backgroundFileWrite(fatal_message.message_); - backgroundFileWrite("Log flushed successfully to disk: exiting"); - std::cout << "g2log exiting successfully after receiving fatal event" << std::endl; - std::cout << "Log file at: [" << log_file_with_path_ << "]\n" << std::endl << std::flush; - out.close(); + backgroundFileWrite("Log flushed successfully to disk \nExiting"); + std::cerr << "g2log exiting after receiving fatal event" << std::endl; + std::cerr << "Log file at: [" << log_file_with_path_ << "]\n" << std::endl << std::flush; + filestream().close(); exitWithDefaultSignalHandler(fatal_message.signal_id_); perror("g2log exited after receiving FATAL trigger. Flush message status: "); // should never reach this point } +std::string g2LogWorkerImpl::backgroundChangeLogFile(const std::string& directory) +{ + std::string file_name = createLogFileName(log_prefix_backup_); + std::string prospect_log = directory + file_name; + std::unique_ptr log_stream = createLogFile(prospect_log); + if(nullptr == log_stream) + { + backgroundFileWrite("Unable to change log file. Illegal filename or busy? Unsuccessful log name was:" + prospect_log); + return ""; // no success + } -// BELOW g2LogWorker + std::ostringstream ss_change; + ss_change << "\n\tChanging log file from : " << log_file_with_path_; + ss_change << "\n\tto new location: " << prospect_log << "\n"; + backgroundFileWrite(ss_change.str().c_str()); + ss_change.str(""); + + // setting the new log as active + std::string old_log = log_file_with_path_; + log_file_with_path_ = prospect_log; + outptr_ = std::move(log_stream); + ss_change << "\n\tNew log file. The previous log file was at: "; + ss_change << old_log; + backgroundFileWrite(ss_change.str()); + return log_file_with_path_; +} + +std::string g2LogWorkerImpl::backgroundFileName() +{ + return log_file_with_path_; +} + + + + +// +// ***** BELOW g2LogWorker ***** // Public API implementation - g2LogWorker::g2LogWorker(const std::string& log_prefix, const std::string& log_directory) - : pimpl_(new g2LogWorkerImpl(log_prefix, log_directory)) - , log_file_with_path_(pimpl_->log_file_with_path_) - { - } +// +g2LogWorker::g2LogWorker(const std::string& log_prefix, const std::string& log_directory) + : pimpl_(new g2LogWorkerImpl(log_prefix, log_directory)) + , log_file_with_path_(pimpl_->log_file_with_path_) +{ + assert((pimpl_ != nullptr) && "shouild never happen"); +} - g2LogWorker::~g2LogWorker() - { - pimpl_.reset(); - std::cout << "\nExiting, log location: " << log_file_with_path_ << std::endl << std::flush; - } +g2LogWorker::~g2LogWorker() +{ + pimpl_.reset(); + std::cerr << "\nExiting, log location: " << log_file_with_path_ << std::endl << std::flush; +} - void g2LogWorker::save(g2::internal::LogEntry msg) - { - pimpl_->bg_->send(std::bind(&g2LogWorkerImpl::backgroundFileWrite, pimpl_.get(), msg)); - } +void g2LogWorker::save(g2::internal::LogEntry msg) +{ + pimpl_->bg_->send(std::bind(&g2LogWorkerImpl::backgroundFileWrite, pimpl_.get(), msg)); +} - void g2LogWorker::fatal(g2::internal::FatalMessage fatal_message) - { - pimpl_->bg_->send(std::bind(&g2LogWorkerImpl::backgroundExitFatal, pimpl_.get(), fatal_message)); - } +void g2LogWorker::fatal(g2::internal::FatalMessage fatal_message) +{ + pimpl_->bg_->send(std::bind(&g2LogWorkerImpl::backgroundExitFatal, pimpl_.get(), fatal_message)); +} - std::string g2LogWorker::logFileName() const - { - return log_file_with_path_; - } +std::future g2LogWorker::changeLogFile(const std::string& log_directory) +{ + kjellkod::Active* bgWorker = pimpl_->bg_.get(); + //auto future_result = g2::spawn_task(std::bind(&g2LogWorkerImpl::backgroundChangeLogFile, pimpl_.get(), log_directory), bgWorker); + auto bg_call = [this, log_directory]() {return pimpl_->backgroundChangeLogFile(log_directory);}; + auto future_result = g2::spawn_task(bg_call, bgWorker); + return std::move(future_result); +} + +std::future g2LogWorker::logFileName() +{ + kjellkod::Active* bgWorker = pimpl_->bg_.get(); + auto bg_call=[&](){return pimpl_->backgroundFileName();}; + auto future_result = g2::spawn_task(bg_call ,bgWorker); + return std::move(future_result); +} diff --git a/g2log/src/g2logworker.h b/g2log/src/g2logworker.h index a89ef56..41ef952 100644 --- a/g2log/src/g2logworker.h +++ b/g2log/src/g2logworker.h @@ -1,75 +1,57 @@ -#ifndef G2_LOG_WORKER_H_ -#define G2_LOG_WORKER_H_ -/** ========================================================================== - * 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. - * ============================================================================ - * Filename:g2logworker.h Framework for Logging and Design By Contract - * Created: 2011 by Kjell Hedström - * - * PUBLIC DOMAIN and Not copywrited. First published at KjellKod.cc - * ********************************************* */ - - -#include -#include "g2log.h" - -struct g2LogWorkerImpl; - -/** -* \param log_prefix is the 'name' of the binary, this give the log name 'LOG-'name'-... -* \param log_directory gives the directory to put the log files */ -class g2LogWorker -{ -public: - g2LogWorker(const std::string& log_prefix, const std::string& log_directory); - virtual ~g2LogWorker(); - - /// pushes in background thread (asynchronously) input messages to log file - void save(g2::internal::LogEntry entry); - - /// Will push a fatal message on the queue, this is the last message to be processed - /// this way it's ensured that all existing entries were flushed before 'fatal' - /// Will abort the application! - void fatal(g2::internal::FatalMessage fatal_message); - - /// basically only needed for unit-testing or specific log management post logging - std::string logFileName() const; - -private: - std::unique_ptr pimpl_; - const std::string log_file_with_path_; - - g2LogWorker(const g2LogWorker&); // c++11 feature not yet in vs2010 = delete; - g2LogWorker& operator=(const g2LogWorker&); // c++11 feature not yet in vs2010 = delete; -}; - - -/* Possible improvement --- for making localtime thread safe - Is there really no C++11 localtime replacement?! - -// localtime_r (POSIX) or localtime_s (WIN) thanks to http://stackoverflow.com/questions/7313919/c11-alternative-to-localtime-r -namespace query { - char localtime_r( ... ); - - struct has_localtime_r - { enum { value = sizeof localtime_r( std::declval< std::time_t * >(), std::declval< std::tm * >() ) - == sizeof( std::tm * ) }; }; +#ifndef G2_LOG_WORKER_H_ +#define G2_LOG_WORKER_H_ +/** ========================================================================== +* 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. +* ============================================================================ +* Filename:g2logworker.h Framework for Logging and Design By Contract +* Created: 2011 by Kjell Hedström +* +* PUBLIC DOMAIN and Not copywrited. First published at KjellKod.cc +* ********************************************* */ - template< bool available > struct safest_localtime { - static std::tm *call( std::time_t const *t, std::tm *r ) - { return localtime_r( t, r ); } - }; +#include +#include +#include - template<> struct safest_localtime< false > { - static std::tm *call( std::time_t const *t, std::tm *r ) - { return std::localtime( t ); } - }; -} -std::tm *localtime( std::time_t const *t, std::tm *r ) - { return query::safest_localtime< query::has_localtime_r::value >().call( t, r ); } - */ - -#endif // LOG_WORKER_H_ +#include "g2log.h" + +struct g2LogWorkerImpl; + +/** +* \param log_prefix is the 'name' of the binary, this give the log name 'LOG-'name'-... +* \param log_directory gives the directory to put the log files */ +class g2LogWorker +{ +public: + g2LogWorker(const std::string& log_prefix, const std::string& log_directory); + virtual ~g2LogWorker(); + + /// pushes in background thread (asynchronously) input messages to log file + void save(g2::internal::LogEntry entry); + + /// Will push a fatal message on the queue, this is the last message to be processed + /// this way it's ensured that all existing entries were flushed before 'fatal' + /// Will abort the application! + void fatal(g2::internal::FatalMessage fatal_message); + + /// Attempt to change the current log file to another name/location. + /// returns filename with full path if successful, else empty string + std::future changeLogFile(const std::string& log_directory); + + /// Probably only needed for unit-testing or specific log management post logging + /// request to get log name is processed in FIFO order just like any other background job. + std::future logFileName(); + +private: + std::unique_ptr pimpl_; + const std::string log_file_with_path_; + + g2LogWorker(const g2LogWorker&); // c++11 feature not yet in vs2010 = delete; + g2LogWorker& operator=(const g2LogWorker&); // c++11 feature not yet in vs2010 = delete; +}; + + +#endif // LOG_WORKER_H_ diff --git a/g2log/src/g2time.cpp b/g2log/src/g2time.cpp new file mode 100644 index 0000000..60343a7 --- /dev/null +++ b/g2log/src/g2time.cpp @@ -0,0 +1,78 @@ +/** ========================================================================== +* 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:g2time.cpp cross-platform, thread-safe replacement for C++11 non-thread-safe +* localtime (and similar) +* Created: 2012 by Kjell Hedström +* +* PUBLIC DOMAIN and Not under copywrite protection. First published for g2log at KjellKod.cc +* ********************************************* */ + +#include "g2time.h" + +#include +#include +#include +#include +#include +#include + + +namespace g2 { namespace internal { + // This mimics the original "std::put_time(const std::tm* tmb, const charT* fmt)" + // This is needed since latest version (at time of writing) of gcc4.7 does not implement this library function yet. + // return value is SIMPLIFIED to only return a std::string + std::string put_time(const struct tm* tmb, const char* c_time_format) + { +#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) + std::ostringstream oss; + oss.fill('0'); + oss << std::put_time(const_cast(tmb), c_time_format); // BOGUS hack done for VS2012: C++11 non-conformant since it SHOULD take a "const struct tm* " + return oss.str(); +#else // LINUX + const size_t size = 1024; + char buffer[size]; // OBS: kolla om inte std::put_time finns. This is way more buffer space then we need + auto success = std::strftime(buffer, size, c_time_format, tmb); // Ta över denna funktion till BitBucket/code/g2log sen då denna är utvecklingsbranchen + if (0 == success) + return c_time_format; // error return result indeterminate due to buffer overflow - should throw instead? + return buffer; // implicit conversion to std::string +#endif + } +} // internal +} // g2 + + + +namespace g2 +{ +std::time_t systemtime_now() +{ + system_time_point system_now = std::chrono::system_clock::now(); + return std::chrono::system_clock::to_time_t(system_now); +} + + +tm localtime(const std::time_t& time) +{ + struct tm tm_snapshot; +#if !(defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) + localtime_r(&time, &tm_snapshot); // POSIX +#else + localtime_s(&tm_snapshot, &time); // windsows +#endif + return tm_snapshot; +} + +/// returns a std::string with content of time_t as localtime formatted by input format string +/// * format string must conform to std::put_time +/// This is similar to std::put_time(std::localtime(std::time_t*), time_format.c_str()); +std::string localtime_formatted(const std::time_t& time_snapshot, const std::string& time_format) +{ + std::tm t = localtime(time_snapshot); // could be const, but cannot due to VS2012 is non conformant for C++11's std::put_time (see above) + std::stringstream buffer; + buffer << g2::internal::put_time(&t, time_format.c_str()); // format example: //"%Y/%m/%d %H:%M:%S"); + return buffer.str(); +} +} // g2 diff --git a/g2log/src/g2time.h b/g2log/src/g2time.h new file mode 100644 index 0000000..7198f1a --- /dev/null +++ b/g2log/src/g2time.h @@ -0,0 +1,45 @@ +#ifndef G2_TIME_H_ +#define G2_TIME_H_ +/** ========================================================================== +* 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:g2time.h cross-platform, thread-safe replacement for C++11 non-thread-safe +* localtime (and similar) +* Created: 2012 by Kjell Hedström +* +* PUBLIC DOMAIN and Not under copywrite protection. First published for g2log at KjellKod.cc +* ********************************************* */ + +#include +#include +#include + +// FYI: +// namespace g2::internal ONLY in g2time.cpp +// std::string put_time(const struct tm* tmb, const char* c_time_format) + +namespace g2 +{ + typedef std::chrono::steady_clock::time_point steady_time_point; + typedef std::chrono::time_point system_time_point; + typedef std::chrono::milliseconds milliseconds; + typedef std::chrono::microseconds microseconds; + + // wrap for std::chrono::system_clock::now() + std::time_t systemtime_now(); + + /** return time representing POD struct (ref ctime + wchar) that is normally + * retrieved with std::localtime. g2::localtime is threadsafe which std::localtime is not. + * g2::localtime is probably used together with @ref g2::systemtime_now */ + tm localtime(const std::time_t& time); + + /** format string must conform to std::put_time's demands. + * WARNING: At time of writing there is only so-so compiler support for + * std::put_time. A possible fix if your c++11 library is not updated is to + * modify this to use std::strftime instead */ + std::string localtime_formatted(const std::time_t& time_snapshot, const std::string& time_format) ; +} + +#endif diff --git a/g2log/src/shared_queue.h b/g2log/src/shared_queue.h index b0806fc..1a8b223 100644 --- a/g2log/src/shared_queue.h +++ b/g2log/src/shared_queue.h @@ -9,7 +9,7 @@ * the help from the std::thread library from JustSoftwareSolutions * ref: http://www.stdthread.co.uk/doc/headers/mutex.html * -* This exampel was inspired by Anthony Williams lock-based data structures in +* This exampel was totally inspired by Anthony Williams lock-based data structures in * Ref: "C++ Concurrency In Action" http://www.manning.com/williams */ #ifndef SHARED_QUEUE @@ -47,7 +47,7 @@ public: if(queue_.empty()){ return false; } - popped_item=queue_.front(); + popped_item=std::move(queue_.front()); queue_.pop(); return true; } @@ -59,7 +59,7 @@ public: { // The 'while' loop below is equal to data_cond_.wait(lock); //data_cond_.wait(lock, [](bool result){return !queue_.empty();}); } - popped_item=queue_.front(); + popped_item=std::move(queue_.front()); queue_.pop(); } diff --git a/g2log/test_example/main.cpp b/g2log/test_example/main.cpp index 08721ee..0e562f6 100644 --- a/g2log/test_example/main.cpp +++ b/g2log/test_example/main.cpp @@ -12,12 +12,32 @@ namespace { #if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) - const std::string path_to_log_file = "./"; +const std::string path_to_log_file = "./"; #else - const std::string path_to_log_file = "/tmp/"; +const std::string path_to_log_file = "/tmp/"; #endif } +namespace example_fatal +{ +void killWithContractFailureIfNonEqual(int first, int second) +{ + CHECK(first == second) << "Test to see if contract works: onetwothree: " << 123 << ". This should be at the end of the log, and will exit this example"; +} + +// on Ubunti this caused get a compiler warning with gcc4.6 +// from gcc 4.7.2 (at least) it causes a crash (as expected) +// On windows it'll probably crash too. +void tryToKillWithIllegalPrintout() +{ + std::cout << "\n\n***** Be ready this last example may 'abort' if on Windows/Linux_gcc4.7 " << std::endl << std::flush; + std::cout << "************************************************************\n\n" << std::endl << std::flush; + std::this_thread::sleep_for(std::chrono::seconds(1)); + const std::string logging = "logging"; + LOGF(DEBUG, "ILLEGAL PRINTF_SYNTAX EXAMPLE. WILL GENERATE compiler warning.\n\nbadly formatted message:[Printf-type %s is the number 1 for many %s]", logging.c_str()); +} +} // example fatal + int main(int argc, char** argv) { double pi_d = 3.1415926535897932384626433832795; @@ -26,11 +46,12 @@ int main(int argc, char** argv) g2LogWorker logger(argv[0], path_to_log_file); g2::initializeLogging(&logger); + std::future log_file_name = logger.logFileName(); std::cout << "*** This is an example of g2log " << std::endl; std::cout << "*** It WILL exit by a FATAL trigger in the end" << std::endl; std::cout << "*** Please see the generated log and compare to " << std::endl; std::cout << "*** the code at g2log/test_example/main.cpp" << std::endl; - std::cout << "\n*** Log file: [" << logger.logFileName() << "]\n\n" << std::endl; + std::cout << "\n*** Log file: [" << log_file_name.get() << "]\n\n" << std::endl; LOGF(INFO, "Hi log %d", 123); LOG(INFO) << "Test SLOG INFO"; @@ -55,19 +76,22 @@ int main(int argc, char** argv) LOG_IF(FATAL, (2>3)) << "This message should NOT throw"; LOGF(DEBUG, "This API is popular with some %s", "programmers"); LOGF_IF(DEBUG, (1<2), "If true, then this %s will be logged", "message"); - { - // OK --- on Ubunti this WILL get a compiler warning - // On windows it'll probably crash std::cout << "\n\n***** Be ready on Windows this example will 'abort' " << std::endl; - std::cout << "\n\n***** Be ready this last example may 'abort' if on Windows/Linux_gcc4.7 " << std::endl << std::flush; - std::cout << "************************************************************\n\n" << std::endl << std::flush; - std::this_thread::sleep_for(std::chrono::seconds(1)); - const std::string logging = "logging"; - LOGF(DEBUG, "ILLEGAL PRINTF_SYNTAX EXAMPLE. WILL GENERATE compiler warning.\n\nbadly formatted message:[Printf-type %s is the number 1 for many %s]", logging.c_str()); - } + // OK --- on Ubunti this caused get a compiler warning with gcc4.6 + // from gcc 4.7.2 (at least) it causes a crash (as expected) + // On windows itll probably crash + // ---- IF you want to try 'FATAL' contract failure please comment away + // ----- the 'illegalPrinout' call below + example_fatal::tryToKillWithIllegalPrintout(); - std::cout << "\n\n***** Be ready this last example will trigger 'abort' " << std::endl; - std::cout << "************************************************************\n\n" << std::endl; - CHECK(1<2) << "SHOULD NOT SEE THIS MESSAGE"; - CHECK(1>2) << "Test to see if contract works: onetwothree: " << 123 << ". This should be at the end of the log, and will exit this example"; + + CHECK(1<2) << "SHOULD NOT SEE THIS MESSAGE"; // non-failure contract + + std::cout << "\n\n***** Be ready this last example WILL trigger 'abort' (if not done earlier)" << std::endl; + // exit by contract failure. See the dumped log, the function + // that caused the fatal exit should be shown in the stackdump + int smaller = 1; + int larger = 2; + example_fatal::killWithContractFailureIfNonEqual(smaller, larger); } + diff --git a/g2log/test_unit/test_configuration.cpp b/g2log/test_unit/test_configuration.cpp new file mode 100644 index 0000000..b26ba9c --- /dev/null +++ b/g2log/test_unit/test_configuration.cpp @@ -0,0 +1,195 @@ +/** ========================================================================== +* 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. +* ============================================================================*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include "g2time.h" +#include "g2future.h" + +TEST(Configuration, LOG) +{ // ref: http://www.cplusplus.com/reference/clibrary/ctime/strftime/ + // ref: http://en.cppreference.com/w/cpp/io/manip/put_time + // Day Month Date Time Year: is written as "%a %b %d %H:%M:%S %Y" and formatted output as : Wed Sep 19 08:28:16 2012 + // --- WARNING: The try/catch setup does NOT work,. but for fun and for fake-clarity I leave it + // --- For formatting options to std::put_time that are NOT YET implemented on Windows fatal errors/assert will occurr + // --- the last example is such an example. + try + { + std::cout << g2::localtime_formatted(g2::systemtime_now(), "%a %b %d %H:%M:%S %Y") << std::endl; + std::this_thread::sleep_for(std::chrono::seconds(1)); + std::cout << g2::localtime_formatted(g2::systemtime_now(), "%%Y/%%m/%%d %%H:%%M:%%S = %Y/%m/%d %H:%M:%S") << std::endl; +#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) + std::cerr << "Formatting options skipped due to VS2012, C++11 non-conformance for" << std::endl; + std::cerr << " some formatting options. The skipped code was:\n\t\t %EX %Ec, \n(see http://en.cppreference.com/w/cpp/io/manip/put_time for details)" << std::endl; +#else + std::cout << "C++11 new formatting options:\n" << g2::localtime_formatted(g2::systemtime_now(), "%%EX: %EX\n%%z: %z\n%%Ec: %Ec") << std::endl; +#endif + } +// This does not work. Other kinds of fatal exits (on Windows) seems to be used instead of exceptions +// Maybe a signal handler catch would be better? --- TODO: Make it better, both failing and correct +catch(...) +{ + ADD_FAILURE() << "On this platform the library does not support given (C++11?) specifiers"; + return; +} +ASSERT_TRUE(true); // no exception. all good +} + +std::future sillyFutureReturn() +{ + std::packaged_task task([](){return std::string("Hello Future");}); // wrap the function + std::future result = task.get_future(); // get a future + std::thread(std::move(task)).detach(); // launch on a thread + std::cout << "Waiting..."; + result.wait(); + return result; // already wasted +} +TEST(Configuration, FutureSilly) +{ + std::string hello = sillyFutureReturn().get(); + ASSERT_STREQ(hello.c_str(), "Hello Future"); +} + +struct MsgType +{ + std::string msg_; + MsgType(std::string m): msg_(m){}; + std::string msg(){return msg_;} +}; + + +TEST(TestOf_CopyableCall, Expecting_SmoothSailing) +{ + using namespace kjellkod; + const std::string str("Hello from struct"); + MsgType type(str); + std::unique_ptr bgWorker(Active::createActive()); + std::future fstring = + g2::spawn_task(std::bind(&MsgType::msg, type), bgWorker.get()); + ASSERT_STREQ(str.c_str(), fstring.get().c_str()); +} + + + +TEST(TestOf_CopyableLambdaCall, Expecting_AllFine) +{ + using namespace kjellkod; + std::unique_ptr bgWorker(Active::createActive()); + + // lambda task + const std::string str_standalone("Hello from standalone"); + auto msg_lambda=[=](){return (str_standalone+str_standalone);}; + std::string expected(str_standalone+str_standalone); + + auto fstring_standalone = g2::spawn_task(msg_lambda, bgWorker.get()); + ASSERT_STREQ(expected.c_str(), fstring_standalone.get().c_str()); +} + + + + +template +std::future::type> ObsoleteSpawnTask(F f) +{ + typedef typename std::result_of::type result_type; + typedef std::packaged_task task_type; + + task_type task(std::move(f)); + std::future result = task.get_future(); + + std::vector> vec; + vec.push_back(g2::PretendToBeCopyable(std::move(task))); + std::thread(std::move(vec.back())).detach(); + result.wait(); + return std::move(result); +} + +TEST(TestOf_ObsoleteSpawnTaskWithStringReturn, Expecting_FutureString) +{ + std::string str("Hello"); + std::string expected(str+str); + auto msg_lambda=[=](){return (str+str);}; + auto future_string = ObsoleteSpawnTask(msg_lambda); + + ASSERT_STREQ(expected.c_str(), future_string.get().c_str()); +} +// gcc thread example below +// tests code below copied from mail-list conversion between +// Lars Gullik Bjønnes and Jonathan Wakely +// http://gcc.gnu.org/ml/gcc-help/2011-11/msg00052.html + +// -------------------------------------------------------------- +namespace WORKING +{ + using namespace g2; + +#include + +#include +#include +#include +#include + + std::vector> vec; + + template + std::future::type> spawn_task(F f) + { + typedef typename std::result_of::type result_type; + typedef std::packaged_task task_type; + + task_type task(std::move(f)); + std::future res = task.get_future(); + + vec.push_back( + PretendToBeCopyable( + std::move(task))); + + std::thread([]() + { + auto task = std::move(vec.back()); + vec.pop_back(); + task(); + } + ).detach(); + + return std::move(res); + } + + + + double get_res() + { + return 42.2; + } + + std::string msg2(){return "msg2";} +} // WORKING + +TEST(Yalla, Testar) +{ + using namespace WORKING; + auto f = spawn_task(get_res); + std::cout << "Res = " << f.get() << std::endl; + + auto f2 = spawn_task(msg2); + std::cout << "Res2 = " << f2.get() << std::endl; + + + ASSERT_TRUE(true); +} + + + + + diff --git a/g2log/test_unit/test_filechange.cpp b/g2log/test_unit/test_filechange.cpp new file mode 100644 index 0000000..d9a5bd1 --- /dev/null +++ b/g2log/test_unit/test_filechange.cpp @@ -0,0 +1,188 @@ +/** ========================================================================== +* 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. +* ============================================================================*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "g2log.h" +#include "g2logworker.h" + + +namespace { // anonymous +const char* name_path_1 = "./some_fake_DirectoryOrName_1_"; +const char* name_path_2 = "./some_fake_DirectoryOrName_3_"; + +g2LogWorker* g_logger_ptr = nullptr; + + +bool isTextAvailableInContent(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); + if(!in.is_open()) + { + return ""; // error just return empty string - test will 'fault' + } + std::ostringstream oss; + oss << in.rdbuf(); + std::string content(oss.str()); + return content; +} + +bool removeFile(std::string path_to_file) +{ + return (0 == std::remove(path_to_file.c_str())); +} + +class LogFileCleaner // RAII cluttering files cleanup +{ +private: + std::vector logs_to_clean_; + std::mutex g_mutex; +public: + size_t size(){return logs_to_clean_.size();} + virtual ~LogFileCleaner() { + std::lock_guard lock(g_mutex); + { + for (std::string p : logs_to_clean_) + { + if(false == removeFile(p)) + { + ADD_FAILURE() << "UNABLE to remove: " << p.c_str() << std::endl; + } + } + logs_to_clean_.clear(); + } // mutex + } + + void addLogToClean(std::string path_to_log) { + std::lock_guard lock(g_mutex); + { + if (std::find(logs_to_clean_.begin(), logs_to_clean_.end(), path_to_log.c_str()) == logs_to_clean_.end()) + logs_to_clean_.push_back(path_to_log); + } + } +}; LogFileCleaner* g_cleaner_ptr = nullptr; + +std::string changeDirectoryOrName(std::string new_file_to_create) +{ + static std::mutex m; + static int count; + std::lock_guard lock(m); + { + std::string add_count = std::to_string(++count) + "_"; + auto new_log = g_logger_ptr->changeLogFile(new_file_to_create+add_count).get(); + if(!new_log.empty()) g_cleaner_ptr->addLogToClean(new_log); + return new_log; + } +} + +} // anonymous + + + + + +// TODO: this must change. Initialization of this is done here! and not in a special test_main.cpp +// which MAY be OK ... however it is also very redundant with test_io + + + +TEST(TestOf_GetFileName, Expecting_ValidLogFile) +{ + LOG(INFO) << "test_filechange, Retrieving file name: "; + ASSERT_NE(g_logger_ptr, nullptr); + std::future f_get_old_name = g_logger_ptr->logFileName(); + ASSERT_TRUE(f_get_old_name.valid()); + ASSERT_FALSE(f_get_old_name.get().empty()); +} + + + +TEST(TestOf_ChangingLogFile, Expecting_NewLogFileUsed) +{ + auto old_log = g_logger_ptr->logFileName().get(); + std::string name = changeDirectoryOrName(name_path_1); + auto new_log = g_logger_ptr->changeLogFile(name).get(); +} + + +TEST(TestOf_ManyThreadsChangingLogFileName, Expecting_EqualNumberLogsCreated) +{ + auto old_log = g_logger_ptr->logFileName().get(); + if(!old_log.empty()) g_cleaner_ptr->addLogToClean(old_log); + + LOG(INFO) << "SoManyThreadsAllDoingChangeFileName"; + std::vector threads; + auto max = 2; + auto size = g_cleaner_ptr->size(); + for(auto count = 0; count < max; ++count) + { + std::string drive = ((count % 2) == 0) ? "./_threadEven_" : "./_threaOdd_"; + threads.push_back(std::thread(changeDirectoryOrName, drive)); + } + for(auto& thread : threads) + thread.join(); + + // check that all logs were created + ASSERT_EQ(size+max, g_cleaner_ptr->size()); +} + + + +TEST(TestOf_IllegalLogFileName, Expecting_NoChangeToOriginalFileName) +{ + std::string original = g_logger_ptr->logFileName().get(); + std::cerr << "Below WILL print 'FiLE ERROR'. This is part of the testing and perfectly OK" << std::endl; + std::cerr << "****" << std::endl; + std::future perhaps_a_name = g_logger_ptr->changeLogFile("XY:/"); // does not exist + ASSERT_TRUE(perhaps_a_name.get().empty()); + std::cerr << "****" << std::endl; + std::string post_illegal = g_logger_ptr->logFileName().get(); + ASSERT_STREQ(original.c_str(), post_illegal.c_str()); +} + + + + +int main(int argc, char *argv[]) +{ + LogFileCleaner cleaner; + g_cleaner_ptr = &cleaner; + int return_value = 1; + + std::string last_log_file; + { + g2LogWorker logger("ReplaceLogFile", name_path_2); + testing::InitGoogleTest(&argc, argv); + g_logger_ptr = &logger; // ugly but fine for this test + g2::initializeLogging(g_logger_ptr); + cleaner.addLogToClean(g_logger_ptr->logFileName().get()); + return_value = RUN_ALL_TESTS(); + last_log_file = g_logger_ptr->logFileName().get(); + g2::shutDownLogging(); + } + std::cout << "FINISHED WITH THE TESTING" << std::endl; + // cleaning up + cleaner.addLogToClean(last_log_file); + return return_value; +} diff --git a/g2log/test_unit/test_io.cpp b/g2log/test_unit/test_io.cpp index 2fbb566..538d75d 100644 --- a/g2log/test_unit/test_io.cpp +++ b/g2log/test_unit/test_io.cpp @@ -15,383 +15,390 @@ namespace { -const int k_wait_time = 5; // 5s wait between LOG/CHECK FATAL till we say it's too long time + const int k_wait_time = 5; // 5s wait between LOG/CHECK FATAL till we say it's too long time + const std::string log_directory = "./"; -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); -} + 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); - if(!in.is_open()) - { - return ""; // error just return empty string - test will 'fault' - } - std::ostringstream oss; - oss << in.rdbuf(); - std::string content(oss.str()); - return content; -} + std::string readFileToText(std::string filename) + { + std::ifstream in; + in.open(filename.c_str(),std::ios_base::in); + if(!in.is_open()) + { + return ""; // error just return empty string - test will 'fault' + } + std::ostringstream oss; + oss << in.rdbuf(); + std::string content(oss.str()); + return content; + } } // end anonymous namespace // RAII temporarily replace of logger // and restoration of original logger at scope end struct RestoreLogger { - RestoreLogger(); - ~RestoreLogger(); + RestoreLogger(); + ~RestoreLogger(); + void reset(); - void reset(); - std::string readFileToText(); - - std::unique_ptr logger_; - const std::string log_file_; + std::unique_ptr logger_; + std::string logFile(){return log_file_;} +private: + std::string log_file_; }; RestoreLogger::RestoreLogger() - : logger_(new g2LogWorker("UNIT_TEST_LOGGER", "./")) - , log_file_(logger_->logFileName()) + : logger_(new g2LogWorker("UNIT_TEST_LOGGER", log_directory)) { - g2::initializeLogging(logger_.get()); - g2::internal::changeFatalInitHandlerForUnitTesting(); + g2::initializeLogging(logger_.get()); + g2::internal::changeFatalInitHandlerForUnitTesting(); + + std::future filename(logger_->logFileName()); + EXPECT_TRUE(filename.valid()); + log_file_ = filename.get(); } RestoreLogger::~RestoreLogger() { - reset(); - g2::shutDownLogging(); - EXPECT_EQ(0, remove(log_file_.c_str())); + reset(); + g2::shutDownLogging(); + EXPECT_EQ(0, remove(log_file_.c_str())); } void RestoreLogger::reset() { - logger_.reset(); + logger_.reset(); } - // LOG TEST(LOGTest, LOG) { - std::string file_content; - { - RestoreLogger logger; - LOG(INFO) << "test LOG(INFO)"; - logger.reset(); // force flush of logger - file_content = readFileToText(logger.log_file_); - SCOPED_TRACE("LOG_INFO"); // Scope exit be prepared for destructor failure - } - ASSERT_TRUE(verifyContent(file_content, "test LOG(INFO)")); + std::string file_content; + { + RestoreLogger logger; + LOG(INFO) << "test LOG(INFO)"; + logger.reset(); // force flush of logger + file_content = readFileToText(logger.logFile()); + SCOPED_TRACE("LOG_INFO"); // Scope exit be prepared for destructor failure + } + ASSERT_TRUE(verifyContent(file_content, "test LOG(INFO)")); } -namespace -{ -const std::string t_info = "test INFO "; -const std::string t_info2 = "test INFO 123"; -const std::string t_debug = "test DEBUG "; -const std::string t_debug2 = "test DEBUG 1.123456"; -const std::string t_warning = "test WARNING "; -const std::string t_warning2 = "test WARNING yello"; + +namespace { + const std::string t_info = "test INFO "; + const std::string t_info2 = "test INFO 123"; + const std::string t_debug = "test DEBUG "; + const std::string t_debug2 = "test DEBUG 1.123456"; + const std::string t_warning = "test WARNING "; + const std::string t_warning2 = "test WARNING yello"; } // printf-type log TEST(LogTest, LOG_F) -{ +{ + std::string file_content; + { + RestoreLogger logger; + std::cout << "logfilename: " << logger.logFile() << std::flush << std::endl; - std::string file_content; - { - RestoreLogger logger; - LOGF(INFO, std::string(t_info + "%d").c_str(), 123); - LOGF(DEBUG, std::string(t_debug + "%f").c_str(), 1.123456); - LOGF(WARNING, std::string(t_warning + "%s").c_str(), "yello"); - logger.reset(); // force flush of logger - file_content = readFileToText(logger.log_file_); - SCOPED_TRACE("LOG_INFO"); // Scope exit be prepared for destructor failure - } - ASSERT_TRUE(verifyContent(file_content, t_info2)); - ASSERT_TRUE(verifyContent(file_content, t_debug2)); - ASSERT_TRUE(verifyContent(file_content, t_warning2)); + LOGF(INFO, std::string(t_info + "%d").c_str(), 123); + LOGF(DEBUG, std::string(t_debug + "%f").c_str(), 1.123456); + LOGF(WARNING, std::string(t_warning + "%s").c_str(), "yello"); + logger.reset(); // force flush of logger + file_content = readFileToText(logger.logFile()); + SCOPED_TRACE("LOG_INFO"); // Scope exit be prepared for destructor failure + } + ASSERT_TRUE(verifyContent(file_content, t_info2)); + ASSERT_TRUE(verifyContent(file_content, t_debug2)); + ASSERT_TRUE(verifyContent(file_content, t_warning2)); } + + + // stream-type log TEST(LogTest, LOG) { - std::string file_content; - { - RestoreLogger logger; - LOG(INFO) << t_info << 123; - LOG(DEBUG) << t_debug << std::setprecision(7) << 1.123456f; - LOG(WARNING) << t_warning << "yello"; - logger.reset(); // force flush of logger - file_content = readFileToText(logger.log_file_); - SCOPED_TRACE("LOG_INFO"); // Scope exit be prepared for destructor failure - } - ASSERT_TRUE(verifyContent(file_content, t_info2)); - ASSERT_TRUE(verifyContent(file_content, t_debug2)); - ASSERT_TRUE(verifyContent(file_content, t_warning2)); + std::string file_content; + { + RestoreLogger logger; + LOG(INFO) << t_info << 123; + LOG(DEBUG) << t_debug << std::setprecision(7) << 1.123456f; + LOG(WARNING) << t_warning << "yello"; + logger.reset(); // force flush of logger + file_content = readFileToText(logger.logFile()); + SCOPED_TRACE("LOG_INFO"); // Scope exit be prepared for destructor failure + } + ASSERT_TRUE(verifyContent(file_content, t_info2)); + ASSERT_TRUE(verifyContent(file_content, t_debug2)); + ASSERT_TRUE(verifyContent(file_content, t_warning2)); } TEST(LogTest, LOG_F_IF) { - std::string file_content; - { - RestoreLogger logger; - LOGF_IF(INFO, (2 == 2), std::string(t_info + "%d").c_str(), 123); - LOGF_IF(DEBUG, (2 != 2), std::string(t_debug + "%f").c_str(), 1.123456); - logger.reset(); // force flush of logger - file_content = readFileToText(logger.log_file_); - SCOPED_TRACE("LOG_IF"); // Scope exit be prepared for destructor failure - } - ASSERT_TRUE(verifyContent(file_content, t_info2)); - ASSERT_FALSE(verifyContent(file_content, t_debug2)); + std::string file_content; + { + RestoreLogger logger; + LOGF_IF(INFO, (2 == 2), std::string(t_info + "%d").c_str(), 123); + LOGF_IF(DEBUG, (2 != 2), std::string(t_debug + "%f").c_str(), 1.123456); + logger.reset(); // force flush of logger + file_content = readFileToText(logger.logFile()); + SCOPED_TRACE("LOG_IF"); // Scope exit be prepared for destructor failure + } + ASSERT_TRUE(verifyContent(file_content, t_info2)); + ASSERT_FALSE(verifyContent(file_content, t_debug2)); } TEST(LogTest, LOG_IF) { - std::string file_content; - { - RestoreLogger logger; - LOG_IF(INFO, (2 == 2)) << t_info << 123; - LOG_IF(DEBUG, (2 != 2)) << t_debug << std::setprecision(7) << 1.123456f; - logger.reset(); // force flush of logger - file_content = readFileToText(logger.log_file_); - SCOPED_TRACE("LOG_IF"); // Scope exit be prepared for destructor failure - } - ASSERT_TRUE(verifyContent(file_content, t_info2)); - ASSERT_FALSE(verifyContent(file_content, t_debug2)); + std::string file_content; + { + RestoreLogger logger; + LOG_IF(INFO, (2 == 2)) << t_info << 123; + LOG_IF(DEBUG, (2 != 2)) << t_debug << std::setprecision(7) << 1.123456f; + logger.reset(); // force flush of logger + file_content = readFileToText(logger.logFile()); + SCOPED_TRACE("LOG_IF"); // Scope exit be prepared for destructor failure + } + ASSERT_TRUE(verifyContent(file_content, t_info2)); + ASSERT_FALSE(verifyContent(file_content, t_debug2)); } TEST(LogTest, LOGF__FATAL) { - RestoreLogger logger; - try - { - LOGF(FATAL, "This message should throw %d",0); - } - catch (std::exception const &e) - { - logger.reset(); - std::string file_content = readFileToText(logger.log_file_); - std::cerr << file_content << std::endl << std::flush; - if(verifyContent(e.what(), "EXIT trigger caused by ") && - verifyContent(file_content, "FATAL") && - verifyContent(file_content, "This message should throw")) - { - SUCCEED(); - return; - } - else - { - ADD_FAILURE() << "Didn't throw exception as expected"; - } - } - ADD_FAILURE() << "Didn't throw exception at ALL"; + RestoreLogger logger; + try + { + LOGF(FATAL, "This message should throw %d",0); + } + catch (std::exception const &e) + { + logger.reset(); + std::string file_content = readFileToText(logger.logFile()); + std::cerr << file_content << std::endl << std::flush; + if(verifyContent(e.what(), "EXIT trigger caused by ") && + verifyContent(file_content, "FATAL") && + verifyContent(file_content, "This message should throw")) + { + SUCCEED(); + return; + } + else + { + ADD_FAILURE() << "Didn't throw exception as expected"; + } + } + ADD_FAILURE() << "Didn't throw exception at ALL"; } TEST(LogTest, LOG_FATAL) { - RestoreLogger logger; - try - { - LOG(FATAL) << "This message should throw"; - } - catch (std::exception const &e) - { - logger.reset(); - std::string file_content = readFileToText(logger.log_file_); - if(verifyContent(e.what(), "EXIT trigger caused by ") && - verifyContent(file_content, "FATAL") && - verifyContent(file_content, "This message should throw")) - { - SUCCEED(); - return; - } - else - { - ADD_FAILURE() << "Didn't throw exception as expected"; - } - } - ADD_FAILURE() << "Didn't throw exception at ALL"; + RestoreLogger logger; + try + { + LOG(FATAL) << "This message should throw"; + } + catch (std::exception const &e) + { + logger.reset(); + std::string file_content = readFileToText(logger.logFile()); + if(verifyContent(e.what(), "EXIT trigger caused by ") && + verifyContent(file_content, "FATAL") && + verifyContent(file_content, "This message should throw")) + { + SUCCEED(); + return; + } + else + { + ADD_FAILURE() << "Didn't throw exception as expected"; + } + } + ADD_FAILURE() << "Didn't throw exception at ALL"; } TEST(LogTest, LOGF_IF__FATAL) { - RestoreLogger logger; - try - { - LOGF_IF(FATAL, (2<3), "This message%sshould throw"," "); - } - catch (std::exception const &e) - { - logger.reset(); - std::string file_content = readFileToText(logger.log_file_); - if(verifyContent(e.what(), "EXIT trigger caused by ") && - verifyContent(file_content, "FATAL") && - verifyContent(file_content, "This message should throw")) - { - SUCCEED(); - return; - } - else - { - ADD_FAILURE() << "Didn't throw exception as expected"; - } - } - ADD_FAILURE() << "Didn't throw exception at ALL"; + RestoreLogger logger; + try + { + LOGF_IF(FATAL, (2<3), "This message%sshould throw"," "); + } + catch (std::exception const &e) + { + logger.reset(); + std::string file_content = readFileToText(logger.logFile()); + if(verifyContent(e.what(), "EXIT trigger caused by ") && + verifyContent(file_content, "FATAL") && + verifyContent(file_content, "This message should throw")) + { + SUCCEED(); + return; + } + else + { + ADD_FAILURE() << "Didn't throw exception as expected"; + } + } + ADD_FAILURE() << "Didn't throw exception at ALL"; } TEST(LogTest, LOG_IF__FATAL) { - RestoreLogger logger; - try - { - LOG_IF(WARNING, (0 != t_info.compare(t_info))) << "This message should NOT be written"; - LOG_IF(FATAL, (0 != t_info.compare(t_info2))) << "This message should throw"; - } - catch (std::exception const &e) - { - logger.reset(); - std::string file_content = readFileToText(logger.log_file_); - if(verifyContent(e.what(), "EXIT trigger caused by ") && - verifyContent(file_content, "FATAL") && - verifyContent(file_content, "This message should throw") && - (false == verifyContent(file_content, "This message should NOT be written"))) - { - SUCCEED(); - return; - } - else - { - ADD_FAILURE() << "Didn't throw exception as expected"; - } - } - ADD_FAILURE() << "Didn't throw exception at ALL"; + RestoreLogger logger; + try + { + LOG_IF(WARNING, (0 != t_info.compare(t_info))) << "This message should NOT be written"; + LOG_IF(FATAL, (0 != t_info.compare(t_info2))) << "This message should throw"; + } + catch (std::exception const &e) + { + logger.reset(); + std::string file_content = readFileToText(logger.logFile()); + if(verifyContent(e.what(), "EXIT trigger caused by ") && + verifyContent(file_content, "FATAL") && + verifyContent(file_content, "This message should throw") && + (false == verifyContent(file_content, "This message should NOT be written"))) + { + SUCCEED(); + return; + } + else + { + ADD_FAILURE() << "Didn't throw exception as expected"; + } + } + ADD_FAILURE() << "Didn't throw exception at ALL"; } TEST(LogTest, LOG_IF__FATAL__NO_THROW) { - RestoreLogger logger; - try - { - LOG_IF(FATAL, (2>3)) << "This message%sshould NOT throw"; - } - catch (std::exception const &e) - { - std::cerr << e.what() << std::endl; - logger.reset(); - ADD_FAILURE() << "Didn't throw exception as expected"; - } - logger.reset(); - SUCCEED(); + RestoreLogger logger; + try + { + LOG_IF(FATAL, (2>3)) << "This message%sshould NOT throw"; + } + catch (std::exception const &e) + { + std::cerr << e.what() << std::endl; + logger.reset(); + ADD_FAILURE() << "Didn't throw exception as expected"; + } + logger.reset(); + SUCCEED(); } // CHECK_F TEST(CheckTest, CHECK_F__thisWILL_PrintErrorMsg) { - RestoreLogger logger; - try - { - CHECK(1 == 2); - } - catch (std::exception const &e) - { - logger.reset(); - std::string file_content = readFileToText(logger.log_file_); - if(verifyContent(e.what(), "EXIT trigger caused by ") && - verifyContent(file_content, "FATAL")) - { - SUCCEED(); - return; - } - } - ADD_FAILURE() << "Didn't throw exception as expected"; + RestoreLogger logger; + try + { + CHECK(1 == 2); + } + catch (std::exception const &e) + { + logger.reset(); + std::string file_content = readFileToText(logger.logFile()); + if(verifyContent(e.what(), "EXIT trigger caused by ") && + verifyContent(file_content, "FATAL")) + { + SUCCEED(); + return; + } + } + ADD_FAILURE() << "Didn't throw exception as expected"; } TEST(CHECK_F_Test, CHECK_F__thisWILL_PrintErrorMsg) { - RestoreLogger logger; - std::string msg = "This message is added to throw %s and %s"; - std::string msg2 = "This message is added to throw message and log"; - std::string arg1 = "message"; - std::string arg2 = "log"; - try - { - CHECK_F(1 >= 2, msg.c_str(), arg1.c_str(), arg2.c_str()); - } - catch (std::exception const &e) - { - logger.reset(); - std::string file_content = readFileToText(logger.log_file_); - if(verifyContent(e.what(), "EXIT trigger caused by ") && - verifyContent(file_content, "FATAL") && - verifyContent(file_content, msg2)) - { - SUCCEED(); - return; - } - } - ADD_FAILURE() << "Didn't throw exception as expected"; + RestoreLogger logger; + std::string msg = "This message is added to throw %s and %s"; + std::string msg2 = "This message is added to throw message and log"; + std::string arg1 = "message"; + std::string arg2 = "log"; + try + { + CHECK_F(1 >= 2, msg.c_str(), arg1.c_str(), arg2.c_str()); + } + catch (std::exception const &e) + { + logger.reset(); + std::string file_content = readFileToText(logger.logFile()); + if(verifyContent(e.what(), "EXIT trigger caused by ") && + verifyContent(file_content, "FATAL") && + verifyContent(file_content, msg2)) + { + SUCCEED(); + return; + } + } + ADD_FAILURE() << "Didn't throw exception as expected"; } TEST(CHECK_Test, CHECK__thisWILL_PrintErrorMsg) { - RestoreLogger logger; - std::string msg = "This message is added to throw %s and %s"; - std::string msg2 = "This message is added to throw message and log"; - std::string arg1 = "message"; - std::string arg2 = "log"; - try - { - CHECK(1 >= 2) << msg2; - } - catch (std::exception const &e) - { - logger.reset(); - std::string file_content = readFileToText(logger.log_file_); - if(verifyContent(e.what(), "EXIT trigger caused by ") && - verifyContent(file_content, "FATAL") && - verifyContent(file_content, msg2)) - { - SUCCEED(); - return; - } - } - ADD_FAILURE() << "Didn't throw exception as expected"; + RestoreLogger logger; + std::string msg = "This message is added to throw %s and %s"; + std::string msg2 = "This message is added to throw message and log"; + std::string arg1 = "message"; + std::string arg2 = "log"; + try + { + CHECK(1 >= 2) << msg2; + } + catch (std::exception const &e) + { + logger.reset(); + std::string file_content = readFileToText(logger.logFile()); + if(verifyContent(e.what(), "EXIT trigger caused by ") && + verifyContent(file_content, "FATAL") && + verifyContent(file_content, msg2)) + { + SUCCEED(); + return; + } + } + ADD_FAILURE() << "Didn't throw exception as expected"; } TEST(CHECK, CHECK_ThatWontThrow) { - RestoreLogger logger; - std::string msg = "This %s should never appear in the %s"; - std::string msg2 = "This message should never appear in the log"; - std::string arg1 = "message"; - std::string arg2 = "log"; - try - { - CHECK(1 == 1); - CHECK_F(1==1, msg.c_str(), "message", "log"); - } - catch (std::exception const &e) - { - std::cerr << e.what() << std::endl; - ADD_FAILURE() << "Should never have thrown"; - } + RestoreLogger logger; + std::string msg = "This %s should never appear in the %s"; + std::string msg2 = "This message should never appear in the log"; + std::string arg1 = "message"; + std::string arg2 = "log"; + try + { + CHECK(1 == 1); + CHECK_F(1==1, msg.c_str(), "message", "log"); + } + catch (std::exception const &e) + { + std::cerr << e.what() << std::endl; + ADD_FAILURE() << "Should never have thrown"; + } - std::string file_content = readFileToText(logger.log_file_); - ASSERT_FALSE(verifyContent(file_content, msg2)); + std::string file_content = readFileToText(logger.logFile()); + ASSERT_FALSE(verifyContent(file_content, msg2)); }