Merge pull request #48 from KjellKod/disable_single_signals

Custom signal handler
This commit is contained in:
Kjell Hedstrom 2015-09-11 07:03:38 -06:00
commit 4974cf1da6
5 changed files with 159 additions and 75 deletions

View File

@ -2,11 +2,11 @@
# 2010 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
# ============================================================================*/
# Below are details for compiling on Windows and Linux
# by default only an example g3log binary is created
# the performance and unit tests creation can be enabled by switching their

View File

@ -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"

View File

@ -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,93 +206,69 @@ 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) {
case SIGABRT: return "SIGABRT";
break;
case SIGFPE: return "SIGFPE";
break;
case SIGSEGV: return "SIGSEGV";
break;
case SIGILL: return "SIGILL";
break;
case SIGTERM: return "SIGTERM";
break;
default:
std::ostringstream oss;
oss << "UNKNOWN SIGNAL(" << signal_number << ") for " << level.text;
return oss.str();
case SIGABRT: return "SIGABRT";
break;
case SIGFPE: return "SIGFPE";
break;
case SIGSEGV: return "SIGSEGV";
break;
case SIGILL: return "SIGILL";
break;
case SIGTERM: return "SIGTERM";
break;
default:
std::ostringstream oss;
oss << "UNKNOWN SIGNAL(" << signal_number << ") for " << level.text;
return oss.str();
}
}
// 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;
kill(getpid(), signal_number);
exit(signal_number);
exit(signal_number);
}
} // 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

View File

@ -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

View File

@ -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);