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
===================
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.

View File

@ -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,8 +84,6 @@ 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)
@ -86,7 +94,8 @@ IF(UNIX)
# "set_target_properties(unit_test PROPERTIES COMPILE_DEFINITIONS "GTEST_USE_OWN_TR1_TUPLE=0")
endif ()
#Visual Studio 2010 -- must use justthread
#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\" ..]")
@ -94,6 +103,7 @@ IF(MSVC10)
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)
@ -111,8 +121,8 @@ 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)
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})
@ -121,6 +131,8 @@ ENDIF(MSVC11)
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})
@ -242,10 +254,19 @@ 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)
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})
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.
* ============================================================================
*
* 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 */

View File

@ -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();
void doDone(){done_ = true;}
void run();
shared_queue<Callback> mq_;
std::thread thd_;
bool done_; // finished flag to be set through msg queue by ~Active
public:
virtual ~Active();
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
* 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/

View File

@ -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,7 +195,7 @@ 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_;}

View File

@ -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 <iostream>
#include <functional>
#include <algorithm>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <cassert>
#include <iomanip>
#include <ctime>
#include <algorithm>
#include <string>
#include <chrono>
#include <future>
#include <functional>
#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<long,std::ratio<1, 1000> > millisecond;
typedef std::chrono::duration<long long,std::ratio<1, 1000000> > microsecond;
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
};
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<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
/** 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<kjellkod::Active> bg_;
std::ofstream out;
time_point start_time_;
std::unique_ptr<std::ofstream> 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<microsecond>(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<std::chrono::microseconds>(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<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
g2LogWorker::g2LogWorker(const std::string& log_prefix, const std::string& log_directory)
//
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()
{
g2LogWorker::~g2LogWorker()
{
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));
}
}
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));
}
}
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,18 +1,21 @@
#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
* ********************************************* */
* 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 <memory>
#include <future>
#include <string>
#include "g2log.h"
struct g2LogWorkerImpl;
@ -34,8 +37,13 @@ public:
/// 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;
/// 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_;
@ -46,30 +54,4 @@ private:
};
/* 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 {
static std::tm *call( std::time_t const *t, std::tm *r )
{ return localtime_r( t, r ); }
};
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_

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
* 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();
}

View File

@ -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<std::string> 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);
}

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,17 +15,18 @@
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);
size_t location = content.find(msg_to_find);
return (location != std::string::npos);
}
}
std::string readFileToText(std::string filename)
{
std::string readFileToText(std::string filename)
{
std::ifstream in;
in.open(filename.c_str(),std::ios_base::in);
if(!in.is_open())
@ -36,7 +37,7 @@ std::string readFileToText(std::string filename)
oss << in.rdbuf();
std::string content(oss.str());
return content;
}
}
} // end anonymous namespace
// RAII temporarily replace of logger
@ -45,21 +46,24 @@ struct RestoreLogger
{
RestoreLogger();
~RestoreLogger();
void reset();
std::string readFileToText();
std::unique_ptr<g2LogWorker> logger_;
const std::string log_file_;
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();
std::future<std::string> filename(logger_->logFileName());
EXPECT_TRUE(filename.valid());
log_file_ = filename.get();
}
RestoreLogger::~RestoreLogger()
@ -76,7 +80,6 @@ void RestoreLogger::reset()
// LOG
TEST(LOGTest, LOG)
{
@ -85,34 +88,35 @@ TEST(LOGTest, LOG)
RestoreLogger logger;
LOG(INFO) << "test LOG(INFO)";
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
}
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;
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_);
file_content = readFileToText(logger.logFile());
SCOPED_TRACE("LOG_INFO"); // Scope exit be prepared for destructor failure
}
ASSERT_TRUE(verifyContent(file_content, t_info2));
@ -120,6 +124,9 @@ TEST(LogTest, LOG_F)
ASSERT_TRUE(verifyContent(file_content, t_warning2));
}
// stream-type log
TEST(LogTest, LOG)
{
@ -130,7 +137,7 @@ TEST(LogTest, LOG)
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_);
file_content = readFileToText(logger.logFile());
SCOPED_TRACE("LOG_INFO"); // Scope exit be prepared for destructor failure
}
ASSERT_TRUE(verifyContent(file_content, t_info2));
@ -147,7 +154,7 @@ TEST(LogTest, LOG_F_IF)
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_);
file_content = readFileToText(logger.logFile());
SCOPED_TRACE("LOG_IF"); // Scope exit be prepared for destructor failure
}
ASSERT_TRUE(verifyContent(file_content, t_info2));
@ -162,7 +169,7 @@ TEST(LogTest, LOG_IF)
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_);
file_content = readFileToText(logger.logFile());
SCOPED_TRACE("LOG_IF"); // Scope exit be prepared for destructor failure
}
ASSERT_TRUE(verifyContent(file_content, t_info2));
@ -179,7 +186,7 @@ TEST(LogTest, LOGF__FATAL)
catch (std::exception const &e)
{
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;
if(verifyContent(e.what(), "EXIT trigger caused by ") &&
verifyContent(file_content, "FATAL") &&
@ -207,7 +214,7 @@ TEST(LogTest, LOG_FATAL)
catch (std::exception const &e)
{
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 ") &&
verifyContent(file_content, "FATAL") &&
verifyContent(file_content, "This message should throw"))
@ -234,7 +241,7 @@ TEST(LogTest, LOGF_IF__FATAL)
catch (std::exception const &e)
{
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 ") &&
verifyContent(file_content, "FATAL") &&
verifyContent(file_content, "This message should throw"))
@ -262,7 +269,7 @@ TEST(LogTest, LOG_IF__FATAL)
catch (std::exception const &e)
{
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 ") &&
verifyContent(file_content, "FATAL") &&
verifyContent(file_content, "This message should throw") &&
@ -308,7 +315,7 @@ TEST(CheckTest, CHECK_F__thisWILL_PrintErrorMsg)
catch (std::exception const &e)
{
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 ") &&
verifyContent(file_content, "FATAL"))
{
@ -334,7 +341,7 @@ TEST(CHECK_F_Test, CHECK_F__thisWILL_PrintErrorMsg)
catch (std::exception const &e)
{
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 ") &&
verifyContent(file_content, "FATAL") &&
verifyContent(file_content, msg2))
@ -360,7 +367,7 @@ TEST(CHECK_Test, CHECK__thisWILL_PrintErrorMsg)
catch (std::exception const &e)
{
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 ") &&
verifyContent(file_content, "FATAL") &&
verifyContent(file_content, msg2))
@ -391,7 +398,7 @@ TEST(CHECK, CHECK_ThatWontThrow)
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));
}