Ed Tanous | 50c50c2 | 2017-05-12 16:58:06 -0700 | [diff] [blame] | 1 | /** ========================================================================== |
| 2 | * 2011 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes |
| 3 | * with no warranties. This code is yours to share, use and modify with no |
| 4 | * strings attached and no restrictions or obligations. |
| 5 | * |
| 6 | * For more information see g3log/LICENSE or refer refer to http://unlicense.org |
| 7 | * ============================================================================ |
| 8 | * |
| 9 | * Filename:g3log.cpp Framework for Logging and Design By Contract |
| 10 | * Created: 2011 by Kjell Hedström |
| 11 | * |
| 12 | * PUBLIC DOMAIN and Not copywrited since it was built on public-domain software and at least in "spirit" influenced |
| 13 | * from the following sources |
| 14 | * 1. kjellkod.cc ;) |
| 15 | * 2. Dr.Dobbs, Petru Marginean: http://drdobbs.com/article/printableArticle.jhtml?articleId=201804215&dept_url=/cpp/ |
| 16 | * 3. Dr.Dobbs, Michael Schulze: http://drdobbs.com/article/printableArticle.jhtml?articleId=225700666&dept_url=/cpp/ |
| 17 | * 4. Google 'glog': http://google-glog.googlecode.com/svn/trunk/doc/glog.html |
| 18 | * 5. Various Q&A at StackOverflow |
| 19 | * ********************************************* */ |
| 20 | |
| 21 | #include "g3log/g3log.hpp" |
| 22 | #include "g3log/std2_make_unique.hpp" |
| 23 | #include "g3log/logworker.hpp" |
| 24 | #include "g3log/crashhandler.hpp" |
| 25 | #include "g3log/logmessage.hpp" |
| 26 | #include "g3log/loglevels.hpp" |
| 27 | |
| 28 | |
| 29 | #include <mutex> |
| 30 | #include <memory> |
| 31 | #include <iostream> |
| 32 | #include <thread> |
| 33 | #include <atomic> |
| 34 | #include <cstdlib> |
| 35 | #include <sstream> |
| 36 | |
| 37 | namespace { |
| 38 | std::once_flag g_initialize_flag; |
| 39 | g3::LogWorker *g_logger_instance = nullptr; // instantiated and OWNED somewhere else (main) |
| 40 | std::mutex g_logging_init_mutex; |
| 41 | |
| 42 | std::unique_ptr<g3::LogMessage> g_first_unintialized_msg = {nullptr}; |
| 43 | std::once_flag g_set_first_uninitialized_flag; |
| 44 | std::once_flag g_save_first_unintialized_flag; |
| 45 | const std::function<void(void)> g_pre_fatal_hook_that_does_nothing = [] { /*does nothing */}; |
| 46 | std::function<void(void)> g_fatal_pre_logging_hook; |
| 47 | |
| 48 | |
| 49 | std::atomic<size_t> g_fatal_hook_recursive_counter = {0}; |
| 50 | } |
| 51 | |
| 52 | |
| 53 | |
| 54 | |
| 55 | |
| 56 | namespace g3 { |
| 57 | // signalhandler and internal clock is only needed to install once |
| 58 | // for unit testing purposes the initializeLogging might be called |
| 59 | // several times... |
| 60 | // for all other practical use, it shouldn't! |
| 61 | |
| 62 | void initializeLogging(LogWorker *bgworker) { |
| 63 | std::call_once(g_initialize_flag, [] { |
| 64 | installCrashHandler(); |
| 65 | }); |
| 66 | std::lock_guard<std::mutex> lock(g_logging_init_mutex); |
| 67 | if (internal::isLoggingInitialized() || nullptr == bgworker) { |
| 68 | std::ostringstream exitMsg; |
| 69 | exitMsg << __FILE__ "->" << __FUNCTION__ << ":" << __LINE__ << std::endl; |
| 70 | exitMsg << "\tFatal exit due to illegal initialization of g3::LogWorker\n"; |
| 71 | exitMsg << "\t(due to multiple initializations? : " << std::boolalpha << internal::isLoggingInitialized(); |
| 72 | exitMsg << ", due to nullptr == bgworker? : " << std::boolalpha << (nullptr == bgworker) << ")"; |
| 73 | std::cerr << exitMsg.str() << std::endl; |
| 74 | std::exit(EXIT_FAILURE); |
| 75 | } |
| 76 | |
| 77 | // Save the first uninitialized message, if any |
| 78 | std::call_once(g_save_first_unintialized_flag, [&bgworker] { |
| 79 | if (g_first_unintialized_msg) { |
| 80 | bgworker->save(LogMessagePtr {std::move(g_first_unintialized_msg)}); |
| 81 | } |
| 82 | }); |
| 83 | |
| 84 | g_logger_instance = bgworker; |
| 85 | // by default the pre fatal logging hook does nothing |
| 86 | // if it WOULD do something it would happen in |
| 87 | setFatalPreLoggingHook(g_pre_fatal_hook_that_does_nothing); |
| 88 | // recurvise crash counter re-set to zero |
| 89 | g_fatal_hook_recursive_counter.store(0); |
| 90 | } |
| 91 | |
| 92 | |
| 93 | /** |
| 94 | * default does nothing, @ref ::g_pre_fatal_hook_that_does_nothing |
| 95 | * It will be called just before sending the fatal message, @ref pushFatalmessageToLogger |
| 96 | * It will be reset to do nothing in ::initializeLogging(...) |
| 97 | * so please call this function, if you ever need to, after initializeLogging(...) |
| 98 | */ |
| 99 | void setFatalPreLoggingHook(std::function<void(void)> pre_fatal_hook) { |
| 100 | static std::mutex m; |
| 101 | std::lock_guard<std::mutex> lock(m); |
| 102 | g_fatal_pre_logging_hook = pre_fatal_hook; |
| 103 | } |
| 104 | |
| 105 | |
| 106 | |
| 107 | |
| 108 | // By default this function pointer goes to \ref pushFatalMessageToLogger; |
| 109 | std::function<void(FatalMessagePtr) > g_fatal_to_g3logworker_function_ptr = internal::pushFatalMessageToLogger; |
| 110 | |
| 111 | /** REPLACE fatalCallToLogger for fatalCallForUnitTest |
| 112 | * This function switches the function pointer so that only |
| 113 | * 'unitTest' mock-fatal calls are made. |
| 114 | * */ |
| 115 | void setFatalExitHandler(std::function<void(FatalMessagePtr) > fatal_call) { |
| 116 | g_fatal_to_g3logworker_function_ptr = fatal_call; |
| 117 | } |
| 118 | |
| 119 | |
| 120 | namespace internal { |
| 121 | |
| 122 | bool isLoggingInitialized() { |
| 123 | return g_logger_instance != nullptr; |
| 124 | } |
| 125 | |
| 126 | /** |
| 127 | * Shutdown the logging by making the pointer to the background logger to nullptr. The object is not deleted |
| 128 | * that is the responsibility of its owner. * |
| 129 | */ |
| 130 | void shutDownLogging() { |
| 131 | std::lock_guard<std::mutex> lock(g_logging_init_mutex); |
| 132 | g_logger_instance = nullptr; |
| 133 | |
| 134 | } |
| 135 | |
| 136 | /** Same as the Shutdown above but called by the destructor of the LogWorker, thus ensuring that no further |
| 137 | * LOG(...) calls can happen to a non-existing LogWorker. |
| 138 | * @param active MUST BE the LogWorker initialized for logging. If it is not then this call is just ignored |
| 139 | * and the logging continues to be active. |
| 140 | * @return true if the correct worker was given,. and shutDownLogging was called |
| 141 | */ |
| 142 | bool shutDownLoggingForActiveOnly(LogWorker *active) { |
| 143 | if (isLoggingInitialized() && nullptr != active && (active != g_logger_instance)) { |
| 144 | LOG(WARNING) << "\n\t\tAttempted to shut down logging, but the ID of the Logger is not the one that is active." |
| 145 | << "\n\t\tHaving multiple instances of the g3::LogWorker is likely a BUG" |
| 146 | << "\n\t\tEither way, this call to shutDownLogging was ignored" |
| 147 | << "\n\t\tTry g3::internal::shutDownLogging() instead"; |
| 148 | return false; |
| 149 | } |
| 150 | shutDownLogging(); |
| 151 | return true; |
| 152 | } |
| 153 | |
| 154 | |
| 155 | |
| 156 | |
| 157 | /** explicits copy of all input. This is makes it possibly to use g3log across dynamically loaded libraries |
| 158 | * i.e. (dlopen + dlsym) */ |
| 159 | void saveMessage(const char *entry, const char *file, int line, const char *function, const LEVELS &level, |
| 160 | const char *boolean_expression, int fatal_signal, const char *stack_trace) { |
| 161 | LEVELS msgLevel {level}; |
| 162 | LogMessagePtr message {std2::make_unique<LogMessage>(file, line, function, msgLevel)}; |
| 163 | message.get()->write().append(entry); |
| 164 | message.get()->setExpression(boolean_expression); |
| 165 | |
| 166 | |
| 167 | if (internal::wasFatal(level)) { |
| 168 | auto fatalhook = g_fatal_pre_logging_hook; |
| 169 | // In case the fatal_pre logging actually will cause a crash in its turn |
| 170 | // let's not do recursive crashing! |
| 171 | setFatalPreLoggingHook(g_pre_fatal_hook_that_does_nothing); |
| 172 | ++g_fatal_hook_recursive_counter; // thread safe counter |
| 173 | // "benign" race here. If two threads crashes, with recursive crashes |
| 174 | // then it's possible that the "other" fatal stack trace will be shown |
| 175 | // that's OK since it was anyhow the first crash detected |
| 176 | static const std::string first_stack_trace = stack_trace; |
| 177 | fatalhook(); |
| 178 | message.get()->write().append(stack_trace); |
| 179 | |
| 180 | if (g_fatal_hook_recursive_counter.load() > 1) { |
| 181 | message.get()->write() |
| 182 | .append("\n\n\nWARNING\n" |
| 183 | "A recursive crash detected. It is likely the hook set with 'setFatalPreLoggingHook(...)' is responsible\n\n") |
| 184 | .append("---First crash stacktrace: ").append(first_stack_trace).append("\n---End of first stacktrace\n"); |
| 185 | } |
| 186 | FatalMessagePtr fatal_message { std2::make_unique<FatalMessage>(*(message._move_only.get()), fatal_signal) }; |
| 187 | // At destruction, flushes fatal message to g3LogWorker |
| 188 | // either we will stay here until the background worker has received the fatal |
| 189 | // message, flushed the crash message to the sinks and exits with the same fatal signal |
| 190 | //..... OR it's in unit-test mode then we throw a std::runtime_error (and never hit sleep) |
| 191 | fatalCall(fatal_message); |
| 192 | } else { |
| 193 | pushMessageToLogger(message); |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | /** |
| 198 | * save the message to the logger. In case of called before the logger is instantiated |
| 199 | * the first message will be saved. Any following subsequent unitnialized log calls |
| 200 | * will be ignored. |
| 201 | * |
| 202 | * The first initialized log entry will also save the first uninitialized log message, if any |
| 203 | * @param log_entry to save to logger |
| 204 | */ |
| 205 | void pushMessageToLogger(LogMessagePtr incoming) { // todo rename to Push SavedMessage To Worker |
| 206 | // Uninitialized messages are ignored but does not CHECK/crash the logger |
| 207 | if (!internal::isLoggingInitialized()) { |
| 208 | std::call_once(g_set_first_uninitialized_flag, [&] { |
| 209 | g_first_unintialized_msg = incoming.release(); |
| 210 | std::string err = {"LOGGER NOT INITIALIZED:\n\t\t"}; |
| 211 | err.append(g_first_unintialized_msg->message()); |
| 212 | std::string &str = g_first_unintialized_msg->write(); |
| 213 | str.clear(); |
| 214 | str.append(err); // replace content |
| 215 | std::cerr << str << std::endl; |
| 216 | }); |
| 217 | return; |
| 218 | } |
| 219 | |
| 220 | // logger is initialized |
| 221 | g_logger_instance->save(incoming); |
| 222 | } |
| 223 | |
| 224 | /** Fatal call saved to logger. This will trigger SIGABRT or other fatal signal |
| 225 | * to exit the program. After saving the fatal message the calling thread |
| 226 | * will sleep forever (i.e. until the background thread catches up, saves the fatal |
| 227 | * message and kills the software with the fatal signal. |
| 228 | */ |
| 229 | void pushFatalMessageToLogger(FatalMessagePtr message) { |
| 230 | if (!isLoggingInitialized()) { |
| 231 | std::ostringstream error; |
| 232 | error << "FATAL CALL but logger is NOT initialized\n" |
| 233 | << "CAUSE: " << message.get()->reason() |
| 234 | << "\nMessage: \n" << message.get()->toString() << std::flush; |
| 235 | std::cerr << error.str() << std::flush; |
| 236 | internal::exitWithDefaultSignalHandler(message.get()->_level, message.get()->_signal_id); |
| 237 | } |
| 238 | g_logger_instance->fatal(message); |
| 239 | while (shouldBlockForFatalHandling()) { |
| 240 | std::this_thread::sleep_for(std::chrono::seconds(1)); |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | /** The default, initial, handling to send a 'fatal' event to g3logworker |
| 245 | * the caller will stay here, eternally, until the software is aborted |
| 246 | * ... in the case of unit testing it is the given "Mock" fatalCall that will |
| 247 | * define the behaviour. |
| 248 | */ |
| 249 | void fatalCall(FatalMessagePtr message) { |
| 250 | g_fatal_to_g3logworker_function_ptr(FatalMessagePtr {std::move(message)}); |
| 251 | } |
| 252 | |
| 253 | |
| 254 | } // internal |
| 255 | } // g3 |
| 256 | |
| 257 | |
| 258 | |