Merged in changes from development repository.

* threadsafe use of localtime
* changing/retrieving log filename at runtime (using futures)
This commit is contained in:
KjellKod 2012-10-14 01:54:56 +02:00
parent d0042cf730
commit a4c49a5549
16 changed files with 1255 additions and 565 deletions

30
README
View File

@ -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 HOW TO BUILD
=================== ===================
This g2log is a snapshot from KjellKod repository. 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. justthread C++11 thread library is no longer needed.
On Windows it is enough to use Visual Studio 11 (2012) On Windows it is enough to use Visual Studio 11 (2012)
On Linux it is enough to use gcc4.7. On Linux it is enough to use gcc4.7.2. The CMakeFile.txt
The CMakeFile.txt is updated to reflect that, the justthread parts is is updated to reflect that, the justthread parts is
commented away in case someone still needs that. 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 If you want to integrate g2log in your own software you need
the following files the files under g2log/src
g2log/src/
g2log.cpp/h
g2logworker.cpp/h
crashhandler.h (crashhandler_win.cpp or crashhandler_unix.cpp)
shared_queue.h
active.h/cpp
BUILDING g2log: 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 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 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. I can improve g2log and it is also nice to see if someone is using it.

View File

@ -1,10 +1,10 @@
# CMakeLists.txt cmake configuration for g2log test # CMakeLists.txt cmake configuration for g2log test
# g2log is a KjellKod Logger # 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 # 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 # This code is yours to share, use and modify with no strings attached
# and no restrictions or obligations. # and no restrictions or obligations.
# =================================================================== # ===================================================================
@ -19,7 +19,8 @@
# WINDOWS == README: Example how to setup environment + running an example # 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 # 2. from the g2log folder
# mkdir build # mkdir build
# cd build; # cd build;
@ -51,6 +52,15 @@ MESSAGE(" LOG_SRC = : ${LOG_SRC}")
include_directories(${LOG_SRC}) include_directories(${LOG_SRC})
SET(ACTIVE_CPP0xx_DIR "Release") 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) IF(UNIX)
@ -74,32 +84,32 @@ IF(UNIX)
# include_directories("/usr/include/justthread") # include_directories("/usr/include/justthread")
ENDIF(UNIX) ENDIF(UNIX)
if (MSVC) if (MSVC)
# VC11 bug: http://code.google.com/p/googletest/issues/detail?id=408 # VC11 bug: http://code.google.com/p/googletest/issues/detail?id=408
# add_definition(-D_VARIADIC_MAX=10) # 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) ADD_DEFINITIONS (/D_VARIADIC_MAX=10)
MESSAGE(STATUS "- MSVC: Set variadic max to 10 for MSVC compatibility") 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" # 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") # "set_target_properties(unit_test PROPERTIES COMPILE_DEFINITIONS "GTEST_USE_OWN_TR1_TUPLE=0")
endif () endif ()
#Visual Studio 2010 -- must use justthread
IF(MSVC10) #Visual Studio 2010 -- must use justthread. For now hardcoded for x64
IF(MSVC10)
MESSAGE("") MESSAGE("")
MESSAGE("Windows: Please run the command [cmake -DCMAKE_BUILD_TYPE=Release -G \"Visual Studio 10\" ..]") 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("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("then run 'Release\\g2log-FATAL-example.exe' or whatever performance test you feel like trying")
MESSAGE("") MESSAGE("")
set(PLATFORM_LINK_LIBRIES $ENV{PROGRAMFILES}/JustSoftwareSolutions/JustThread/lib/justthread_vc10_mdd.lib) 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) set(SRC_PLATFORM_SPECIFIC ${LOG_SRC}/crashhandler_win.cpp)
include_directories("$ENV{PROGRAMFILES}/JustSoftwareSolutions/JustThread/include") include_directories("$ENV{PROGRAMFILES}/JustSoftwareSolutions/JustThread/include")
ENDIF(MSVC10) ENDIF(MSVC10)
# Visual Studio 2011 -- std::thread etc are included with the Visual Studio package, so justthread dependencies are removed # Visual Studio 2011 -- std::thread etc are included with the Visual Studio package, so justthread dependencies are removed
IF(MSVC11) IF(MSVC11)
MESSAGE("") MESSAGE("")
MESSAGE("Windows: Please run the command [cmake -DCMAKE_BUILD_TYPE=Release -G \"Visual Studio 11\" ..]") 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'") MESSAGE("if cmake finishes OK, do 'msbuild g2log_by_kjellkod.sln /p:Configuration=Release'")
@ -110,17 +120,19 @@ ENDIF(MSVC11)
# GENERIC STEPS # GENERIC STEPS
# CODE SOURCES these + # CODE SOURCES these +
set(SRC_CPP ${LOG_SRC}/g2logworker.cpp ${LOG_SRC}/g2log.cpp) 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) 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}) set(SRC_FILES ${SRC_CPP} ${SRC_H} ${SRC_PLATFORM_SPECIFIC})
# add a ActiveObject library # add a ActiveObject library
add_library(lib_activeobject ${LOG_SRC}/active.cpp ${LOG_SRC}/active.h ${LOG_SRC}/shared_queue.h) 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) set_target_properties(lib_activeobject PROPERTIES LINKER_LANGUAGE CXX)
# add a g2log library # add a g2log library
include_directories(src) include_directories(src)
include_directories(${LOG_SRC}) include_directories(${LOG_SRC})
@ -135,7 +147,7 @@ ENDIF(MSVC11)
# ============================================================================ # ============================================================================
# OPTIONS: Turn OFF the ones that is of no interest to you # OPTIONS: Turn OFF the ones that is of no interest to you
# ---- by default all is OFF: except 'g2log-FATAL-example ----- # ---- 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- # ----- 1) the performance tests were only thoroughly tested on Ubuntu, not windows-
# (g2log windows/linux, but Google's glog only on linux) # (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 # before it can be "cmake'd" and compiled --- leaving it as OFF for now
# ============================================================================ # ============================================================================
# 1. a simple test example 'g2log-FATAL-example' # 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) "Create simple binaries that runs a few LOG calls" ON)
@ -173,16 +185,16 @@ ENDIF(MSVC11)
include_directories (${DIR_EXAMPLE}) include_directories (${DIR_EXAMPLE})
add_executable(g2log-FATAL-example ${DIR_EXAMPLE}/main.cpp) add_executable(g2log-FATAL-example ${DIR_EXAMPLE}/main.cpp)
target_link_libraries(g2log-FATAL-example lib_activeobject lib_g2logger ${PLATFORM_LINK_LIBRIES}) 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 # 2. create the g2log's performance tests
if (USE_G2LOG_PERFORMANCE) if (USE_G2LOG_PERFORMANCE)
MESSAGE(" g2log performance tests option ON") MESSAGE(" g2log performance tests option ON")
include_directories (${DIR_PERFORMANCE}) include_directories (${DIR_PERFORMANCE})
# MEAN PERFORMANCE TEST # 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) ${DIR_PERFORMANCE}/main_threaded_mean.cpp ${DIR_PERFORMANCE}/performance.h)
# Turn on G2LOG performance flag # Turn on G2LOG performance flag
set_target_properties(g2log-performance-threaded_mean PROPERTIES COMPILE_DEFINITIONS "G2LOG_PERFORMANCE=1") 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 # 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") MESSAGE(" Google's glog performance tests option ON")
include_directories (${DIR_PERFORMANCE}) include_directories (${DIR_PERFORMANCE})
#Linux is easy! #Linux is easy!
if(UNIX) if(UNIX)
set(GLOG_LIB glog) 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) 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") 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}) target_link_libraries(google_glog-performance-threaded_mean lib_activeobject ${GLOG_LIB} ${PLATFORM_LINK_LIBRIES})
# create the the GOOGLE MEAN_PERFORMANCE executable # create the the GOOGLE MEAN_PERFORMANCE executable
add_executable(google_glog-performance-threaded_worst ${DIR_PERFORMANCE}/main_threaded_worst.cpp ${DIR_PERFORMANCE}/performance.h) 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") 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}) target_link_libraries(google_glog-performance-threaded_worst lib_activeobject ${GLOG_LIB} ${PLATFORM_LINK_LIBRIES})
endif(UNIX) endif(UNIX)
# GLOG on Linux is easy - but for Windows trickier,. and it doesn't work (as of yet) # GLOG on Linux is easy - but for Windows trickier,. and it doesn't work (as of yet)
if(WIN32) if(WIN32)
MESSAGE("******************************************************") MESSAGE("******************************************************")
MESSAGE("*** SORRY- Google glog on windows is not preconfigured") MESSAGE("*** SORRY- Google glog on windows is not preconfigured")
MESSAGE("*** You have to do this yourself: ref CMakeLists.txt") MESSAGE("*** You have to do this yourself: ref CMakeLists.txt")
MESSAGE("******************************************************") MESSAGE("******************************************************")
MESSAGE("") MESSAGE("")
#set(GLOG_DIR ../3rdParty/glog/glog-0.3.1) #set(GLOG_DIR ../3rdParty/glog/glog-0.3.1)
#include_directories(${GLOG_DIR}/src/windows) #include_directories(${GLOG_DIR}/src/windows)
endif(WIN32) endif(WIN32)
endif (USE_GOOGLE_GLOG_PERFORMANCE) 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 # 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") MESSAGE(" g2log unit testing option ON")
# SETUP for GTEST # SETUP for GTEST
set(GTEST_DIR ../3rdParty/gtest/gtest-1.6.0__stripped) 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) add_library(gtest_160_lib ${GTEST_DIR}/src/gtest-all.cc ${GTEST_DIR}/src/gtest_main.cc)
enable_testing(true) enable_testing(true)
add_executable(g2log-unit_test ../test_main/test_main.cpp ${DIR_UNIT_TEST}/test_io.cpp) add_executable(g2log-unit_test ../test_main/test_main.cpp ${DIR_UNIT_TEST}/test_io.cpp ${DIR_UNIT_TEST}/test_configuration.cpp)
set_target_properties(g2log-unit_test PROPERTIES COMPILE_DEFINITIONS "_VARIADIC_MAX=10") # 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") 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}) 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)

