diff --git a/g2log/src/crashhandler_unix.cpp b/g2log/src/crashhandler_unix.cpp index 1c27b10..8d3c416 100644 --- a/g2log/src/crashhandler_unix.cpp +++ b/g2log/src/crashhandler_unix.cpp @@ -7,6 +7,7 @@ #include "crashhandler.hpp" #include "g2log.hpp" #include "g2logmessage.hpp" +#include "g2LogMessageBuilder.hpp" #include #include @@ -41,14 +42,13 @@ namespace { fatal_stream << oss.str() << std::endl; fatal_stream << "\n***** SIGNAL " << signalName(signal_number) << "(" << signal_number << ")" << std::endl; - FatalMessage fatal_message(fatal_stream.str(), signal_number); - const auto& crashMessage = fatal_message._crash_message; - std::cerr << crashMessage.toString() << std::endl << std::flush; + //g2::FatalMessage fatal_message{fatal_stream.str(), signal_number}; + //const auto& crashMessage = fatal_message._crash_message; + std::cerr << fatal_stream.str() << std::flush; - FatalTrigger trigger(fatal_message); - std::ostringstream oss; - } // message sent to g2LogWorker by FatalTrigger - // wait to die -- will be inside the FatalTrigger + g2::FatalMessageBuilder trigger(fatal_stream.str(), signal_number); + } // message sent to g2LogWorker by FatalMessageBuilder + // wait to die -- will be inside the FatalMessageBuilder } } // end anonymous namespace diff --git a/g2log/src/crashhandler_win.cpp b/g2log/src/crashhandler_win.cpp index aba8ec1..cad38cf 100644 --- a/g2log/src/crashhandler_win.cpp +++ b/g2log/src/crashhandler_win.cpp @@ -27,7 +27,7 @@ void crashHandler(int signal_number) fatal_stream << "\n***** RETHROWING SIGNAL " << signalName(signal_number) << "(" << signal_number << ")" << std::endl; FatalMessage fatal_message(fatal_stream.str(),FatalMessage::kReasonOS_FATAL_SIGNAL, signal_number); - FatalTrigger trigger(fatal_message); std::ostringstream oss; + FatalMessageBuilder trigger(fatal_message); std::ostringstream oss; std::cerr << fatal_message.message_ << std::endl << std::flush; } // scope exit - message sent to LogWorker, wait to die... } // end anonymous namespace diff --git a/g2log/src/g2LogMessageBuilder.cpp b/g2log/src/g2LogMessageBuilder.cpp new file mode 100644 index 0000000..4333935 --- /dev/null +++ b/g2log/src/g2LogMessageBuilder.cpp @@ -0,0 +1,56 @@ +/* + * File: MessageBuilder.cpp + * Author: kjell + * + * Created on October 26, 2013, 3:45 PM + */ + +#include "g2LogMessageBuilder.hpp" +#include "g2logmessageimpl.hpp" +#include "g2log.hpp" +#include "g2logmessage.hpp" +#include +#include + +namespace g2 { + + LogMessageBuilder::LogMessageBuilder(const std::string& file, const int line, + const std::string& function, const LEVELS& level) + : _message(std::make_shared(file, line, function, level)) {} + //: _message(new LogMessageImpl(file, line, function, level)) // + //{} + + LogMessageBuilder::~LogMessageBuilder() { + LogMessage log_entry(_message); + if (log_entry.wasFatal()) { + FatalMessageBuilder trigger({log_entry.toString(), SIGABRT}); + return; // FatalMessageBuilder will send to worker at scope exit + } + internal::saveMessage(log_entry); // message saved to g2LogWorker + } + + LogMessageBuilder& LogMessageBuilder::setExpression(const std::string& boolean_expression) { + _message->_expression = boolean_expression; + return *this; + } + + std::ostringstream& LogMessageBuilder::stream() { + return _message->_stream; + } + + + /// FatalMessageBuilder + + FatalMessageBuilder::FatalMessageBuilder(const std::string& exit_message, int fatal_signal) + :_exit_message(exit_message), _fatal_signal(fatal_signal) { } + + FatalMessageBuilder::~FatalMessageBuilder() { + // At destruction, flushes fatal message to g2LogWorker + // either we will stay here until the background worker has received the fatal + // message, flushed the crash message to the sinks and exits with the same fatal signal + //..... OR it's in unit-test mode then we throw a std::runtime_error (and never hit sleep) + internal::fatalCall({_exit_message, _fatal_signal}); + } + + +} // g2 \ No newline at end of file diff --git a/g2log/src/g2LogMessageBuilder.hpp b/g2log/src/g2LogMessageBuilder.hpp new file mode 100644 index 0000000..acbe299 --- /dev/null +++ b/g2log/src/g2LogMessageBuilder.hpp @@ -0,0 +1,48 @@ +/* + * File: MessageBuilder.hpp + * Author: kjell + * + * Created on October 26, 2013, 3:45 PM + */ + +#pragma once +#include +#include +#include +#include +#include "g2loglevels.hpp" + + +namespace g2 {struct LogMessageImpl; + +// At RAII scope end this struct will trigger a FatalMessage sending +struct FatalMessageBuilder { + //explicit FatalMessageBuilder(const FatalMessage& exit_message); + FatalMessageBuilder(const std::string& exit_message, int fatal_signal); + ~FatalMessageBuilder(); + + std::string _exit_message; + int _fatal_signal; +}; + + +struct LogMessageBuilder { + LogMessageBuilder(const std::string& file, const int line, const std::string& function, const LEVELS& level); + virtual ~LogMessageBuilder(); + + LogMessageBuilder& setExpression(const std::string& boolean_expression); + std::ostringstream& stream(); + + // Use "-Wall" to generate warnings in case of illegal printf format. + // Ref: http://www.unixwiz.net/techtips/gnu-c-attributes.html +#ifndef __GNUC__ +#define __attribute__(x) // Disable 'attributes' if compiler does not support 'em +#endif + void messageSave(const char *printf_like_message, ...) + __attribute__((format(printf, 2, 3))); // ref: http://www.codemaestro.com/reviews/18 + +private: + std::shared_ptr _message; +}; + +} // g2 \ No newline at end of file diff --git a/g2log/src/g2log.hpp b/g2log/src/g2log.hpp index 2a34e0d..395692f 100644 --- a/g2log/src/g2log.hpp +++ b/g2log/src/g2log.hpp @@ -17,8 +17,7 @@ * ********************************************* */ -#ifndef G2LOG_H -#define G2LOG_H +#pragma once #include #include @@ -46,14 +45,23 @@ class g2LogWorker; */ namespace g2 { struct LogMessage; +struct FatalMessage; + /** Should be called at very first startup of the software with \ref g2LogWorker * pointer. Ownership of the \ref g2LogWorker is the responsibilkity of the caller */ void initializeLogging(g2LogWorker *logger); namespace internal { + // Save the created LogMessage to any existing sinks -void saveMessage(const g2::LogMessage& log_entry); +void saveMessage(g2::LogMessage log_entry); + + +// Save the created FatalMessage to any existing sinks and exit with +// the originating fatal signal,. or SIGABRT if it originated from a broken contract +void fatalCall(FatalMessage message); + /** FOR TESTING PURPOSES * Shutdown the logging by making the pointer to the background logger to nullptr @@ -72,21 +80,18 @@ bool isLoggingInitialized(); * test of FATAL level cumbersome. A work around is to change the * fatal call' which can be done here */ void changeFatalInitHandlerForUnitTesting(); - - -} // end namespace internal -} // end namespace g2 +} // g2::internal +} // g2 -#define INTERNAL_LOG_MESSAGE(level) g2::internal::LogMessageBuilder(__FILE__, __LINE__, __PRETTY_FUNCTION__, level) +#define INTERNAL_LOG_MESSAGE(level) g2::LogMessageBuilder(__FILE__, __LINE__, __PRETTY_FUNCTION__, level) + -//LogMessageBuilder(const char* file, const int line, const char* function, const LEVELS& level){} #define INTERNAL_CONTRACT_MESSAGE(boolean_expression) \ - g2::internal::LogMessageBuilder(__FILE__, __LINE__, __PRETTY_FUNCTION__, ::FATAL) -//.setExpression(boolean_expression) + g2::LogMessageBuilder(__FILE__, __LINE__, __PRETTY_FUNCTION__, FATAL).setExpression(boolean_expression) // LOG(level) is the API for the stream log @@ -165,4 +170,4 @@ And here is possible output #define CHECK_F(boolean_expression, printf_like_message, ...) \ if (false == (boolean_expression)) INTERNAL_CONTRACT_MESSAGE(#boolean_expression).messageSave(printf_like_message, ##__VA_ARGS__) -#endif // G2LOG_H + diff --git a/g2log/src/g2log.ipp b/g2log/src/g2log.ipp new file mode 100644 index 0000000..0996670 --- /dev/null +++ b/g2log/src/g2log.ipp @@ -0,0 +1,95 @@ +/* + * File: g2logg.ipp + * Author: kjell + * + * Created on October 20, 2013, 8:47 PM + */ + +#pragma once + +#include "g2log.hpp" +#include "g2logmessage.hpp" +#include "g2logworker.hpp" +#include "g2logmessageimpl.hpp" +#include "g2loglevels.hpp" + +#include +#include +#include +#include +#include + +namespace g2 { +namespace internal { +g2LogWorker* g_logger_instance = nullptr; // instantiated and OWNED somewhere else (main) +std::string g_once_error; +std::once_flag g_error_flag; +std::once_flag g_retrieve_error_flag; +} + + +/** + * saveMesage to the LogWorker which will pass them to the sinks. + * + * In the messup of calling LOG before instantiating the logger the first + * message will be saved,.. to be given later with an initialization warning + */ +void saveMessage(g2::LogMessage log_entry) { + using namespace internal; + + if (false == isLoggingInitialized()) { + std::call_once(g_error_flag, [&]() { + g_once_error = {"\n\nWARNING LOGGER NOT INSTANTIATED WHEN CALLING IT" + "\nAt least once tried to call the logger before instantiating it\n\n"}; + g_once_error.append("\nThe first message was: \n").append(log_entry.toString()); + }); + std::cerr << g_once_error << std::endl; + return; + } + + std::call_once(g_retrieve_error_flag, [&]() { + if (false == g_once_error.empty()) { + std::string empty = {""}; + g2::LogMessage error{std::make_shared(empty, 0, empty, WARNING)}; + error.stream() << g_once_error; + g_logger_instance->save(error); + } + }); + + g_logger_instance->save(log_entry); +} + +void fatalCallToLogger(FatalMessage message) { + // real fatal call to loggr + internal::g_logger_instance->fatal(message); +} + +// By default this function pointer goes to \ref fatalCall; +void (*g_fatal_to_g2logworker_function_ptr)(FatalMessage) = fatalCallToLogger; + +void fatalCallForUnitTest(FatalMessage fatal_message) { + // mock fatal call, not to logger: used by unit test + assert(internal::g_logger_instance != nullptr); + internal::g_logger_instance->save(fatal_message.copyToLogMessage()); // calling 'save' instead of 'fatal' + throw std::runtime_error(fatal_message.toString()); +} + +/** The default, initial, handling to send a 'fatal' event to g2logworker + * he caller will stay here, eternally, until the software is aborted + * During unit testing the sleep-loop will be interrupted by exception from + * @ref fatalCallForUnitTest */ +void fatalCall(FatalMessage message) { + g_fatal_to_g2logworker_function_ptr(message); + while (true) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } +} + +/** Used to REPLACE fatalCallToLogger for fatalCallForUnitTest + * This function switches the function pointer so that only + * 'unitTest' mock-fatal calls are made. */ +void changeFatalInitHandlerForUnitTesting() { + g_fatal_to_g2logworker_function_ptr = fatalCallForUnitTest; +} +} // g2 + diff --git a/g2log/src/g2logmessage.cpp b/g2log/src/g2logmessage.cpp index b043762..187c978 100644 --- a/g2log/src/g2logmessage.cpp +++ b/g2log/src/g2logmessage.cpp @@ -10,11 +10,12 @@ * ********************************************* */ #include "g2logmessage.hpp" +#include "g2logmessageimpl.hpp" #include "g2time.hpp" #include "crashhandler.hpp" #include // exceptions #include "g2log.hpp" -#include "g2log.ipp" + namespace g2 { @@ -76,7 +77,7 @@ namespace g2 { std::ostringstream oss; oss << "\n" << timestamp() << "." << microseconds() << "\t"; - + oss << level() << " [" << file(); oss << " L: " << line() << "]\t"; @@ -116,31 +117,20 @@ namespace g2 { } - LogMessage(std::shared_ptr details) + LogMessage::LogMessage(std::shared_ptr details) : _pimpl(details) { } - - namespace internal { + FatalMessage::FatalMessage(const std::string& crash_message, int signal_id) + : LogMessage(std::make_shared(crash_message)), signal_id_(signal_id) + { } - FatalMessage::FatalMessage(const std::string& crash_message, int signal_id) - : FatalMessage({std::make_shared(crash_message)}, signal_id) { } + LogMessage FatalMessage::copyToLogMessage() const { + return LogMessage(_pimpl); + } + + //FatalMessage(const LogMessage& message, int signal_id) + //: _crash_message(message), signal_id_(signal_id) { } - - FatalMessage(const LogMessage& message, int signal_id) - : _crash_message(message), signal_id_(signal_id) { } - - - FatalTrigger::FatalTrigger(const FatalMessage& exit_message) : _fatal_message(exit_message) { } - - - FatalTrigger::~FatalTrigger() { - // At destruction, flushes fatal message to g2LogWorker - // either we will stay here until the background worker has received the fatal - // message, flushed the crash message to the sinks and exits with the same fatal signal - //..... OR it's in unit-test mode then we throw a std::runtime_error (and never hit sleep) - fatalCall(_fatal_message); - } - } // internal } // g2 diff --git a/g2log/src/g2logmessage.hpp b/g2log/src/g2logmessage.hpp index 58d64fa..25e6ac7 100644 --- a/g2log/src/g2logmessage.hpp +++ b/g2log/src/g2logmessage.hpp @@ -21,52 +21,45 @@ #include "g2log.hpp" #include "g2loglevels.hpp" #include "g2time.hpp" +//#include "g2logmessageimpl.hpp" namespace g2 { -struct LogMessageImpl; + struct LogMessageImpl; + + struct LogMessage { + mutable std::shared_ptr _pimpl; + std::string file() const; + std::string line() const; + std::string function() const; + std::string level() const; + + std::string timestamp(const std::string& time_format = {internal::date_formatted + " " + internal::time_formatted}) const; + std::string microseconds() const; + std::string message() const; + std::string expression() const; + + bool wasFatal() const; + + // convert all content to ONE string + std::string toString() const; -struct LogMessage { - mutable std::shared_ptr _pimpl; - std::string file() const; - std::string line() const; - std::string function() const; - std::string level() const; - - std::string timestamp(const std::string& time_format = {internal::date_formatted + " " + internal::time_formatted}) const; - std::string microseconds() const; - std::string message() const; - std::string expression() const; + std::ostringstream& stream(); + explicit LogMessage(std::shared_ptr details); + ~LogMessage() = default; + }; - bool wasFatal() const; - - // convert all content to ONE string - std::string toString() const; - - - std::ostringstream& stream(); - explicit LogMessage(std::shared_ptr details); - ~LogMessage() = default; -}; - - - namespace internal { /** 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 { + struct FatalMessage : public LogMessage { FatalMessage(const std::string& message, int signal_id); - FatalMessage(const LogMessage& message, int signal_id); + //FatalMessage(const LogMessage& message, int signal_id); ~FatalMessage() = default; - mutable LogMessage _crash_message; + + LogMessage copyToLogMessage() const; + + //mutable LogMessage _crash_message; int signal_id_; }; - - // At RAII scope end this struct will trigger a FatalMessage sending - struct FatalTrigger { - explicit FatalTrigger(const FatalMessage& exit_message); - ~FatalTrigger(); - FatalMessage _fatal_message; - }; - } // internal } // g2 diff --git a/g2log/src/g2logmessageimpl.cpp b/g2log/src/g2logmessageimpl.cpp new file mode 100644 index 0000000..324d949 --- /dev/null +++ b/g2log/src/g2logmessageimpl.cpp @@ -0,0 +1,39 @@ + + +#include +#include +#include + +namespace { + const int kMaxMessageSize = 2048; + const std::string kTruncatedWarningText = "[...truncated...]"; + std::once_flag g_start_time_flag; + g2::steady_time_point g_start_time; + + long microsecondsCounter() { + std::call_once(g_start_time_flag, []() { g_start_time = std::chrono::steady_clock::now(); }); + g2::steady_time_point now = std::chrono::steady_clock::now(); + return std::chrono::duration_cast(g_start_time - now).count(); + } + + std::string splitFileName(const std::string& str) { + size_t found; + found = str.find_last_of("(/\\"); + return str.substr(found + 1); + } +} // anonymous + + +namespace g2 { + LogMessageImpl::LogMessageImpl(const std::string &file, const int line, + const std::string& function, const LEVELS& level) + : _timestamp(g2::systemtime_now()) + , _microseconds(microsecondsCounter()) + , _file(splitFileName(file)), _line(line), _function(function), _level(level) { } + + + LogMessageImpl::LogMessageImpl(const std::string& fatalOsSignalCrashMessage) + : LogMessageImpl({""}, 0, {""}, internal::FATAL_SIGNAL) { + _stream << fatalOsSignalCrashMessage; + } +} // g2 \ No newline at end of file diff --git a/g2log/src/g2logmessageimpl.hpp b/g2log/src/g2logmessageimpl.hpp new file mode 100644 index 0000000..602f892 --- /dev/null +++ b/g2log/src/g2logmessageimpl.hpp @@ -0,0 +1,39 @@ +// -*- C++ -*- +/* + * File: g2logmessageimpl.hpp + * Author: kjell + * + * Created on October 27, 2013, 9:14 PM + */ + +#pragma once + + +#include +#include "g2loglevels.hpp" +#include +#include + +namespace g2 { +struct LogMessageImpl { + + // LOG message constructor + LogMessageImpl(const std::string &file, const int line, + const std::string& function, const LEVELS& level); + + // Fatal OS-Signal constructor + explicit LogMessageImpl(const std::string& fatalOsSignalCrashMessage); + + ~LogMessageImpl() = default; + + const std::time_t _timestamp; + const long _microseconds; + const std::string _file; + const int _line; + const std::string _function; + const LEVELS _level; + + std::string _expression; // only with content for CHECK(...) calls + std::ostringstream _stream; +}; +} \ No newline at end of file diff --git a/g2log/src/g2logworker.cpp b/g2log/src/g2logworker.cpp index f14249f..5a01a95 100644 --- a/g2log/src/g2logworker.cpp +++ b/g2log/src/g2logworker.cpp @@ -39,7 +39,7 @@ struct g2LogWorkerImpl { std::cout << "g2logworker active object destroyed. done sending exit messages to all sinks\n"; } - void bgSave(const g2::LogMessage& msg) { + void bgSave(g2::LogMessage msg) { for (auto& sink : _sinks) { sink->send(msg); } @@ -51,12 +51,13 @@ struct g2LogWorkerImpl { } } - void bgFatal(g2::internal::FatalMessage fatal_message) { - auto entry = fatal_message._crash_message; - entry.stream() << "\nExiting after fatal event. Log flushed sucessfully to disk.\n"; - bgSave(entry); + void bgFatal(FatalMessage msg) { + //auto entry = fatal_message._crash_message; + auto fatal_message = msg; + fatal_message.stream() << "\nExiting after fatal event. Log flushed sucessfully to disk.\n"; + bgSave(fatal_message.copyToLogMessage()); - std::cerr << entry.toString() << std::endl; + std::cerr << fatal_message.toString() << std::endl; std::cerr << "g2log sinks are flushed. Now exiting after receiving fatal event\n" << std::flush; _sinks.clear(); // flush all queues exitWithDefaultSignalHandler(fatal_message.signal_id_); @@ -78,11 +79,11 @@ g2LogWorker::~g2LogWorker() { _pimpl->_bg->send([this]{_pimpl->_sinks.clear();}); } // todo move operator -void g2LogWorker::save(g2::LogMessage msg) { +void g2LogWorker::save(LogMessage msg) { _pimpl->_bg->send([this, msg] { _pimpl->bgSave(msg); }); // TODO std::move } -void g2LogWorker::fatal(g2::internal::FatalMessage fatal_message) { +void g2LogWorker::fatal(FatalMessage fatal_message) { _pimpl->_bg->send([this, fatal_message] {_pimpl->bgFatal(fatal_message); }); } diff --git a/g2log/src/g2logworker.hpp b/g2log/src/g2logworker.hpp index 8ad44d7..7e50a4c 100644 --- a/g2log/src/g2logworker.hpp +++ b/g2log/src/g2logworker.hpp @@ -61,7 +61,7 @@ public: /// 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); + void fatal(g2::FatalMessage fatal_message); template std::unique_ptr> addSink(std::unique_ptr real_sink, DefaultLogCall call) { diff --git a/g2log/src/g2sink.h b/g2log/src/g2sink.h index a66b235..005e55f 100644 --- a/g2log/src/g2sink.h +++ b/g2log/src/g2sink.h @@ -58,7 +58,7 @@ struct Sink : public SinkWrapper { _bg.reset(); // TODO: to remove } - void send(LogMessage msg) override { + void send(const LogMessage& msg) override { _bg->send([this, msg] { _default_log_call(msg); }); diff --git a/g2log/src/g2sinkwrapper.h b/g2log/src/g2sinkwrapper.h index 37f8ed7..f291a9d 100644 --- a/g2log/src/g2sinkwrapper.h +++ b/g2log/src/g2sinkwrapper.h @@ -15,7 +15,7 @@ namespace g2 { struct SinkWrapper { virtual ~SinkWrapper() { } - virtual void send(LogMessage msg) = 0; + virtual void send(const LogMessage& msg) = 0; }; } } diff --git a/g2log/test_unit/test_concept_sink.cpp b/g2log/test_unit/test_concept_sink.cpp index 2b94eb5..f2678d3 100644 --- a/g2log/test_unit/test_concept_sink.cpp +++ b/g2log/test_unit/test_concept_sink.cpp @@ -13,6 +13,7 @@ #include "g2sinkwrapper.h" #include "g2sinkhandle.h" #include "g2logmessage.hpp" +#include "g2logmessageimpl.hpp" using namespace std; @@ -35,7 +36,7 @@ public: return buffer.str(); } - void save(g2::internal::LogEntry msg) { + void save(std::string msg) { std::cout << msg; } @@ -50,7 +51,7 @@ public: namespace { typedef std::shared_ptr SinkWrapperPtr; - typedef g2::internal::LogEntry LogEntry; + typedef std::string LogEntry; } namespace g2 { @@ -61,7 +62,9 @@ namespace g2 { void bgSave(LogEntry msg) { for (auto& sink : _container) { - sink->send(msg); + g2::LogMessage message(std::make_shared("test", 0, "test", DEBUG)); + message.stream() << msg; + sink->send(message); } } diff --git a/g2log/test_unit/testing_helpers.cpp b/g2log/test_unit/testing_helpers.cpp index 9f8a459..601d7e5 100644 --- a/g2log/test_unit/testing_helpers.cpp +++ b/g2log/test_unit/testing_helpers.cpp @@ -42,13 +42,13 @@ namespace testing_helpers { ScopedLogger::ScopedLogger() - : _previousWorker(g2::shutDownLogging()) + : _previousWorker(g2::internal::shutDownLogging()) , _currentWorker(g2LogWorker::createWithNoSink()) { g2::initializeLogging(_currentWorker.get()); } ScopedLogger::~ScopedLogger() { - auto* current = g2::shutDownLogging(); + auto* current = g2::internal::shutDownLogging(); CHECK(current == _currentWorker.get()); if (nullptr != _previousWorker) { g2::initializeLogging(_previousWorker);