| /** ========================================================================== |
| * 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. |
| * |
| * For more information see g3log/LICENSE or refer refer to http://unlicense.org |
| * ============================================================================ |
| * |
| * Filename:g3log.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 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/ |
| * 3. Dr.Dobbs, Michael Schulze: http://drdobbs.com/article/printableArticle.jhtml?articleId=225700666&dept_url=/cpp/ |
| * 4. Google 'glog': http://google-glog.googlecode.com/svn/trunk/doc/glog.html |
| * 5. Various Q&A at StackOverflow |
| * ********************************************* */ |
| |
| #include "g3log/g3log.hpp" |
| #include "g3log/std2_make_unique.hpp" |
| #include "g3log/logworker.hpp" |
| #include "g3log/crashhandler.hpp" |
| #include "g3log/logmessage.hpp" |
| #include "g3log/loglevels.hpp" |
| |
| |
| #include <mutex> |
| #include <memory> |
| #include <iostream> |
| #include <thread> |
| #include <atomic> |
| #include <cstdlib> |
| #include <sstream> |
| |
| namespace { |
| std::once_flag g_initialize_flag; |
| g3::LogWorker *g_logger_instance = nullptr; // instantiated and OWNED somewhere else (main) |
| std::mutex g_logging_init_mutex; |
| |
| std::unique_ptr<g3::LogMessage> g_first_unintialized_msg = {nullptr}; |
| std::once_flag g_set_first_uninitialized_flag; |
| std::once_flag g_save_first_unintialized_flag; |
| const std::function<void(void)> g_pre_fatal_hook_that_does_nothing = [] { /*does nothing */}; |
| std::function<void(void)> g_fatal_pre_logging_hook; |
| |
| |
| std::atomic<size_t> g_fatal_hook_recursive_counter = {0}; |
| } |
| |
| |
| |
| |
| |
| namespace g3 { |
| // signalhandler and internal clock is only needed to install once |
| // for unit testing purposes the initializeLogging might be called |
| // several times... |
| // for all other practical use, it shouldn't! |
| |
| void initializeLogging(LogWorker *bgworker) { |
| std::call_once(g_initialize_flag, [] { |
| installCrashHandler(); |
| }); |
| std::lock_guard<std::mutex> lock(g_logging_init_mutex); |
| if (internal::isLoggingInitialized() || nullptr == bgworker) { |
| std::ostringstream exitMsg; |
| exitMsg << __FILE__ "->" << __FUNCTION__ << ":" << __LINE__ << std::endl; |
| exitMsg << "\tFatal exit due to illegal initialization of g3::LogWorker\n"; |
| exitMsg << "\t(due to multiple initializations? : " << std::boolalpha << internal::isLoggingInitialized(); |
| exitMsg << ", due to nullptr == bgworker? : " << std::boolalpha << (nullptr == bgworker) << ")"; |
| std::cerr << exitMsg.str() << std::endl; |
| std::exit(EXIT_FAILURE); |
| } |
| |
| // Save the first uninitialized message, if any |
| std::call_once(g_save_first_unintialized_flag, [&bgworker] { |
| if (g_first_unintialized_msg) { |
| bgworker->save(LogMessagePtr {std::move(g_first_unintialized_msg)}); |
| } |
| }); |
| |
| g_logger_instance = bgworker; |
| // by default the pre fatal logging hook does nothing |
| // if it WOULD do something it would happen in |
| setFatalPreLoggingHook(g_pre_fatal_hook_that_does_nothing); |
| // recurvise crash counter re-set to zero |
| g_fatal_hook_recursive_counter.store(0); |
| } |
| |
| |
| /** |
| * default does nothing, @ref ::g_pre_fatal_hook_that_does_nothing |
| * It will be called just before sending the fatal message, @ref pushFatalmessageToLogger |
| * It will be reset to do nothing in ::initializeLogging(...) |
| * so please call this function, if you ever need to, after initializeLogging(...) |
| */ |
| void setFatalPreLoggingHook(std::function<void(void)> pre_fatal_hook) { |
| static std::mutex m; |
| std::lock_guard<std::mutex> lock(m); |
| g_fatal_pre_logging_hook = pre_fatal_hook; |
| } |
| |
| |
| |
| |
| // By default this function pointer goes to \ref pushFatalMessageToLogger; |
| std::function<void(FatalMessagePtr) > g_fatal_to_g3logworker_function_ptr = internal::pushFatalMessageToLogger; |
| |
| /** REPLACE fatalCallToLogger for fatalCallForUnitTest |
| * This function switches the function pointer so that only |
| * 'unitTest' mock-fatal calls are made. |
| * */ |
| void setFatalExitHandler(std::function<void(FatalMessagePtr) > fatal_call) { |
| g_fatal_to_g3logworker_function_ptr = fatal_call; |
| } |
| |
| |
| namespace internal { |
| |
| bool isLoggingInitialized() { |
| return g_logger_instance != nullptr; |
| } |
| |
| /** |
| * Shutdown the logging by making the pointer to the background logger to nullptr. The object is not deleted |
| * that is the responsibility of its owner. * |
| */ |
| void shutDownLogging() { |
| std::lock_guard<std::mutex> lock(g_logging_init_mutex); |
| g_logger_instance = nullptr; |
| |
| } |
| |
| /** Same as the Shutdown above but called by the destructor of the LogWorker, thus ensuring that no further |
| * LOG(...) calls can happen to a non-existing LogWorker. |
| * @param active MUST BE the LogWorker initialized for logging. If it is not then this call is just ignored |
| * and the logging continues to be active. |
| * @return true if the correct worker was given,. and shutDownLogging was called |
| */ |
| bool shutDownLoggingForActiveOnly(LogWorker *active) { |
| if (isLoggingInitialized() && nullptr != active && (active != g_logger_instance)) { |
| std::cerr << "\n\t\tAttempted to shut down logging, but the ID of the Logger is not the one that is active." |
| << "\n\t\tHaving multiple instances of the g3::LogWorker is likely a BUG" |
| << "\n\t\tEither way, this call to shutDownLogging was ignored" |
| << "\n\t\tTry g3::internal::shutDownLogging() instead"; |
| return false; |
| } |
| shutDownLogging(); |
| return true; |
| } |
| |
| |
| |
| |
| /** explicits copy of all input. This is makes it possibly to use g3log across dynamically loaded libraries |
| * i.e. (dlopen + dlsym) */ |
| void saveMessage(const char *entry, const char *file, int line, const char *function, const LEVELS &level, |
| const char *boolean_expression, int fatal_signal, const char *stack_trace) { |
| LEVELS msgLevel {level}; |
| LogMessagePtr message {std2::make_unique<LogMessage>(file, line, function, msgLevel)}; |
| message.get()->write().append(entry); |
| message.get()->setExpression(boolean_expression); |
| |
| |
| if (internal::wasFatal(level)) { |
| auto fatalhook = g_fatal_pre_logging_hook; |
| // In case the fatal_pre logging actually will cause a crash in its turn |
| // let's not do recursive crashing! |
| setFatalPreLoggingHook(g_pre_fatal_hook_that_does_nothing); |
| ++g_fatal_hook_recursive_counter; // thread safe counter |
| // "benign" race here. If two threads crashes, with recursive crashes |
| // then it's possible that the "other" fatal stack trace will be shown |
| // that's OK since it was anyhow the first crash detected |
| static const std::string first_stack_trace = stack_trace; |
| fatalhook(); |
| message.get()->write().append(stack_trace); |
| |
| if (g_fatal_hook_recursive_counter.load() > 1) { |
| message.get()->write() |
| .append("\n\n\nWARNING\n" |
| "A recursive crash detected. It is likely the hook set with 'setFatalPreLoggingHook(...)' is responsible\n\n") |
| .append("---First crash stacktrace: ").append(first_stack_trace).append("\n---End of first stacktrace\n"); |
| } |
| FatalMessagePtr fatal_message { std2::make_unique<FatalMessage>(*(message._move_only.get()), fatal_signal) }; |
| // At destruction, flushes fatal message to g3LogWorker |
| // either we will stay here until the background worker has received the fatal |
| // message, flushed the crash message to the sinks and exits with the same fatal signal |
| //..... OR it's in unit-test mode then we throw a std::runtime_error (and never hit sleep) |
| fatalCall(fatal_message); |
| } else { |
| pushMessageToLogger(message); |
| } |
| } |
| |
| /** |
| * save the message to the logger. In case of called before the logger is instantiated |
| * the first message will be saved. Any following subsequent unitnialized log calls |
| * will be ignored. |
| * |
| * The first initialized log entry will also save the first uninitialized log message, if any |
| * @param log_entry to save to logger |
| */ |
| void pushMessageToLogger(LogMessagePtr incoming) { // todo rename to Push SavedMessage To Worker |
| // Uninitialized messages are ignored but does not CHECK/crash the logger |
| if (!internal::isLoggingInitialized()) { |
| std::call_once(g_set_first_uninitialized_flag, [&] { |
| g_first_unintialized_msg = incoming.release(); |
| std::string err = {"LOGGER NOT INITIALIZED:\n\t\t"}; |
| err.append(g_first_unintialized_msg->message()); |
| std::string &str = g_first_unintialized_msg->write(); |
| str.clear(); |
| str.append(err); // replace content |
| std::cerr << str << std::endl; |
| }); |
| return; |
| } |
| |
| // logger is initialized |
| g_logger_instance->save(incoming); |
| } |
| |
| /** Fatal call saved to logger. This will trigger SIGABRT or other fatal signal |
| * to exit the program. After saving the fatal message the calling thread |
| * will sleep forever (i.e. until the background thread catches up, saves the fatal |
| * message and kills the software with the fatal signal. |
| */ |
| void pushFatalMessageToLogger(FatalMessagePtr message) { |
| if (!isLoggingInitialized()) { |
| std::ostringstream error; |
| error << "FATAL CALL but logger is NOT initialized\n" |
| << "CAUSE: " << message.get()->reason() |
| << "\nMessage: \n" << message.get()->toString() << std::flush; |
| std::cerr << error.str() << std::flush; |
| internal::exitWithDefaultSignalHandler(message.get()->_level, message.get()->_signal_id); |
| } |
| g_logger_instance->fatal(message); |
| while (shouldBlockForFatalHandling()) { |
| std::this_thread::sleep_for(std::chrono::seconds(1)); |
| } |
| } |
| |
| /** The default, initial, handling to send a 'fatal' event to g3logworker |
| * the caller will stay here, eternally, until the software is aborted |
| * ... in the case of unit testing it is the given "Mock" fatalCall that will |
| * define the behaviour. |
| */ |
| void fatalCall(FatalMessagePtr message) { |
| g_fatal_to_g3logworker_function_ptr(FatalMessagePtr {std::move(message)}); |
| } |
| |
| |
| } // internal |
| } // g3 |
| |
| |
| |