View File

@ -4,17 +4,17 @@
* strings attached and no restrictions or obligations. * 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. * safe for thread communication.
* *
* This was originally published at http://sites.google.com/site/kjellhedstrom2/active-object-with-cpp0x * 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 * 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 * The code below uses JustSoftware Solutions Inc std::thread implementation
* http://www.justsoftwaresolutions.co.uk * 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 * e-mail: hedstrom at kjellkod dot cc
* linkedin: http://linkedin.com/se/kjellkod */ * linkedin: http://linkedin.com/se/kjellkod */

View File

@ -4,17 +4,17 @@
* strings attached and no restrictions or obligations. * 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. * safe for thread communication.
* *
* This was originally published at http://sites.google.com/site/kjellhedstrom2/active-object-with-cpp0x * 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 * 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 * The code below uses JustSoftware Solutions Inc std::thread implementation
* http://www.justsoftwaresolutions.co.uk * 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 * e-mail: hedstrom at kjellkod dot cc
* linkedin: http://linkedin.com/se/kjellkod */ * linkedin: http://linkedin.com/se/kjellkod */
@ -36,15 +36,15 @@ class Active {
private: private:
Active(const Active&); // c++11 feature not yet in vs2010 = delete; Active(const Active&); // c++11 feature not yet in vs2010 = delete;
Active& operator=(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 doDone(){done_ = true;}
void run(); void run();
shared_queue<Callback> mq_; shared_queue<Callback> mq_;
std::thread thd_; std::thread thd_;
bool done_; // finished flag to be set through msg queue by ~Active bool done_; // finished flag to be set through msg queue by ~Active
public: public:
virtual ~Active(); virtual ~Active();
void send(Callback msg_); void send(Callback msg_);

70
g2log/src/g2future.h Normal file
View File

@ -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 <future>
#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<typename Moveable>
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<Active> bgWorker{Active::createActive()};
// ...
// auto msg_call=[=](){return ("Hello from the Background");};
// auto future_msg = g2::spawn_task(msg_lambda, bgWorker.get());
template <typename Func>
std::future<typename std::result_of<Func()>::type> spawn_task(Func func, kjellkod::Active* worker)
{
typedef typename std::result_of<Func()>::type result_type;
typedef std::packaged_task<result_type()> task_type;
task_type task(std::move(func));
std::future<result_type> result = task.get_future();
worker->send(PretendToBeCopyable<task_type>(std::move(task)));
return std::move(result);
}
} // end namespace g2
#endif // G2FUTURE_H

View File

@ -7,7 +7,7 @@
* Filename:g2log.cpp Framework for Logging and Design By Contract * Filename:g2log.cpp Framework for Logging and Design By Contract
* Created: 2011 by Kjell Hedström * 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 * from the following sources
* 1. kjellkod.cc ;) * 1. kjellkod.cc ;)
* 2. Dr.Dobbs, Petru Marginean: http://drdobbs.com/article/printableArticle.jhtml?articleId=201804215&dept_url=/cpp/ * 2. Dr.Dobbs, Petru Marginean: http://drdobbs.com/article/printableArticle.jhtml?articleId=201804215&dept_url=/cpp/

