// base/kaldi-error.cc // Copyright 2019 LAIX (Yi Sun) // Copyright 2019 SmartAction LLC (kkm) // Copyright 2016 Brno University of Technology (author: Karel Vesely) // Copyright 2009-2011 Microsoft Corporation; Lukas Burget; Ondrej Glembek // See ../../COPYING for clarification regarding multiple authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED // WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, // MERCHANTABLITY OR NON-INFRINGEMENT. // See the Apache 2 License for the specific language governing permissions and // limitations under the License. #ifdef HAVE_EXECINFO_H #include // To get stack trace in error messages. // If this #include fails there is an error in the Makefile, it does not // support your platform well. Make sure HAVE_EXECINFO_H is undefined, // and the code will compile. #ifdef HAVE_CXXABI_H #include // For name demangling. // Useful to decode the stack trace, but only used if we have execinfo.h #endif // HAVE_CXXABI_H #endif // HAVE_EXECINFO_H #include "base/kaldi-common.h" #include "base/kaldi-error.h" #include "base/version.h" namespace kaldi { /***** GLOBAL VARIABLES FOR LOGGING *****/ int32 g_kaldi_verbose_level = 0; static std::string program_name; static LogHandler log_handler = NULL; void SetProgramName(const char *basename) { // Using the 'static std::string' for the program name is mostly harmless, // because (a) Kaldi logging is undefined before main(), and (b) no stdc++ // string implementation has been found in the wild that would not be just // an empty string when zero-initialized but not yet constructed. program_name = basename; } /***** HELPER FUNCTIONS *****/ // Trim filename to at most 1 trailing directory long. Given a filename like // "/a/b/c/d/e/f.cc", return "e/f.cc". Support both '/' and '\' as the path // separator. static const char *GetShortFileName(const char *path) { if (path == nullptr) return ""; const char *prev = path, *last = path; while ((path = std::strpbrk(path, "\\/")) != nullptr) { ++path; prev = last; last = path; } return prev; } /***** STACK TRACE *****/ namespace internal { bool LocateSymbolRange(const std::string &trace_name, size_t *begin, size_t *end) { // Find the first '_' with leading ' ' or '('. *begin = std::string::npos; for (size_t i = 1; i < trace_name.size(); i++) { if (trace_name[i] != '_') { continue; } if (trace_name[i - 1] == ' ' || trace_name[i - 1] == '(') { *begin = i; break; } } if (*begin == std::string::npos) { return false; } *end = trace_name.find_first_of(" +", *begin); return *end != std::string::npos; } } // namespace internal #ifdef HAVE_EXECINFO_H static std::string Demangle(std::string trace_name) { #ifndef HAVE_CXXABI_H return trace_name; #else // HAVE_CXXABI_H // Try demangle the symbol. We are trying to support the following formats // produced by different platforms: // // Linux: // ./kaldi-error-test(_ZN5kaldi13UnitTestErrorEv+0xb) [0x804965d] // // Mac: // 0 server 0x000000010f67614d _ZNK5kaldi13MessageLogger10LogMessageEv + 813 // // We want to extract the name e.g., '_ZN5kaldi13UnitTestErrorEv' and // demangle it info a readable name like kaldi::UnitTextError. size_t begin, end; if (!internal::LocateSymbolRange(trace_name, &begin, &end)) { return trace_name; } std::string symbol = trace_name.substr(begin, end - begin); int status; char *demangled_name = abi::__cxa_demangle(symbol.c_str(), 0, 0, &status); if (status == 0 && demangled_name != nullptr) { symbol = demangled_name; free(demangled_name); } return trace_name.substr(0, begin) + symbol + trace_name.substr(end, std::string::npos); #endif // HAVE_CXXABI_H } #endif // HAVE_EXECINFO_H static std::string KaldiGetStackTrace() { std::string ans; #ifdef HAVE_EXECINFO_H const size_t KALDI_MAX_TRACE_SIZE = 50; const size_t KALDI_MAX_TRACE_PRINT = 50; // Must be even. // Buffer for the trace. void *trace[KALDI_MAX_TRACE_SIZE]; // Get the trace. size_t size = backtrace(trace, KALDI_MAX_TRACE_SIZE); // Get the trace symbols. char **trace_symbol = backtrace_symbols(trace, size); if (trace_symbol == NULL) return ans; // Compose a human-readable backtrace string. ans += "[ Stack-Trace: ]\n"; if (size <= KALDI_MAX_TRACE_PRINT) { for (size_t i = 0; i < size; i++) { ans += Demangle(trace_symbol[i]) + "\n"; } } else { // Print out first+last (e.g.) 5. for (size_t i = 0; i < KALDI_MAX_TRACE_PRINT / 2; i++) { ans += Demangle(trace_symbol[i]) + "\n"; } ans += ".\n.\n.\n"; for (size_t i = size - KALDI_MAX_TRACE_PRINT / 2; i < size; i++) { ans += Demangle(trace_symbol[i]) + "\n"; } if (size == KALDI_MAX_TRACE_SIZE) ans += ".\n.\n.\n"; // Stack was too long, probably a bug. } // We must free the array of pointers allocated by backtrace_symbols(), // but not the strings themselves. free(trace_symbol); #endif // HAVE_EXECINFO_H return ans; } /***** KALDI LOGGING *****/ MessageLogger::MessageLogger(LogMessageEnvelope::Severity severity, const char *func, const char *file, int32 line) { // Obviously, we assume the strings survive the destruction of this object. envelope_.severity = severity; envelope_.func = func; envelope_.file = GetShortFileName(file); // Points inside 'file'. envelope_.line = line; } void MessageLogger::LogMessage() const { // Send to the logging handler if provided. if (log_handler != NULL) { log_handler(envelope_, GetMessage().c_str()); return; } // Otherwise, use the default Kaldi logging. // Build the log-message header. std::stringstream full_message; if (envelope_.severity > LogMessageEnvelope::kInfo) { full_message << "VLOG[" << envelope_.severity << "] ("; } else { switch (envelope_.severity) { case LogMessageEnvelope::kInfo: full_message << "LOG ("; break; case LogMessageEnvelope::kWarning: full_message << "WARNING ("; break; case LogMessageEnvelope::kAssertFailed: full_message << "ASSERTION_FAILED ("; break; case LogMessageEnvelope::kError: default: // If not the ERROR, it still an error! full_message << "ERROR ("; break; } } // Add other info from the envelope and the message text. full_message << program_name.c_str() << "[" KALDI_VERSION "]" << ':' << envelope_.func << "():" << envelope_.file << ':' << envelope_.line << ") " << GetMessage().c_str(); // Add stack trace for errors and assertion failures, if available. if (envelope_.severity < LogMessageEnvelope::kWarning) { const std::string &stack_trace = KaldiGetStackTrace(); if (!stack_trace.empty()) { full_message << "\n\n" << stack_trace; } } // Print the complete message to stderr. full_message << "\n"; std::cerr << full_message.str(); } /***** KALDI ASSERTS *****/ void KaldiAssertFailure_(const char *func, const char *file, int32 line, const char *cond_str) { MessageLogger::Log() = MessageLogger(LogMessageEnvelope::kAssertFailed, func, file, line) << "Assertion failed: (" << cond_str << ")"; fflush(NULL); // Flush all pending buffers, abort() may not flush stderr. std::abort(); } /***** THIRD-PARTY LOG-HANDLER *****/ LogHandler SetLogHandler(LogHandler handler) { LogHandler old_handler = log_handler; log_handler = handler; return old_handler; } } // namespace kaldi