1
0
Fork 0
arangodb/3rdParty/iresearch/core/utils/log.cpp

813 lines
25 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2016 by EMC Corporation, All Rights Reserved
///
/// 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
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is EMC Corporation
///
/// @author Andrey Abramov
/// @author Vasiliy Nabatchikov
////////////////////////////////////////////////////////////////////////////////
#include <memory>
#include <mutex>
#if defined(_MSC_VER)
#include <Windows.h> // must be included before DbgHelp.h
#include <Psapi.h>
#include <DbgHelp.h>
#include <stdio.h> // for *printf(...)
#else
#include <cstdio> // for *printf(...)
#include <thread>
#include <cxxabi.h> // for abi::__cxa_demangle(...)
#include <dlfcn.h> // for dladdr(...)
#include <execinfo.h>
#include <unistd.h> // for STDIN_FILENO/STDOUT_FILENO/STDERR_FILENO
#include <sys/wait.h> // for waitpid(...)
#endif
#if defined(__APPLE__)
#include <libproc.h> // for proc_pidpath(...)
#endif
#if defined(USE_LIBBFD)
#include <bfd.h>
#endif
#if defined(USE_LIBUNWIND)
#include <libunwind.h>
#endif
#include "file_utils.hpp"
#include "shared.hpp"
#include "singleton.hpp"
#include "thread_utils.hpp"
#include "misc.hpp"
#include "log.hpp"
NS_LOCAL
MSVC_ONLY(__pragma(warning(push)))
MSVC_ONLY(__pragma(warning(disable: 4996))) // the compiler encountered a deprecated declaration
static FILE* dev_null() {
static auto dev_null = file_open(static_cast<file_path_t>(nullptr), "wb");
return dev_null.get();
}
MSVC_ONLY(__pragma(warning(pop)))
class file_streambuf: public std::streambuf {
public:
typedef std::streambuf::char_type char_type;
typedef std::streambuf::int_type int_type;
file_streambuf(FILE* out): out_(out ? out : dev_null()) {
}
file_streambuf& operator=(FILE* out) {
out_ = out ? out : dev_null();
return *this;
}
virtual std::streamsize xsputn(const char_type* data, std::streamsize size) override {
return std::fwrite(data, sizeof(char_type), size, out_);
}
virtual int_type overflow(int_type ch) override {
return std::fputc(ch, out_);
}
private:
FILE* out_;
};
class logger_ctx: public iresearch::singleton<logger_ctx> {
public:
logger_ctx()
: singleton(),
stack_trace_level_(irs::logger::level_t::IRL_DEBUG) {
// set everything up to and including INFO to stderr
for (size_t i = 0, last = iresearch::logger::IRL_INFO; i <= last; ++i) {
out_[i].file_ = stderr;
out_[i].streambuf_ = stderr;
}
}
bool enabled(iresearch::logger::level_t level) const { return dev_null() != out_[level].file_; }
FILE* file(iresearch::logger::level_t level) const { return out_[level].file_; }
logger_ctx& output(iresearch::logger::level_t level, FILE* out) {
out_[level].file_ = out ? out : dev_null();
out_[level].streambuf_ = out;
return *this;
}
logger_ctx& output_le(iresearch::logger::level_t level, FILE* out) {
for (size_t i = 0, count = IRESEARCH_COUNTOF(out_); i < count; ++i) {
output(static_cast<iresearch::logger::level_t>(i), i > level ? nullptr : out);
}
return *this;
}
irs::logger::level_t stack_trace_level() { return stack_trace_level_; }
logger_ctx& stack_trace_level(irs::logger::level_t level) {
stack_trace_level_ = level;
return *this;
}
std::ostream& stream(iresearch::logger::level_t level) { return out_[level].stream_; }
private:
struct level_ctx_t {
FILE* file_;
std::ostream stream_;
file_streambuf streambuf_;
level_ctx_t(): file_(dev_null()), stream_(&streambuf_), streambuf_(nullptr) {}
};
level_ctx_t out_[iresearch::logger::IRL_TRACE + 1]; // IRL_TRACE is the last value, +1 for 0'th id
irs::logger::level_t stack_trace_level_;
};
typedef std::function<void(const char* file, size_t line, const char* fn)> bfd_callback_type_t;
bool file_line_bfd(const bfd_callback_type_t& callback, const char* obj, void* addr); // predeclaration
bool stack_trace_libunwind(iresearch::logger::level_t level); // predeclaration
#if defined(_MSC_VER)
DWORD stack_trace_win32(iresearch::logger::level_t level, struct _EXCEPTION_POINTERS* ex) {
auto* out = output(level);
static std::mutex mutex;
SCOPED_LOCK(mutex); // win32 stack trace API is not thread safe
if (!ex || !ex->ContextRecord) {
std::fprintf(out, "No stack_trace available\n");
std::fflush(out);
return EXCEPTION_EXECUTE_HANDLER;
}
CONTEXT ctx = *(ex->ContextRecord); // create a modifiable copy
STACKFRAME frame = { 0 };
frame.AddrPC.Offset = ctx.Rip;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = ctx.Rbp;
frame.AddrFrame.Mode = AddrModeFlat;
auto process = GetCurrentProcess();
// SYMOPT_DEFERRED_LOADS required to avoid win32 GUI exception
//SymSetOptions(SymGetOptions() | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);
// always load symbols
SymInitialize(process, NULL, TRUE);
HMODULE module_handle;
DWORD module_handle_size;
if (!EnumProcessModules(process, &module_handle, sizeof(HMODULE), &module_handle_size)) {
std::fprintf(out, "Failed to enumerate modules for current process\n");
std::fflush(out);
return EXCEPTION_EXECUTE_HANDLER;
}
MODULEINFO module_info;
GetModuleInformation(process, module_handle, &module_info, sizeof(MODULEINFO));
auto image_type = ImageNtHeader(module_info.lpBaseOfDll)->FileHeader.Machine;
auto thread = GetCurrentThread();
char symbol_buf[sizeof(IMAGEHLP_SYMBOL) + 256];
auto& symbol = reinterpret_cast<IMAGEHLP_SYMBOL&>(symbol_buf);
symbol.SizeOfStruct = sizeof(IMAGEHLP_SYMBOL);
symbol.MaxNameLength = 256;
IMAGEHLP_LINE line = { 0 };
line.SizeOfStruct = sizeof(IMAGEHLP_LINE);
IMAGEHLP_MODULE module = { 0 };
module.SizeOfStruct = sizeof(IMAGEHLP_MODULE);
DWORD offset_from_line = 0;
DWORD64 offset_from_symbol = 0;
bool skip_frame = true;
size_t frame_count = size_t(-1);
while (StackWalk64(image_type, process, thread, &frame, &ctx, NULL, NULL, NULL, NULL)) {
static const std::string stack_trace_fn_symbol("iresearch::logger::stack_trace");
auto has_module = SymGetModuleInfo(process, frame.AddrPC.Offset, &module);
auto has_symbol = SymGetSymFromAddr(process, frame.AddrPC.Offset, &offset_from_symbol, &symbol);
auto has_line = SymGetLineFromAddr(process, frame.AddrPC.Offset, &offset_from_line, &line);
// skip frames until stack_trace entry point is encountered
if (skip_frame) {
if (has_symbol && stack_trace_fn_symbol == symbol.Name) {
skip_frame = false; // next frame is the start of the exception stack trace
}
continue;
}
std::fprintf(out, "#%Iu ", ++frame_count);
if (has_module) {
std::fprintf(out, "%s", module.ModuleName);
}
if (has_symbol) {
std::fprintf(out, "(%s+0x%Ix)", symbol.Name, offset_from_symbol);
}
if (has_line) {
std::fprintf(out, ": %s:%u+0x%x", line.FileName, line.LineNumber, offset_from_line);
}
std::fprintf(out, "\n");
}
if (skip_frame) {
std::fprintf(out, "No stack_trace available outside of logger\n");
}
std::fflush(out);
return EXCEPTION_EXECUTE_HANDLER;
}
#else
#if defined(__APPLE__)
bool file_line_addr2line(iresearch::logger::level_t level, const char* obj, const char* addr) {
auto fd = fileno(output(level));
auto pid = fork();
if (!pid) {
char name_buf[PROC_PIDPATHINFO_MAXSIZE]; // buffer size requirement for proc_pidpath(...)
auto ppid = getppid();
if (0 < proc_pidpath(ppid, name_buf, sizeof(name_buf))) {
// The exec() family of functions replaces the current process image with a new process image.
// The exec() functions only return if an error has occurred.
dup2(fd, 1); // redirect stdout to fd
dup2(fd, 2); // redirect stderr to fd
execlp("addr2line", "addr2line", "-e", obj, addr, NULL);
}
exit(1);
}
int status;
return 0 < waitpid(pid, &status, 0) && !WEXITSTATUS(status);
}
#else
bool file_line_addr2line(iresearch::logger::level_t level, const char* obj, const char* addr) {
auto fd = fileno(output(level));
auto pid = fork();
if (!pid) {
CONSTEXPR const size_t pid_size = sizeof(pid_t)*3 + 1; // aproximately 3 chars per byte +1 for \0
CONSTEXPR const char PROC_EXE[] = { "/proc//exe" };
CONSTEXPR const size_t name_size = IRESEARCH_COUNTOF(PROC_EXE) + pid_size + 1; // +1 for \0
char pid_buf[pid_size];
char name_buf[name_size];
auto ppid = getppid();
snprintf(pid_buf, pid_size, "%d", ppid);
snprintf(name_buf, name_size, "/proc/%d/exe", ppid);
// The exec() family of functions replaces the current process image with a new process image.
// The exec() functions only return if an error has occurred.
dup2(fd, 1); // redirect stdout to fd
dup2(fd, 2); // redirect stderr to fd
execlp("addr2line", "addr2line", "-e", obj, addr, nullptr);
exit(1);
}
int status;
return 0 < waitpid(pid, &status, 0) && !WEXITSTATUS(status);
}
#endif
bool file_line_bfd(const bfd_callback_type_t& callback, const char* obj, const char* addr) {
char* suffix;
auto address = std::strtoll(addr, &suffix, 0);
// negative addresses or parse errors are deemed invalid
return address < 0 || suffix ? false : file_line_bfd(callback, obj, (void*)address);
}
std::shared_ptr<char> proc_name_demangle(const char* symbol) {
int status;
// abi::__cxa_demangle(...) expects 'output_buffer' to be malloc()ed and does realloc()/free() internally
std::shared_ptr<char> buf(abi::__cxa_demangle(symbol, nullptr, nullptr, &status), std::free);
return buf && !status ? std::move(buf) : nullptr;
}
#if defined(__APPLE__)
bool stack_trace_gdb(iresearch::logger::level_t level) {
auto fd = fileno(output(level));
auto pid = fork();
if (!pid) {
CONSTEXPR const size_t pid_size = sizeof(pid_t)*3 + 1; // approximately 3 chars per byte +1 for \0
char pid_buf[pid_size];
char name_buf[PROC_PIDPATHINFO_MAXSIZE]; // buffer size requirement for proc_pidpath(...)
auto ppid = getppid();
snprintf(pid_buf, pid_size, "%d", ppid);
if (0 < proc_pidpath(ppid, name_buf, sizeof(name_buf))) {
// The exec() family of functions replaces the current process image with a new process image.
// The exec() functions only return if an error has occurred.
dup2(fd, 1); // redirect stdout to fd
dup2(fd, 2); // redirect stderr to fd
execlp("gdb", "gdb", "-n", "-nx", "-return-child-result", "-batch", "-ex", "thread", "-ex", "bt", name_buf, pid_buf, NULL);
}
exit(1);
}
int status;
return 0 < waitpid(pid, &status, 0) && !WEXITSTATUS(status);
}
#else
bool stack_trace_gdb(iresearch::logger::level_t level) {
auto fd = fileno(output(level));
auto pid = fork();
if (!pid) {
CONSTEXPR const size_t pid_size = sizeof(pid_t)*3 + 1; // approximately 3 chars per byte +1 for \0
CONSTEXPR const char PROC_EXE[] = { "/proc//exe" };
CONSTEXPR const size_t name_size = IRESEARCH_COUNTOF(PROC_EXE) + pid_size + 1; // +1 for \0
char pid_buf[pid_size];
char name_buf[name_size];
auto ppid = getppid();
snprintf(pid_buf, pid_size, "%d", ppid);
snprintf(name_buf, name_size, "/proc/%d/exe", ppid);
// The exec() family of functions replaces the current process image with a new process image.
// The exec() functions only return if an error has occurred.
dup2(fd, 1); // redirect stdout to fd
dup2(fd, 2); // redirect stderr to fd
execlp("gdb", "gdb", "-n", "-nx", "-return-child-result", "-batch", "-ex", "thread", "-ex", "bt", name_buf, pid_buf, nullptr);
exit(1);
}
int status;
return 0 < waitpid(pid, &status, 0) && !WEXITSTATUS(status);
}
#endif
void stack_trace_posix(iresearch::logger::level_t level) {
auto* out = output(level);
CONSTEXPR const size_t frames_max = 128; // arbitrary size
void* frames_buf[frames_max];
auto frames_count = backtrace(frames_buf, frames_max);
if (frames_count < 2) {
return; // nothing to log
}
frames_count -= 2; // -2 to skip stack_trace(...) + stack_trace_posix(...)
auto frames_buf_ptr = frames_buf + 2; // +2 to skip backtrace(...) + stack_trace_posix(...)
int pipefd[2];
if (pipe(pipefd)) {
std::fprintf(out, "Failed to output detailed stack trace to stream, outputting plain stack trace\n");
std::fflush(out);
backtrace_symbols_fd(frames_buf_ptr, frames_count, fileno(out)); // fallback plain output
return;
}
size_t buf_len = 0;
CONSTEXPR const size_t buf_size = 1024; // arbitrary size
char buf[buf_size];
std::thread thread([&pipefd, level, out, &buf, &buf_len, buf_size]()->void {
for (char ch; read(pipefd[0], &ch, 1) > 0;) {
if (ch != '\n') {
if (buf_len < buf_size - 1) {
buf[buf_len++] = ch;
continue;
}
if (buf_len < buf_size) {
buf[buf_len++] = '\0';
std::fprintf(out, "%s", buf);
}
std::fputc(ch, out); // line longer than buf, output line directly
continue;
}
if (buf_len >= buf_size) {
buf_len = 0;
std::fprintf(out, "\n");
std::fflush(out);
continue;
}
char* addr_start = nullptr;
char* addr_end = nullptr;
char* fn_start = nullptr;
char* offset_start = nullptr;
char* offset_end = nullptr;
char* path_start = buf;
for (size_t i = 0; i < buf_len; ++i) {
switch(buf[i]) {
case '(':
fn_start = &buf[i + 1];
continue;
case '+':
offset_start = &buf[i + 1];
continue;
case ')':
offset_end = &buf[i];
continue;
case '[':
addr_start = &buf[i + 1];
continue;
case ']':
addr_end = &buf[i];
continue;
}
}
buf[buf_len] = '\0';
buf_len = 0;
auto fn_end = offset_start ? offset_start - 1 : nullptr;
auto path_end = fn_start ? fn_start - 1 : (addr_start ? addr_start - 1 : nullptr);
bfd_callback_type_t callback = [out](
const char* file, size_t line, const char* fn
)->void {
UNUSED(fn);
if (file) {
std::fprintf(out, "%s:%lu\n", file, line);
} else {
std::fprintf(out, "??:?");
}
std::fflush(out);
};
if (path_start < path_end) {
if (offset_start < offset_end) {
std::fwrite(path_start, sizeof(char), path_end - path_start, out);
if (fn_start < fn_end) {
std::fprintf(out, "(");
*fn_end = '\0';
auto fn_name = proc_name_demangle(fn_start);
if(fn_name) {
std::fprintf(out, "%s", fn_name.get());
} else {
std::fwrite(fn_start, sizeof(char), fn_end - fn_start, out);
}
std::fprintf(out, "+%s\n", offset_start);
std::fflush(out);
} else {
std::fprintf(out, "%s ", path_end);
*offset_end = '\0';
*path_end = '\0';
if (!file_line_bfd(callback, path_start, offset_start) && !file_line_addr2line(level, path_start, offset_start)) {
std::fprintf(out, "\n");
std::fflush(out);
}
}
continue;
}
if (addr_start < addr_end) {
std::fprintf(out, "%s ", path_start);
*addr_end = '\0';
*path_end = '\0';
if (!file_line_bfd(callback, path_start, addr_start) && !file_line_addr2line(level, path_start, addr_start)) {
std::fprintf(out, "\n");
std::fflush(out);
}
continue;
}
}
std::fprintf(out, "%s\n", buf);
std::fflush(out);
}
});
backtrace_symbols_fd(frames_buf_ptr, frames_count, pipefd[1]);
close(pipefd[1]);
thread.join();
close(pipefd[0]);
std::fprintf(out, "\n");
std::fflush(out);
}
#endif
#if defined(USE_LIBBFD)
bool file_line_bfd(const bfd_callback_type_t& callback, const char* obj, void* addr) {
struct bfd_init_t { bfd_init_t() { bfd_init(); } };
static bfd_init_t static_bfd_init; // one-time init of BFD
UNUSED(static_bfd_init);
auto* file_bfd = bfd_openr(obj, nullptr);
if (!file_bfd || !bfd_check_format(file_bfd, bfd_object)) {
return false;
}
CONSTEXPR const size_t symbols_size = 1048576; // arbitrary size (4K proved too small)
auto symbol_bytes = bfd_get_symtab_upper_bound(file_bfd);
if (symbol_bytes <= 0 || symbols_size < size_t(symbol_bytes)) {
return false; // prealocated buffer is not large enough
}
char symbols[symbols_size];
asymbol** symbols_ptr = (asymbol**)&symbols;
auto symbols_len = bfd_canonicalize_symtab(file_bfd, symbols_ptr);
UNUSED(symbols_len); // actual number of symbol pointers, not including the NULL
auto* section = bfd_get_section_by_name(file_bfd, ".text"); // '.text' is a hardcoded section name
auto bfd_addr = bfd_vma(addr);
if (!section || bfd_addr < section->vma) {
return false; // no section or address not within section
}
auto offset = bfd_addr - section->vma;
const char* file;
const char* func;
unsigned int line;
if (!bfd_find_nearest_line(file_bfd, section, symbols_ptr, offset, &file, &func, &line)) {
return false; // unable to obtain file/line
}
callback(file, line, func);
return true;
}
#else
bool file_line_bfd(const bfd_callback_type_t&, const char*, void*) {
return false;
}
#endif
#if defined(USE_LIBUNWIND)
bool file_line_bfd(const bfd_callback_type_t& callback, const char* obj, unw_word_t addr) {
return file_line_bfd(callback, obj, (void*)addr);
}
bool file_line_addr2line(iresearch::logger::level_t level, const char* obj, unw_word_t addr) {
size_t addr_size = sizeof(unw_word_t)*3 + 2 + 1; // aproximately 3 chars per byte +2 for 0x, +1 for \0
char addr_buf[addr_size];
snprintf(addr_buf, addr_size, "0x%lx", addr);
return file_line_addr2line(level, obj, addr_buf);
}
bool stack_trace_libunwind(iresearch::logger::level_t level) {
unw_context_t ctx;
unw_cursor_t cursor;
if (0 != unw_getcontext(&ctx) || 0 != unw_init_local(&cursor, &ctx)) {
return false;
}
// skip backtrace(...) + stack_trace_libunwind(...)
if (unw_step(&cursor) <= 0) {
return true; // nothing to log
}
auto* out = output(level);
unw_word_t instruction_pointer;
while (unw_step(&cursor) > 0) {
if (0 != unw_get_reg(&cursor, UNW_REG_IP, &instruction_pointer)) {
std::fprintf(out, "<unknown>\n");
std::fflush(out);
continue; // no instruction pointer available
}
Dl_info dl_info;
// resolve function/flie/line via dladdr() + addr2line
if (0 != dladdr((void*)instruction_pointer, &dl_info) || !dl_info.dli_fname) {
// there appears to be a magic number base address which should not be subtracted from the instruction_pointer
static const void* static_fbase = (void*)0x400000;
auto addr = instruction_pointer - (static_fbase == dl_info.dli_fbase ? unw_word_t(dl_info.dli_saddr) : unw_word_t(dl_info.dli_fbase));
bool use_addr2line = false;
bfd_callback_type_t callback = [level, out, instruction_pointer, &addr, &dl_info, &use_addr2line](const char* file, size_t line, const char* fn)->void {
std::fprintf(out, "%s(", dl_info.dli_fname ? dl_info.dli_fname : "\?\?");
auto proc_name = proc_name_demangle(fn);
if (!proc_name) {
proc_name = proc_name_demangle(dl_info.dli_sname);
}
if (proc_name) {
std::fprintf(out, "%s", proc_name.get());
} else if (fn) {
std::fprintf(out, "%s", fn);
} else if (dl_info.dli_sname) {
std::fprintf(out, "%s", dl_info.dli_sname);
}
// match offsets in Posix backtrace output
std::fprintf(
out, "+0x%lx)[0x%lx] ",
dl_info.dli_saddr ? (instruction_pointer - unw_word_t(dl_info.dli_saddr)) : addr, instruction_pointer
);
if (use_addr2line) {
if (!file_line_addr2line(level, dl_info.dli_fname, addr)) {
std::fprintf(out, "\n");
std::fflush(out);
}
return;
}
std::fprintf(out, "%s:", file ? file : "??");
if (line) {
std::fprintf(out, "%lu", line);
} else {
std::fprintf(out, "?");
}
std::fprintf(out, "\n");
std::fflush(out);
};
if (!file_line_bfd(callback, dl_info.dli_fname, addr)) {
use_addr2line = true;
callback(nullptr, 0, nullptr);
}
continue;
}
CONSTEXPR const size_t proc_size = 1024; // arbitrary size
char proc_buf[proc_size];
unw_word_t offset;
if (0 != unw_get_proc_name(&cursor, proc_buf, proc_size, &offset)) {
std::fprintf(out, "\?\?[0x%lx]\n", instruction_pointer);
std::fflush(out);
continue; // no function info available
}
auto proc_name = proc_name_demangle(proc_buf);
std::fprintf(out, "\?\?(%s+0x%lx)[0x%lx]\n", proc_name ? proc_name.get() : proc_buf, offset, instruction_pointer);
std::fflush(out);
}
return true;
}
#else
bool stack_trace_libunwind(iresearch::logger::level_t) {
return false;
}
#endif
NS_END
NS_ROOT
NS_BEGIN(logger)
bool enabled(level_t level) {
return logger_ctx::instance().enabled(level);
}
FILE* output(level_t level) {
return logger_ctx::instance().file(level);
}
void output(level_t level, FILE* out) {
logger_ctx::instance().output(level, out);
}
void output_le(level_t level, FILE* out) {
logger_ctx::instance().output_le(level, out);
}
void stack_trace(level_t level) {
if (!enabled(level)) {
return; // skip generating trace if logging is disabled for this level altogether
}
#if defined(_MSC_VER)
__try {
RaiseException(1, 0, 0, NULL);
} __except(stack_trace_win32(level, GetExceptionInformation())) {
return;
}
stack_trace_win32(level, nullptr);
#else
try {
if (!stack_trace_libunwind(level) && !stack_trace_gdb(level)) {
stack_trace_posix(level);
}
} catch(std::bad_alloc&) {
stack_trace_nomalloc(level, 2); // +2 to skip stack_trace()
throw;
}
#endif
}
void stack_trace(level_t level, const std::exception_ptr& eptr) {
UNUSED(eptr); // no known way to get original instruction pointer from exception_ptr
if (!enabled(level)) {
return; // skip generating trace if logging is disabled for this level altogether
}
// copy of stack_trace() for proper ignored-frames calculation
#if defined(_MSC_VER)
__try {
RaiseException(1, 0, 0, NULL);
} __except(stack_trace_win32(level, GetExceptionInformation())) {
return;
}
stack_trace_win32(level, nullptr);
#else
try {
if (!stack_trace_libunwind(level) && !stack_trace_gdb(level)) {
stack_trace_posix(level);
}
} catch(std::bad_alloc&) {
stack_trace_nomalloc(level, 2); // +2 to skip stack_trace()
throw;
}
#endif
}
#ifndef _MSC_VER
void stack_trace_nomalloc(level_t level, size_t skip) {
if (!enabled(level)) {
return; // skip generating trace if logging is disabled for this level altogether
}
CONSTEXPR const size_t frames_max = 128; // arbitrary size
void* frames_buf[frames_max];
auto frames_count = backtrace(frames_buf, frames_max);
if (frames_count > 0 && size_t(frames_count) > skip) {
static std::mutex mtx;
SCOPED_LOCK(mtx);
backtrace_symbols_fd(frames_buf + skip, frames_count - skip, fileno(output(level)));
}
}
#endif
irs::logger::level_t stack_trace_level() {
return logger_ctx::instance().stack_trace_level();
}
void stack_trace_level(level_t level) {
logger_ctx::instance().stack_trace_level(level);
}
std::ostream& stream(level_t level) {
return logger_ctx::instance().stream(level);
}
NS_END // logger
NS_END
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------