View File

@ -8,7 +8,7 @@
* Created: 2011 by Kjell Hedström * 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 influenced
* from the following sources * at least in "spirit" from the following sources
* 1. kjellkod.cc ;) * 1. kjellkod.cc ;)
* 2. Dr.Dobbs, Petru Marginean: http://drdobbs.com/article/printableArticle.jhtml?articleId=201804215&dept_url=/cpp/ * 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/ * 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; typedef const std::string& LogEntry;
/** By default the g2log will call g2LogWorker::fatal(...) which will abort() the system after flushing /** 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' * 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 */ * which can be done here */
void changeFatalInitHandlerForUnitTesting(); 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 A thread that causes a FatalMessage will sleep forever until the
application has exited (after message flush) */ application has exited (after message flush) */
struct FatalMessage struct FatalMessage
@ -195,22 +195,22 @@ struct FatalTrigger
class LogMessage class LogMessage
{ {
public: 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 virtual ~LogMessage(); // at destruction will flush the message
std::ostringstream& messageStream(){return stream_;} std::ostringstream& messageStream(){return stream_;}
// The __attribute__ generates compiler warnings if illegal "printf" format // The __attribute__ generates compiler warnings if illegal "printf" format
// IMPORTANT: You muse enable the compiler flag '-Wall' for this to work! // IMPORTANT: You muse enable the compiler flag '-Wall' for this to work!
// ref: http://www.unixwiz.net/techtips/gnu-c-attributes.html // ref: http://www.unixwiz.net/techtips/gnu-c-attributes.html
// //
//If the compiler does not support attributes, disable them //If the compiler does not support attributes, disable them
#ifndef __GNUC__ #ifndef __GNUC__
#define __attribute__(x) #define __attribute__(x)
#endif #endif
// Coder note: Since it's C++ and not C EVERY CLASS FUNCTION always get a first // 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' // 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, ...) void messageSave(const char *printf_like_message, ...)
__attribute__((format(printf,2,3) )); __attribute__((format(printf,2,3) ));

View File

@ -1,65 +1,45 @@
/** ========================================================================== /** ==========================================================================
* 2011 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes * 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 * with no warranties. This code is yours to share, use and modify with no
* strings attached and no restrictions or obligations. * strings attached and no restrictions or obligations.
* ============================================================================ * ============================================================================
* Filename:g2LogWorker.cpp Framework for Logging and Design By Contract * Filename:g2LogWorker.cpp Framework for Logging and Design By Contract
* Created: 2011 by Kjell Hedström * Created: 2011 by Kjell Hedström
* *
* PUBLIC DOMAIN and Not copywrited. First published at KjellKod.cc * PUBLIC DOMAIN and Not under copywrite protection. First published at KjellKod.cc
* ********************************************* */ * ********************************************* */
#include "g2logworker.h" #include "g2logworker.h"
#include <iostream>
#include <functional>
#include <algorithm>
#include <string>
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <iostream>
#include <cassert> #include <cassert>
#include <iomanip> #include <algorithm>
#include <ctime> #include <string>
#include <chrono>
#include <future>
#include <functional>
#include "active.h" #include "active.h"
#include "g2log.h" #include "g2log.h"
#include "crashhandler.h" #include "crashhandler.h"
#include "g2time.h"
#include "g2future.h"
using namespace g2;
using namespace g2::internal; using namespace g2::internal;
namespace namespace
{ {
typedef std::chrono::steady_clock::time_point time_point; static const std::string date_formatted = "%Y/%m/%d";
typedef std::chrono::duration<long,std::ratio<1, 1000> > millisecond; static const std::string time_formatted = "%H:%M:%S";
typedef std::chrono::duration<long long,std::ratio<1, 1000000> > microsecond; static const std::string file_name_time_formatted = "%Y%m%d-%H%M%S";
struct LogTime
{
LogTime()
{
time_t current_time = time(nullptr);
ctime(&current_time); // fill with time right now
struct tm* ptm = ::localtime(&current_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
};
// check for filename validity - filename should not be part of PATH // check for filename validity - filename should not be part of PATH
bool isValidFilename(const std::string prefix_filename) bool isValidFilename(const std::string prefix_filename)
{ {
std::string illegal_characters("/,|<>:#$%{}()[]\'\"^!?+* "); std::string illegal_characters("/,|<>:#$%{}()[]\'\"^!?+* ");
size_t pos = prefix_filename.find_first_of(illegal_characters,0); size_t pos = prefix_filename.find_first_of(illegal_characters,0);
if(pos != std::string::npos) 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; std::cerr << "Empty filename prefix is not allowed" << std::endl;
return false; return false;
} }
return true; 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<std::ofstream> createLogFile(const std::string& file_with_full_path)
{
std::unique_ptr<std::ofstream> 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<file> signals error in creating the log file
}
return out;
}
} // end anonymous namespace } // end anonymous namespace
/** The Real McCoy Background worker, while g2LogWorker gives the
* asynchronous API to put job in the background the g2LogWorkerImpl
/** The actual background worker, while g2LogWorker gives the * does the actual background thread work */
* asynchronous API to put job in the background the g2LogWorkerImpl
* does the actual background thread work */
struct g2LogWorkerImpl struct g2LogWorkerImpl
{ {
g2LogWorkerImpl(const std::string& log_prefix, const std::string& log_directory); g2LogWorkerImpl(const std::string& log_prefix, const std::string& log_directory);
@ -89,15 +131,19 @@ struct g2LogWorkerImpl
void backgroundFileWrite(g2::internal::LogEntry message); void backgroundFileWrite(g2::internal::LogEntry message);
void backgroundExitFatal(g2::internal::FatalMessage fatal_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_file_with_path_;
std::string log_prefix_backup_; // needed in case of future log file changes of directory
std::unique_ptr<kjellkod::Active> bg_; std::unique_ptr<kjellkod::Active> bg_;
std::ofstream out; std::unique_ptr<std::ofstream> outptr_;
time_point start_time_; steady_time_point steady_start_time_;
private: private:
g2LogWorkerImpl& operator=(const g2LogWorkerImpl&); // c++11 feature not yet in vs2010 = delete; g2LogWorkerImpl& operator=(const g2LogWorkerImpl&); // c++11 feature not yet in vs2010 = delete;
g2LogWorkerImpl(const g2LogWorkerImpl& other); // 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 // Private API implementation : g2LogWorkerImpl
g2LogWorkerImpl::g2LogWorkerImpl(const std::string& log_prefix, const std::string& log_directory) g2LogWorkerImpl::g2LogWorkerImpl(const std::string& log_prefix, const std::string& log_directory)
: log_file_with_path_(log_directory) : log_file_with_path_(log_directory)
, log_prefix_backup_(log_prefix)
, bg_(kjellkod::Active::createActive()) , 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; log_prefix_backup_ = prefixSanityFix(log_prefix);
// if through a debugger the debugger CAN just throw in the whole path if(!isValidFilename(log_prefix_backup_))
// 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))
{ {
// illegal prefix, refuse to start // illegal prefix, refuse to start
std::cerr << "g2log: forced abort due to illegal log prefix [" << log_prefix <<"]" << std::endl << std::flush; std::cerr << "g2log: forced abort due to illegal log prefix [" << log_prefix <<"]" << std::endl << std::flush;
abort(); abort();
} }
using namespace std; std::string file_name = createLogFileName(log_prefix_backup_);
LogTime t; log_file_with_path_ = log_directory + file_name;
ostringstream oss_name; outptr_ = createLogFile(log_file_with_path_);
oss_name.fill('0'); assert((nullptr != outptr_) && "cannot open log file at startup");
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');
} }
g2LogWorkerImpl::~g2LogWorkerImpl() g2LogWorkerImpl::~g2LogWorkerImpl()
{ {
std::ostringstream ss_exit; std::ostringstream ss_exit;
time_t exit_time;
time(&exit_time);
bg_.reset(); // flush the log queue bg_.reset(); // flush the log queue
ss_exit << "\n\t\tg2log file shutdown at: " << ctime(&exit_time); ss_exit << "\n\t\tg2log file shutdown at: " << g2::localtime_formatted(g2::systemtime_now(), time_formatted);
out << ss_exit.str() << std::flush; filestream() << ss_exit.str() << std::flush;
} }
void g2LogWorkerImpl::backgroundFileWrite(LogEntry message) void g2LogWorkerImpl::backgroundFileWrite(LogEntry message)
{ {
using namespace std; using namespace std;
LogTime t; std::ofstream& out(filestream());
auto timesnapshot = chrono::steady_clock::now(); auto system_time = g2::systemtime_now();
out << "\n" << t.year << "/" << setw(2) << t.month << "/" << setw(2) << t.day; auto steady_time = std::chrono::steady_clock::now();
out << " " << setw(2) << t.hour << ":"<< setw(2) << t.minute <<":"<< setw(2) << t.second; out << "\n" << g2::localtime_formatted(system_time, date_formatted);
out << "." << chrono::duration_cast<microsecond>(timesnapshot - start_time_).count(); //microseconds out << " " << g2::localtime_formatted(system_time, time_formatted); // TODO: time kommer från LogEntry
out << "." << chrono::duration_cast<std::chrono::microseconds>(steady_time - steady_start_time_).count(); //microseconds TODO: ta in min g2clocka här StopWatch
out << "\t" << message << std::flush; out << "\t" << message << std::flush;
} }
void g2LogWorkerImpl::backgroundExitFatal(FatalMessage fatal_message) void g2LogWorkerImpl::backgroundExitFatal(FatalMessage fatal_message)
{ {
backgroundFileWrite(fatal_message.message_); backgroundFileWrite(fatal_message.message_);
backgroundFileWrite("Log flushed successfully to disk: exiting"); backgroundFileWrite("Log flushed successfully to disk \nExiting");
std::cout << "g2log exiting successfully after receiving fatal event" << std::endl; std::cerr << "g2log exiting after receiving fatal event" << std::endl;
std::cout << "Log file at: [" << log_file_with_path_ << "]\n" << std::endl << std::flush; std::cerr << "Log file at: [" << log_file_with_path_ << "]\n" << std::endl << std::flush;
out.close(); filestream().close();
exitWithDefaultSignalHandler(fatal_message.signal_id_); exitWithDefaultSignalHandler(fatal_message.signal_id_);
perror("g2log exited after receiving FATAL trigger. Flush message status: "); // should never reach this point 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<std::ofstream> 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 // Public API implementation
g2LogWorker::g2LogWorker(const std::string& log_prefix, const std::string& log_directory) //
: pimpl_(new g2LogWorkerImpl(log_prefix, log_directory)) g2LogWorker::g2LogWorker(const std::string& log_prefix, const std::string& log_directory)
, log_file_with_path_(pimpl_->log_file_with_path_) : pimpl_(new g2LogWorkerImpl(log_prefix, log_directory))
{ , log_file_with_path_(pimpl_->log_file_with_path_)
} {
assert((pimpl_ != nullptr) && "shouild never happen");
}
g2LogWorker::~g2LogWorker() g2LogWorker::~g2LogWorker()
{ {
pimpl_.reset(); pimpl_.reset();
std::cout << "\nExiting, log location: " << log_file_with_path_ << std::endl << std::flush; std::cerr << "\nExiting, log location: " << log_file_with_path_ << std::endl << std::flush;
} }
void g2LogWorker::save(g2::internal::LogEntry msg) void g2LogWorker::save(g2::internal::LogEntry msg)
{ {
pimpl_->bg_->send(std::bind(&g2LogWorkerImpl::backgroundFileWrite, pimpl_.get(), msg)); pimpl_->bg_->send(std::bind(&g2LogWorkerImpl::backgroundFileWrite, pimpl_.get(), msg));
} }
void g2LogWorker::fatal(g2::internal::FatalMessage fatal_message) void g2LogWorker::fatal(g2::internal::FatalMessage fatal_message)
{ {
pimpl_->bg_->send(std::bind(&g2LogWorkerImpl::backgroundExitFatal, pimpl_.get(), 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<std::string> 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<std::string> 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);
}

View File

@ -1,75 +1,57 @@
#ifndef G2_LOG_WORKER_H_ #ifndef G2_LOG_WORKER_H_
#define 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 * 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 * with no warranties. This code is yours to share, use and modify with no
* strings attached and no restrictions or obligations. * strings attached and no restrictions or obligations.
* ============================================================================ * ============================================================================
* Filename:g2logworker.h Framework for Logging and Design By Contract * Filename:g2logworker.h Framework for Logging and Design By Contract
* Created: 2011 by Kjell Hedström * Created: 2011 by Kjell Hedström
* *
* PUBLIC DOMAIN and Not copywrited. First published at KjellKod.cc * PUBLIC DOMAIN and Not copywrited. First published at KjellKod.cc
* ********************************************* */ * ********************************************* */
#include <memory>
#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<g2LogWorkerImpl> 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 * ) }; };
template< bool available > struct safest_localtime { #include <memory>
static std::tm *call( std::time_t const *t, std::tm *r ) #include <future>
{ return localtime_r( t, r ); } #include <string>
};
template<> struct safest_localtime< false > { #include "g2log.h"
static std::tm *call( std::time_t const *t, std::tm *r )
{ return std::localtime( t ); } struct g2LogWorkerImpl;
};
} /**
std::tm *localtime( std::time_t const *t, std::tm *r ) * \param log_prefix is the 'name' of the binary, this give the log name 'LOG-'name'-...
{ return query::safest_localtime< query::has_localtime_r::value >().call( t, r ); } * \param log_directory gives the directory to put the log files */
*/ class g2LogWorker
{
#endif // LOG_WORKER_H_ 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<std::string> 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<std::string> logFileName();
private:
std::unique_ptr<g2LogWorkerImpl> 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_

78
g2log/src/g2time.cpp Normal file
View File

@ -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 <sstream>
#include <string>
#include <chrono>
#include <thread>
#include <ctime>
#include <iomanip>
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<struct tm*>(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

45
g2log/src/g2time.h Normal file
View File

@ -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 <ctime>
#include <string>
#include <chrono>
// 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<std::chrono::system_clock> 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

View File

@ -9,7 +9,7 @@
* the help from the std::thread library from JustSoftwareSolutions * the help from the std::thread library from JustSoftwareSolutions
* ref: http://www.stdthread.co.uk/doc/headers/mutex.html * 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 */ * Ref: "C++ Concurrency In Action" http://www.manning.com/williams */
#ifndef SHARED_QUEUE #ifndef SHARED_QUEUE
@ -47,7 +47,7 @@ public:
if(queue_.empty()){ if(queue_.empty()){
return false; return false;
} }
popped_item=queue_.front(); popped_item=std::move(queue_.front());
queue_.pop(); queue_.pop();
return true; return true;
} }
@ -59,7 +59,7 @@ public:
{ // The 'while' loop below is equal to { // The 'while' loop below is equal to
data_cond_.wait(lock); //data_cond_.wait(lock, [](bool result){return !queue_.empty();}); 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(); queue_.pop();
} }

View File

@ -12,12 +12,32 @@
namespace namespace
{ {
#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) #if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__))
const std::string path_to_log_file = "./"; const std::string path_to_log_file = "./";
#else #else
const std::string path_to_log_file = "/tmp/"; const std::string path_to_log_file = "/tmp/";
#endif #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) int main(int argc, char** argv)
{ {
double pi_d = 3.1415926535897932384626433832795; double pi_d = 3.1415926535897932384626433832795;
@ -26,11 +46,12 @@ int main(int argc, char** argv)
g2LogWorker logger(argv[0], path_to_log_file); g2LogWorker logger(argv[0], path_to_log_file);
g2::initializeLogging(&logger); g2::initializeLogging(&logger);
std::future<std::string> log_file_name = logger.logFileName();
std::cout << "*** This is an example of g2log " << std::endl; 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 << "*** 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 << "*** Please see the generated log and compare to " << std::endl;
std::cout << "*** the code at g2log/test_example/main.cpp" << 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); LOGF(INFO, "Hi log %d", 123);
LOG(INFO) << "Test SLOG INFO"; 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"; LOG_IF(FATAL, (2>3)) << "This message should NOT throw";
LOGF(DEBUG, "This API is popular with some %s", "programmers"); LOGF(DEBUG, "This API is popular with some %s", "programmers");
LOGF_IF(DEBUG, (1<2), "If true, then this %s will be logged", "message"); LOGF_IF(DEBUG, (1<2), "If true, then this %s will be logged", "message");
{ // OK --- on Ubunti this caused get a compiler warning with gcc4.6
// OK --- on Ubunti this WILL get a compiler warning // from gcc 4.7.2 (at least) it causes a crash (as expected)
// On windows it'll probably crash std::cout << "\n\n***** Be ready on Windows this example will 'abort' " << std::endl; // On windows itll probably crash
std::cout << "\n\n***** Be ready this last example may 'abort' if on Windows/Linux_gcc4.7 " << std::endl << std::flush; // ---- IF you want to try 'FATAL' contract failure please comment away
std::cout << "************************************************************\n\n" << std::endl << std::flush; // ----- the 'illegalPrinout' call below
std::this_thread::sleep_for(std::chrono::seconds(1)); example_fatal::tryToKillWithIllegalPrintout();
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());
}
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"; // non-failure contract
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"; 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);
} }

View File

@ -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 <gtest/gtest.h>
#include <chrono>
#include <thread>
#include <future>
#include <string>
#include <exception>
#include <functional>
#include <memory>
#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<std::string> sillyFutureReturn()
{
std::packaged_task<std::string()> task([](){return std::string("Hello Future");}); // wrap the function
std::future<std::string> 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<Active> bgWorker(Active::createActive());
std::future<std::string> 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<Active> 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<typename F>
std::future<typename std::result_of<F()>::type> ObsoleteSpawnTask(F f)
{
typedef typename std::result_of<F()>::type result_type;
typedef std::packaged_task<result_type()> task_type;
task_type task(std::move(f));
std::future<result_type> result = task.get_future();
std::vector<std::function<void()>> vec;
vec.push_back(g2::PretendToBeCopyable<task_type>(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 <gtest/gtest.h>
#include <iostream>
#include <future>
#include <thread>
#include <vector>
std::vector<std::function<void()>> vec;
template<typename F>
std::future<typename std::result_of<F()>::type> spawn_task(F f)
{
typedef typename std::result_of<F()>::type result_type;
typedef std::packaged_task<result_type()> task_type;
task_type task(std::move(f));
std::future<result_type> res = task.get_future();
vec.push_back(
PretendToBeCopyable<task_type>(
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);
}

View File

@ -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 <gtest/gtest.h>
#include <memory>
#include <fstream>
#include <string>
#include <memory>
#include <future>
#include <queue>
#include <algorithm>
#include <mutex>
#include <thread>
#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<std::string> logs_to_clean_;
std::mutex g_mutex;
public:
size_t size(){return logs_to_clean_.size();}
virtual ~LogFileCleaner() {
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::string> 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<std::thread> 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<std::string> 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;
}

View File

@ -15,383 +15,390 @@
namespace 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) bool verifyContent(const std::string &total_text,std::string msg_to_find)
{ {
std::string content(total_text); std::string content(total_text);
size_t location = content.find(msg_to_find); size_t location = content.find(msg_to_find);
return (location != std::string::npos); return (location != std::string::npos);
} }
std::string readFileToText(std::string filename) std::string readFileToText(std::string filename)
{ {
std::ifstream in; std::ifstream in;
in.open(filename.c_str(),std::ios_base::in); in.open(filename.c_str(),std::ios_base::in);
if(!in.is_open()) if(!in.is_open())
{ {
return ""; // error just return empty string - test will 'fault' return ""; // error just return empty string - test will 'fault'
} }
std::ostringstream oss; std::ostringstream oss;
oss << in.rdbuf(); oss << in.rdbuf();
std::string content(oss.str()); std::string content(oss.str());
return content; return content;
} }
} // end anonymous namespace } // end anonymous namespace
// RAII temporarily replace of logger // RAII temporarily replace of logger
// and restoration of original logger at scope end // and restoration of original logger at scope end
struct RestoreLogger struct RestoreLogger
{ {
RestoreLogger(); RestoreLogger();
~RestoreLogger(); ~RestoreLogger();
void reset();
void reset(); std::unique_ptr<g2LogWorker> logger_;
std::string readFileToText(); std::string logFile(){return log_file_;}
private:
std::unique_ptr<g2LogWorker> logger_; std::string log_file_;
const std::string log_file_;
}; };
RestoreLogger::RestoreLogger() RestoreLogger::RestoreLogger()
: logger_(new g2LogWorker("UNIT_TEST_LOGGER", "./")) : logger_(new g2LogWorker("UNIT_TEST_LOGGER", log_directory))
, log_file_(logger_->logFileName())
{ {
g2::initializeLogging(logger_.get()); g2::initializeLogging(logger_.get());
g2::internal::changeFatalInitHandlerForUnitTesting(); g2::internal::changeFatalInitHandlerForUnitTesting();
std::future<std::string> filename(logger_->logFileName());
EXPECT_TRUE(filename.valid());
log_file_ = filename.get();
} }
RestoreLogger::~RestoreLogger() RestoreLogger::~RestoreLogger()
{ {
reset(); reset();
g2::shutDownLogging(); g2::shutDownLogging();
EXPECT_EQ(0, remove(log_file_.c_str())); EXPECT_EQ(0, remove(log_file_.c_str()));
} }
void RestoreLogger::reset() void RestoreLogger::reset()
{ {
logger_.reset(); logger_.reset();
} }
// LOG // LOG
TEST(LOGTest, LOG) TEST(LOGTest, LOG)
{ {
std::string file_content; std::string file_content;
{ {
RestoreLogger logger; RestoreLogger logger;
LOG(INFO) << "test LOG(INFO)"; LOG(INFO) << "test LOG(INFO)";
logger.reset(); // force flush of logger logger.reset(); // force flush of logger
file_content = readFileToText(logger.log_file_); file_content = readFileToText(logger.logFile());
SCOPED_TRACE("LOG_INFO"); // Scope exit be prepared for destructor failure SCOPED_TRACE("LOG_INFO"); // Scope exit be prepared for destructor failure
} }
ASSERT_TRUE(verifyContent(file_content, "test LOG(INFO)")); ASSERT_TRUE(verifyContent(file_content, "test LOG(INFO)"));
} }
namespace
{ namespace {
const std::string t_info = "test INFO "; const std::string t_info = "test INFO ";
const std::string t_info2 = "test INFO 123"; const std::string t_info2 = "test INFO 123";
const std::string t_debug = "test DEBUG "; const std::string t_debug = "test DEBUG ";
const std::string t_debug2 = "test DEBUG 1.123456"; const std::string t_debug2 = "test DEBUG 1.123456";
const std::string t_warning = "test WARNING "; const std::string t_warning = "test WARNING ";
const std::string t_warning2 = "test WARNING yello"; const std::string t_warning2 = "test WARNING yello";
} }
// printf-type log // printf-type log
TEST(LogTest, LOG_F) TEST(LogTest, LOG_F)
{ {
std::string file_content;
{
RestoreLogger logger;
std::cout << "logfilename: " << logger.logFile() << std::flush << std::endl;
std::string file_content; LOGF(INFO, std::string(t_info + "%d").c_str(), 123);
{ LOGF(DEBUG, std::string(t_debug + "%f").c_str(), 1.123456);
RestoreLogger logger; LOGF(WARNING, std::string(t_warning + "%s").c_str(), "yello");
LOGF(INFO, std::string(t_info + "%d").c_str(), 123); logger.reset(); // force flush of logger
LOGF(DEBUG, std::string(t_debug + "%f").c_str(), 1.123456); file_content = readFileToText(logger.logFile());
LOGF(WARNING, std::string(t_warning + "%s").c_str(), "yello"); SCOPED_TRACE("LOG_INFO"); // Scope exit be prepared for destructor failure
logger.reset(); // force flush of logger }
file_content = readFileToText(logger.log_file_); ASSERT_TRUE(verifyContent(file_content, t_info2));
SCOPED_TRACE("LOG_INFO"); // Scope exit be prepared for destructor failure ASSERT_TRUE(verifyContent(file_content, t_debug2));
} ASSERT_TRUE(verifyContent(file_content, t_warning2));
ASSERT_TRUE(verifyContent(file_content, t_info2));
ASSERT_TRUE(verifyContent(file_content, t_debug2));
ASSERT_TRUE(verifyContent(file_content, t_warning2));
} }
// stream-type log // stream-type log
TEST(LogTest, LOG) TEST(LogTest, LOG)
{ {
std::string file_content; std::string file_content;
{ {
RestoreLogger logger; RestoreLogger logger;
LOG(INFO) << t_info << 123; LOG(INFO) << t_info << 123;
LOG(DEBUG) << t_debug << std::setprecision(7) << 1.123456f; LOG(DEBUG) << t_debug << std::setprecision(7) << 1.123456f;
LOG(WARNING) << t_warning << "yello"; LOG(WARNING) << t_warning << "yello";
logger.reset(); // force flush of logger logger.reset(); // force flush of logger
file_content = readFileToText(logger.log_file_); file_content = readFileToText(logger.logFile());
SCOPED_TRACE("LOG_INFO"); // Scope exit be prepared for destructor failure SCOPED_TRACE("LOG_INFO"); // Scope exit be prepared for destructor failure
} }
ASSERT_TRUE(verifyContent(file_content, t_info2)); ASSERT_TRUE(verifyContent(file_content, t_info2));
ASSERT_TRUE(verifyContent(file_content, t_debug2)); ASSERT_TRUE(verifyContent(file_content, t_debug2));
ASSERT_TRUE(verifyContent(file_content, t_warning2)); ASSERT_TRUE(verifyContent(file_content, t_warning2));
} }
TEST(LogTest, LOG_F_IF) TEST(LogTest, LOG_F_IF)
{ {
std::string file_content; std::string file_content;
{ {
RestoreLogger logger; RestoreLogger logger;
LOGF_IF(INFO, (2 == 2), std::string(t_info + "%d").c_str(), 123); 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); LOGF_IF(DEBUG, (2 != 2), std::string(t_debug + "%f").c_str(), 1.123456);
logger.reset(); // force flush of logger logger.reset(); // force flush of logger
file_content = readFileToText(logger.log_file_); file_content = readFileToText(logger.logFile());
SCOPED_TRACE("LOG_IF"); // Scope exit be prepared for destructor failure SCOPED_TRACE("LOG_IF"); // Scope exit be prepared for destructor failure
} }
ASSERT_TRUE(verifyContent(file_content, t_info2)); ASSERT_TRUE(verifyContent(file_content, t_info2));
ASSERT_FALSE(verifyContent(file_content, t_debug2)); ASSERT_FALSE(verifyContent(file_content, t_debug2));
} }
TEST(LogTest, LOG_IF) TEST(LogTest, LOG_IF)
{ {
std::string file_content; std::string file_content;
{ {
RestoreLogger logger; RestoreLogger logger;
LOG_IF(INFO, (2 == 2)) << t_info << 123; LOG_IF(INFO, (2 == 2)) << t_info << 123;
LOG_IF(DEBUG, (2 != 2)) << t_debug << std::setprecision(7) << 1.123456f; LOG_IF(DEBUG, (2 != 2)) << t_debug << std::setprecision(7) << 1.123456f;
logger.reset(); // force flush of logger logger.reset(); // force flush of logger
file_content = readFileToText(logger.log_file_); file_content = readFileToText(logger.logFile());
SCOPED_TRACE("LOG_IF"); // Scope exit be prepared for destructor failure SCOPED_TRACE("LOG_IF"); // Scope exit be prepared for destructor failure
} }
ASSERT_TRUE(verifyContent(file_content, t_info2)); ASSERT_TRUE(verifyContent(file_content, t_info2));
ASSERT_FALSE(verifyContent(file_content, t_debug2)); ASSERT_FALSE(verifyContent(file_content, t_debug2));
} }
TEST(LogTest, LOGF__FATAL) TEST(LogTest, LOGF__FATAL)
{ {
RestoreLogger logger; RestoreLogger logger;
try try
{ {
LOGF(FATAL, "This message should throw %d",0); LOGF(FATAL, "This message should throw %d",0);
} }
catch (std::exception const &e) catch (std::exception const &e)
{ {
logger.reset(); logger.reset();
std::string file_content = readFileToText(logger.log_file_); std::string file_content = readFileToText(logger.logFile());
std::cerr << file_content << std::endl << std::flush; std::cerr << file_content << std::endl << std::flush;
if(verifyContent(e.what(), "EXIT trigger caused by ") && if(verifyContent(e.what(), "EXIT trigger caused by ") &&
verifyContent(file_content, "FATAL") && verifyContent(file_content, "FATAL") &&
verifyContent(file_content, "This message should throw")) verifyContent(file_content, "This message should throw"))
{ {
SUCCEED(); SUCCEED();
return; return;
} }
else else
{ {
ADD_FAILURE() << "Didn't throw exception as expected"; ADD_FAILURE() << "Didn't throw exception as expected";
} }
} }
ADD_FAILURE() << "Didn't throw exception at ALL"; ADD_FAILURE() << "Didn't throw exception at ALL";
} }
TEST(LogTest, LOG_FATAL) TEST(LogTest, LOG_FATAL)
{ {
RestoreLogger logger; RestoreLogger logger;
try try
{ {
LOG(FATAL) << "This message should throw"; LOG(FATAL) << "This message should throw";
} }
catch (std::exception const &e) catch (std::exception const &e)
{ {
logger.reset(); logger.reset();
std::string file_content = readFileToText(logger.log_file_); std::string file_content = readFileToText(logger.logFile());
if(verifyContent(e.what(), "EXIT trigger caused by ") && if(verifyContent(e.what(), "EXIT trigger caused by ") &&
verifyContent(file_content, "FATAL") && verifyContent(file_content, "FATAL") &&
verifyContent(file_content, "This message should throw")) verifyContent(file_content, "This message should throw"))
{ {
SUCCEED(); SUCCEED();
return; return;
} }
else else
{ {
ADD_FAILURE() << "Didn't throw exception as expected"; ADD_FAILURE() << "Didn't throw exception as expected";
} }
} }
ADD_FAILURE() << "Didn't throw exception at ALL"; ADD_FAILURE() << "Didn't throw exception at ALL";
} }
TEST(LogTest, LOGF_IF__FATAL) TEST(LogTest, LOGF_IF__FATAL)
{ {
RestoreLogger logger; RestoreLogger logger;
try try
{ {
LOGF_IF(FATAL, (2<3), "This message%sshould throw"," "); LOGF_IF(FATAL, (2<3), "This message%sshould throw"," ");
} }
catch (std::exception const &e) catch (std::exception const &e)
{ {
logger.reset(); logger.reset();
std::string file_content = readFileToText(logger.log_file_); std::string file_content = readFileToText(logger.logFile());
if(verifyContent(e.what(), "EXIT trigger caused by ") && if(verifyContent(e.what(), "EXIT trigger caused by ") &&
verifyContent(file_content, "FATAL") && verifyContent(file_content, "FATAL") &&
verifyContent(file_content, "This message should throw")) verifyContent(file_content, "This message should throw"))
{ {
SUCCEED(); SUCCEED();
return; return;
} }
else else
{ {
ADD_FAILURE() << "Didn't throw exception as expected"; ADD_FAILURE() << "Didn't throw exception as expected";
} }
} }
ADD_FAILURE() << "Didn't throw exception at ALL"; ADD_FAILURE() << "Didn't throw exception at ALL";
} }
TEST(LogTest, LOG_IF__FATAL) TEST(LogTest, LOG_IF__FATAL)
{ {
RestoreLogger logger; RestoreLogger logger;
try try
{ {
LOG_IF(WARNING, (0 != t_info.compare(t_info))) << "This message should NOT be written"; 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"; LOG_IF(FATAL, (0 != t_info.compare(t_info2))) << "This message should throw";
} }
catch (std::exception const &e) catch (std::exception const &e)
{ {
logger.reset(); logger.reset();
std::string file_content = readFileToText(logger.log_file_); std::string file_content = readFileToText(logger.logFile());
if(verifyContent(e.what(), "EXIT trigger caused by ") && if(verifyContent(e.what(), "EXIT trigger caused by ") &&
verifyContent(file_content, "FATAL") && verifyContent(file_content, "FATAL") &&
verifyContent(file_content, "This message should throw") && verifyContent(file_content, "This message should throw") &&
(false == verifyContent(file_content, "This message should NOT be written"))) (false == verifyContent(file_content, "This message should NOT be written")))
{ {
SUCCEED(); SUCCEED();
return; return;
} }
else else
{ {
ADD_FAILURE() << "Didn't throw exception as expected"; ADD_FAILURE() << "Didn't throw exception as expected";
} }
} }
ADD_FAILURE() << "Didn't throw exception at ALL"; ADD_FAILURE() << "Didn't throw exception at ALL";
} }
TEST(LogTest, LOG_IF__FATAL__NO_THROW) TEST(LogTest, LOG_IF__FATAL__NO_THROW)
{ {
RestoreLogger logger; RestoreLogger logger;
try try
{ {
LOG_IF(FATAL, (2>3)) << "This message%sshould NOT throw"; LOG_IF(FATAL, (2>3)) << "This message%sshould NOT throw";
} }
catch (std::exception const &e) catch (std::exception const &e)
{ {
std::cerr << e.what() << std::endl; std::cerr << e.what() << std::endl;
logger.reset(); logger.reset();
ADD_FAILURE() << "Didn't throw exception as expected"; ADD_FAILURE() << "Didn't throw exception as expected";
} }
logger.reset(); logger.reset();
SUCCEED(); SUCCEED();
} }
// CHECK_F // CHECK_F
TEST(CheckTest, CHECK_F__thisWILL_PrintErrorMsg) TEST(CheckTest, CHECK_F__thisWILL_PrintErrorMsg)
{ {
RestoreLogger logger; RestoreLogger logger;
try try
{ {
CHECK(1 == 2); CHECK(1 == 2);
} }
catch (std::exception const &e) catch (std::exception const &e)
{ {
logger.reset(); logger.reset();
std::string file_content = readFileToText(logger.log_file_); std::string file_content = readFileToText(logger.logFile());
if(verifyContent(e.what(), "EXIT trigger caused by ") && if(verifyContent(e.what(), "EXIT trigger caused by ") &&
verifyContent(file_content, "FATAL")) verifyContent(file_content, "FATAL"))
{ {
SUCCEED(); SUCCEED();
return; return;
} }
} }
ADD_FAILURE() << "Didn't throw exception as expected"; ADD_FAILURE() << "Didn't throw exception as expected";
} }
TEST(CHECK_F_Test, CHECK_F__thisWILL_PrintErrorMsg) TEST(CHECK_F_Test, CHECK_F__thisWILL_PrintErrorMsg)
{ {
RestoreLogger logger; RestoreLogger logger;
std::string msg = "This message is added to throw %s and %s"; 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 msg2 = "This message is added to throw message and log";
std::string arg1 = "message"; std::string arg1 = "message";
std::string arg2 = "log"; std::string arg2 = "log";
try try
{ {
CHECK_F(1 >= 2, msg.c_str(), arg1.c_str(), arg2.c_str()); CHECK_F(1 >= 2, msg.c_str(), arg1.c_str(), arg2.c_str());
} }
catch (std::exception const &e) catch (std::exception const &e)
{ {
logger.reset(); logger.reset();
std::string file_content = readFileToText(logger.log_file_); std::string file_content = readFileToText(logger.logFile());
if(verifyContent(e.what(), "EXIT trigger caused by ") && if(verifyContent(e.what(), "EXIT trigger caused by ") &&
verifyContent(file_content, "FATAL") && verifyContent(file_content, "FATAL") &&
verifyContent(file_content, msg2)) verifyContent(file_content, msg2))
{ {
SUCCEED(); SUCCEED();
return; return;
} }
} }
ADD_FAILURE() << "Didn't throw exception as expected"; ADD_FAILURE() << "Didn't throw exception as expected";
} }
TEST(CHECK_Test, CHECK__thisWILL_PrintErrorMsg) TEST(CHECK_Test, CHECK__thisWILL_PrintErrorMsg)
{ {
RestoreLogger logger; RestoreLogger logger;
std::string msg = "This message is added to throw %s and %s"; 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 msg2 = "This message is added to throw message and log";
std::string arg1 = "message"; std::string arg1 = "message";
std::string arg2 = "log"; std::string arg2 = "log";
try try
{ {
CHECK(1 >= 2) << msg2; CHECK(1 >= 2) << msg2;
} }
catch (std::exception const &e) catch (std::exception const &e)
{ {
logger.reset(); logger.reset();
std::string file_content = readFileToText(logger.log_file_); std::string file_content = readFileToText(logger.logFile());
if(verifyContent(e.what(), "EXIT trigger caused by ") && if(verifyContent(e.what(), "EXIT trigger caused by ") &&
verifyContent(file_content, "FATAL") && verifyContent(file_content, "FATAL") &&
verifyContent(file_content, msg2)) verifyContent(file_content, msg2))
{ {
SUCCEED(); SUCCEED();
return; return;
} }
} }
ADD_FAILURE() << "Didn't throw exception as expected"; ADD_FAILURE() << "Didn't throw exception as expected";
} }
TEST(CHECK, CHECK_ThatWontThrow) TEST(CHECK, CHECK_ThatWontThrow)
{ {
RestoreLogger logger; RestoreLogger logger;
std::string msg = "This %s should never appear in the %s"; std::string msg = "This %s should never appear in the %s";
std::string msg2 = "This message should never appear in the log"; std::string msg2 = "This message should never appear in the log";
std::string arg1 = "message"; std::string arg1 = "message";
std::string arg2 = "log"; std::string arg2 = "log";
try try
{ {
CHECK(1 == 1); CHECK(1 == 1);
CHECK_F(1==1, msg.c_str(), "message", "log"); CHECK_F(1==1, msg.c_str(), "message", "log");
} }
catch (std::exception const &e) catch (std::exception const &e)
{ {
std::cerr << e.what() << std::endl; std::cerr << e.what() << std::endl;
ADD_FAILURE() << "Should never have thrown"; ADD_FAILURE() << "Should never have thrown";
} }
std::string file_content = readFileToText(logger.log_file_); std::string file_content = readFileToText(logger.logFile());
ASSERT_FALSE(verifyContent(file_content, msg2)); ASSERT_FALSE(verifyContent(file_content, msg2));
} }