mirror of
https://github.com/KjellKod/g3log.git
synced 2024-12-13 18:45:12 +01:00
Merge pull request #48 from KjellKod/disable_single_signals
Custom signal handler
This commit is contained in:
commit
4974cf1da6
@ -147,7 +147,8 @@ namespace
|
||||
|
||||
|
||||
void SegFaultAttempt_x10000() NOEXCEPT {
|
||||
deathfunc f = []{Throw(); *(char*)0 = 0; char* ptr = 0; *ptr = 1; AccessViolation();};
|
||||
|
||||
deathfunc f = []{char* ptr = 0; *ptr = 1; };
|
||||
Death_x10000(f, "throw uncaught exception... and then some sigsegv calls");
|
||||
}
|
||||
|
||||
@ -292,7 +293,6 @@ int main(int argc, char **argv)
|
||||
auto handle= worker->addDefaultLogger(argv[0], path_to_log_file);
|
||||
g3::initializeLogging(worker.get());
|
||||
g3::setFatalPreLoggingHook(&breakHere);
|
||||
|
||||
std::future<std::string> log_file_name = handle->call(&g3::FileSink::fileName);
|
||||
|
||||
std::cout << "**** G3LOG FATAL EXAMPLE ***\n\n"
|
||||
|
@ -11,7 +11,7 @@
|
||||
#include "g3log/logcapture.hpp"
|
||||
#include "g3log/loglevels.hpp"
|
||||
|
||||
#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__) && !defined(__GNUC__)) // windows and not mingw
|
||||
#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__) && !defined(__GNUC__))
|
||||
#error "crashhandler_unix.cpp used but it's a windows system"
|
||||
#endif
|
||||
|
||||
@ -26,6 +26,8 @@
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
// Linux/Clang, OSX/Clang, OSX/gcc
|
||||
#if (defined(__clang__) || defined(__APPLE__))
|
||||
@ -36,6 +38,18 @@
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
const std::map<int, std::string> kSignals = {
|
||||
{SIGABRT, "SIGABRT"},
|
||||
{SIGFPE, "SIGFPE"},
|
||||
{SIGILL, "SIGILL"},
|
||||
{SIGSEGV, "SIGSEGV"},
|
||||
{SIGTERM, "SIGTERM"},
|
||||
};
|
||||
|
||||
std::map<int, std::string> gSignals = kSignals;
|
||||
|
||||
|
||||
bool shouldDoExit() {
|
||||
static std::atomic<uint64_t> firstExit{0};
|
||||
auto const count = firstExit.fetch_add(1, std::memory_order_relaxed);
|
||||
@ -43,7 +57,6 @@ namespace {
|
||||
}
|
||||
|
||||
void restoreSignalHandler(int signal_number) {
|
||||
std::cerr << "\n\n" << __FUNCTION__ << " " << signal_number << " threadID: " << std::this_thread::get_id() << std::endl;
|
||||
#if !(defined(DISABLE_FATAL_SIGNALHANDLING))
|
||||
struct sigaction action;
|
||||
memset(&action, 0, sizeof (action)); //
|
||||
@ -53,10 +66,11 @@ namespace {
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// Dump of stack,. then exit through g3log background worker
|
||||
// ALL thanks to this thread at StackOverflow. Pretty much borrowed from:
|
||||
// Ref: http://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashes
|
||||
void signalHandler(int signal_number, siginfo_t *info, void *unused_context) {
|
||||
void signalHandler(int signal_number, siginfo_t* info, void* unused_context) {
|
||||
|
||||
// Only one signal will be allowed past this point
|
||||
if (false == shouldDoExit()) {
|
||||
@ -78,6 +92,33 @@ namespace {
|
||||
} // message sent to g3LogWorker
|
||||
// wait to die
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Installs FATAL signal handler that is enough to handle most fatal events
|
||||
// on *NIX systems
|
||||
void installSignalHandler() {
|
||||
#if !(defined(DISABLE_FATAL_SIGNALHANDLING))
|
||||
struct sigaction action;
|
||||
memset(&action, 0, sizeof (action));
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_sigaction = &signalHandler; // callback to crashHandler for fatal signals
|
||||
// sigaction to use sa_sigaction file. ref: http://www.linuxprogrammingblog.com/code-examples/sigaction
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
|
||||
// do it verbose style - install all signal actions
|
||||
for (const auto& sig_pair : gSignals) {
|
||||
if (sigaction(sig_pair.first, &action, nullptr) < 0) {
|
||||
const std::string error = "sigaction - " + sig_pair.second;
|
||||
perror(error.c_str());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
|
||||
@ -108,22 +149,22 @@ namespace g3 {
|
||||
|
||||
/// Generate stackdump. Or in case a stackdump was pre-generated and non-empty just use that one
|
||||
/// i.e. the latter case is only for Windows and test purposes
|
||||
std::string stackdump(const char *rawdump) {
|
||||
std::string stackdump(const char* rawdump) {
|
||||
if (nullptr != rawdump && !std::string(rawdump).empty()) {
|
||||
return {rawdump};
|
||||
}
|
||||
|
||||
const size_t max_dump_size = 50;
|
||||
void *dump[max_dump_size];
|
||||
void* dump[max_dump_size];
|
||||
size_t size = backtrace(dump, max_dump_size);
|
||||
char **messages = backtrace_symbols(dump, size); // overwrite sigaction with caller's address
|
||||
char** messages = backtrace_symbols(dump, size); // overwrite sigaction with caller's address
|
||||
|
||||
// dump stack: skip first frame, since that is here
|
||||
std::ostringstream oss;
|
||||
for (size_t idx = 1; idx < size && messages != nullptr; ++idx) {
|
||||
char *mangled_name = 0, *offset_begin = 0, *offset_end = 0;
|
||||
char* mangled_name = 0, *offset_begin = 0, *offset_end = 0;
|
||||
// find parantheses and +address offset surrounding mangled name
|
||||
for (char *p = messages[idx]; *p; ++p) {
|
||||
for (char* p = messages[idx]; *p; ++p) {
|
||||
if (*p == '(') {
|
||||
mangled_name = p;
|
||||
} else if (*p == '+') {
|
||||
@ -142,7 +183,7 @@ namespace g3 {
|
||||
*offset_end++ = '\0';
|
||||
|
||||
int status;
|
||||
char *real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);
|
||||
char* real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);
|
||||
// if demangling is successful, output the demangled function name
|
||||
if (status == 0) {
|
||||
oss << "\n\tstack dump [" << idx << "] " << messages[idx] << " : " << real_name << "+";
|
||||
@ -165,7 +206,7 @@ namespace g3 {
|
||||
|
||||
|
||||
/// string representation of signal ID
|
||||
std::string exitReasonName(const LEVELS &level, g3::SignalType fatal_id) {
|
||||
std::string exitReasonName(const LEVELS& level, g3::SignalType fatal_id) {
|
||||
|
||||
int signal_number = static_cast<int>(fatal_id);
|
||||
switch (signal_number) {
|
||||
@ -188,27 +229,10 @@ namespace g3 {
|
||||
|
||||
|
||||
|
||||
// KJELL : TODO. The Fatal Message can contain a callback function that depending on OS and test scenario does
|
||||
// different things.
|
||||
// exitWithDefaultSignalHandler is called from g3logworke::bgFatal AFTER all the logging sinks have been cleared
|
||||
// I.e. saving a function that has the value already encapsulated within.
|
||||
// FatalMessagePtr msgPtr
|
||||
// Linux/OSX --> msgPtr.get()->ContinueWithFatalExit(); --> exitWithDefaultSignalHandler(int signal_number);
|
||||
// Windows ..... (if signal) --> exitWithDefaultSignalHandler(int signal_number);
|
||||
// (if exception) ....
|
||||
// the calling thread that is in a never-ending loop should break out of that loop
|
||||
// i.e. an atomic flag should be set
|
||||
// the next step should then be to re-throw the same exception
|
||||
// i.e. just call the next exception handler
|
||||
// we should make sure that 1) g3log exception handler is called BEFORE widows
|
||||
// it should continue and then be caught in Visual Studios exception handler
|
||||
//
|
||||
//
|
||||
|
||||
// Triggered by g3log->g3LogWorker after receiving a FATAL trigger
|
||||
// which is LOG(FATAL), CHECK(false) or a fatal signal our signalhandler caught.
|
||||
// --- If LOG(FATAL) or CHECK(false) the signal_number will be SIGABRT
|
||||
void exitWithDefaultSignalHandler(const LEVELS &level, g3::SignalType fatal_signal_id) {
|
||||
void exitWithDefaultSignalHandler(const LEVELS& level, g3::SignalType fatal_signal_id) {
|
||||
const int signal_number = static_cast<int>(fatal_signal_id);
|
||||
restoreSignalHandler(signal_number);
|
||||
std::cerr << "\n\n" << __FUNCTION__ << ":" << __LINE__ << ". Exiting due to " << level.text << ", " << signal_number << " \n\n" << std::flush;
|
||||
@ -220,38 +244,31 @@ namespace g3 {
|
||||
}
|
||||
} // end g3::internal
|
||||
|
||||
//
|
||||
// Installs FATAL signal handler that is enough to handle most fatal events
|
||||
// on *NIX systems
|
||||
void installSignalHandler() {
|
||||
#if !(defined(DISABLE_FATAL_SIGNALHANDLING))
|
||||
struct sigaction action;
|
||||
memset(&action, 0, sizeof (action));
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_sigaction = &signalHandler; // callback to crashHandler for fatal signals
|
||||
// sigaction to use sa_sigaction file. ref: http://www.linuxprogrammingblog.com/code-examples/sigaction
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
|
||||
// do it verbose style - install all signal actions
|
||||
if (sigaction(SIGABRT, &action, NULL) < 0)
|
||||
perror("sigaction - SIGABRT");
|
||||
if (sigaction(SIGFPE, &action, NULL) < 0)
|
||||
perror("sigaction - SIGFPE");
|
||||
if (sigaction(SIGILL, &action, NULL) < 0)
|
||||
perror("sigaction - SIGILL");
|
||||
if (sigaction(SIGSEGV, &action, NULL) < 0)
|
||||
perror("sigaction - SIGSEGV");
|
||||
if (sigaction(SIGTERM, &action, NULL) < 0)
|
||||
perror("sigaction - SIGTERM");
|
||||
#endif
|
||||
// This will override the default signal handler setup and instead
|
||||
// install a custom set of signals to handle
|
||||
void overrideSetupSignals(const std::map<int, std::string> overrideSignals) {
|
||||
static std::mutex signalLock;
|
||||
std::lock_guard<std::mutex> guard(signalLock);
|
||||
for (const auto& sig : gSignals) {
|
||||
restoreSignalHandler(sig.first);
|
||||
}
|
||||
|
||||
gSignals = overrideSignals;
|
||||
installCrashHandler(); // installs all the signal handling for gSignals
|
||||
}
|
||||
|
||||
// restores the signal handler back to default
|
||||
void restoreSignalHandlerToDefault() {
|
||||
overrideSetupSignals(kSignals);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// installs the signal handling for whatever signal set that is currently active
|
||||
// If you want to setup your own signal handling then
|
||||
// You should instead call overrideSetupSignals()
|
||||
void installCrashHandler() {
|
||||
installSignalHandler();
|
||||
} // namespace g3::internal
|
||||
|
||||
|
||||
}
|
||||
} // end namespace g3
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
* ============================================================================*/
|
||||
#include <string>
|
||||
#include <csignal>
|
||||
#include <map>
|
||||
#include "g3log/loglevels.hpp"
|
||||
#include "g3log/generated_definitions.hpp"
|
||||
|
||||
@ -37,6 +38,21 @@ namespace g3 {
|
||||
void installSignalHandlerForThread();
|
||||
#else
|
||||
typedef int SignalType;
|
||||
|
||||
/// Probably only needed for unit testing. Resets the signal handling back to default
|
||||
/// which might be needed in case it was previously overridden
|
||||
/// The default signals are: SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGTERM
|
||||
void restoreSignalHandlerToDefault();
|
||||
|
||||
/// Overrides the existing signal handling for custom signals
|
||||
/// For example: usage of zcmq relies on its own signal handler for SIGTERM
|
||||
/// so users of g3log with zcmq should then use the @ref overrideSetupSignals
|
||||
/// , likely with the original set of signals but with SIGTERM removed
|
||||
///
|
||||
/// call example:
|
||||
/// g3::overrideSetupSignals({ {SIGABRT, "SIGABRT"}, {SIGFPE, "SIGFPE"},{SIGILL, "SIGILL"},
|
||||
// {SIGSEGV, "SIGSEGV"},});
|
||||
void overrideSetupSignals(const std::map<int, std::string> overrideSignals);
|
||||
#endif
|
||||
|
||||
|
||||
|
@ -312,6 +312,57 @@ TEST(LogTest, LOGF__FATAL) {
|
||||
}
|
||||
|
||||
|
||||
TEST(LogTest, FatalSIGTERM__UsingDefaultHandler) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
g_fatal_counter.store(0);
|
||||
g3::setFatalPreLoggingHook(fatalCounter);
|
||||
raise(SIGTERM);
|
||||
logger.reset();
|
||||
EXPECT_EQ(g_fatal_counter.load(), size_t{1});
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::atomic<size_t> customFatalCounter = {0};
|
||||
std::atomic<int> lastEncounteredSignal = {0};
|
||||
void customSignalHandler(int signal_number, siginfo_t* info, void* unused_context) {
|
||||
lastEncounteredSignal.store(signal_number);
|
||||
++customFatalCounter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void installCustomSIGTERM () {
|
||||
struct sigaction action;
|
||||
memset(&action, 0, sizeof (action));
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_sigaction = &customSignalHandler;
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
sigaction(SIGTERM, &action, nullptr);
|
||||
}
|
||||
|
||||
} // anonymous
|
||||
|
||||
TEST(LogTest, FatalSIGTERM__UsingCustomHandler) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
g_fatal_counter.store(0);
|
||||
g3::setFatalPreLoggingHook(fatalCounter);
|
||||
installCustomSIGTERM();
|
||||
g3::overrideSetupSignals({ {SIGABRT, "SIGABRT"}, {SIGFPE, "SIGFPE"}, {SIGILL, "SIGILL"}});
|
||||
|
||||
installCustomSIGTERM();
|
||||
EXPECT_EQ(customFatalCounter, 0);
|
||||
EXPECT_EQ(lastEncounteredSignal.load(), 0);
|
||||
|
||||
|
||||
raise(SIGTERM);
|
||||
logger.reset();
|
||||
EXPECT_EQ(g_fatal_counter.load(), size_t{0});
|
||||
EXPECT_EQ(lastEncounteredSignal.load(), SIGTERM);
|
||||
EXPECT_EQ(customFatalCounter, 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
TEST(LogTest, LOG_preFatalLogging_hook) {
|
||||
{
|
||||
RestoreFileLogger logger(log_directory);
|
||||
|
Loading…
Reference in New Issue
Block a user