1 #ifndef _WIN32 2 // Disable all warnings from gcc/clang: 3 #pragma GCC diagnostic push 4 #pragma GCC diagnostic ignored "-Wpragmas" 5 6 #pragma GCC diagnostic ignored "-Wc++98-compat" 7 #pragma GCC diagnostic ignored "-Wc++98-compat-pedantic" 8 #pragma GCC diagnostic ignored "-Wexit-time-destructors" 9 #pragma GCC diagnostic ignored "-Wformat-nonliteral" 10 #pragma GCC diagnostic ignored "-Wglobal-constructors" 11 #pragma GCC diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" 12 #pragma GCC diagnostic ignored "-Wmissing-prototypes" 13 #pragma GCC diagnostic ignored "-Wpadded" 14 #pragma GCC diagnostic ignored "-Wsign-compare" 15 #pragma GCC diagnostic ignored "-Wsign-conversion" 16 #pragma GCC diagnostic ignored "-Wunknown-pragmas" 17 #pragma GCC diagnostic ignored "-Wunused-macros" 18 #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" 19 #else 20 #ifdef _MSC_VER 21 #pragma warning(push) 22 #pragma warning(disable:4018) 23 #endif // _MSC_VER 24 #endif 25 26 #include "loguru.hpp" 27 28 #ifndef LOGURU_HAS_BEEN_IMPLEMENTED 29 #define LOGURU_HAS_BEEN_IMPLEMENTED 30 31 #define LOGURU_PREAMBLE_WIDTH (53 + LOGURU_THREADNAME_WIDTH + LOGURU_FILENAME_WIDTH) 32 33 #undef min 34 #undef max 35 36 #include <algorithm> 37 #include <atomic> 38 #include <chrono> 39 #include <cstdarg> 40 #include <cstdio> 41 #include <cstdlib> 42 #include <cstring> 43 #include <mutex> 44 #include <regex> 45 #include <string> 46 #include <thread> 47 #include <vector> 48 49 #ifdef _WIN32 50 #include <direct.h> 51 52 #define localtime_r(a, b) localtime_s(b, a) // No localtime_r with MSVC, but arguments are swapped for localtime_s 53 #else 54 #include <signal.h> 55 #include <sys/stat.h> // mkdir 56 #include <unistd.h> // STDERR_FILENO 57 #endif 58 59 #ifdef __linux__ 60 #include <linux/limits.h> // PATH_MAX 61 #elif !defined(_WIN32) 62 #include <limits.h> // PATH_MAX 63 #endif 64 65 #ifndef PATH_MAX 66 #define PATH_MAX 1024 67 #endif 68 69 #ifdef __APPLE__ 70 #include "TargetConditionals.h" 71 #endif 72 73 // TODO: use defined(_POSIX_VERSION) for some of these things? 74 75 #if defined(_WIN32) || defined(__CYGWIN__) 76 #define LOGURU_PTHREADS 0 77 #define LOGURU_WINTHREADS 1 78 #ifndef LOGURU_STACKTRACES 79 #define LOGURU_STACKTRACES 0 80 #endif 81 #elif defined(__rtems__) || defined(__ANDROID__) 82 #define LOGURU_PTHREADS 1 83 #define LOGURU_WINTHREADS 0 84 #ifndef LOGURU_STACKTRACES 85 #define LOGURU_STACKTRACES 0 86 #endif 87 #else 88 #define LOGURU_PTHREADS 1 89 #define LOGURU_WINTHREADS 0 90 #ifndef LOGURU_STACKTRACES 91 #define LOGURU_STACKTRACES 1 92 #endif 93 #endif 94 95 #if LOGURU_STACKTRACES 96 #include <cxxabi.h> // for __cxa_demangle 97 #include <dlfcn.h> // for dladdr 98 #include <execinfo.h> // for backtrace 99 #endif // LOGURU_STACKTRACES 100 101 #if LOGURU_PTHREADS 102 #include <pthread.h> 103 #if defined(__FreeBSD__) 104 #include <pthread_np.h> 105 #include <sys/thr.h> 106 #elif (defined(__OpenBSD__)||defined(__DragonFly__)) 107 #include <pthread_np.h> 108 #endif 109 110 #ifdef __linux__ 111 /* On Linux, the default thread name is the same as the name of the binary. 112 Additionally, all new threads inherit the name of the thread it got forked from. 113 For this reason, Loguru use the pthread Thread Local Storage 114 for storing thread names on Linux. */ 115 #define LOGURU_PTLS_NAMES 1 116 #endif 117 #endif 118 119 #if LOGURU_WINTHREADS 120 #ifndef _WIN32_WINNT 121 #define _WIN32_WINNT 0x0502 122 #endif 123 #define WIN32_LEAN_AND_MEAN 124 #define NOMINMAX 125 #include <windows.h> 126 #endif 127 128 #ifndef LOGURU_PTLS_NAMES 129 #define LOGURU_PTLS_NAMES 0 130 #endif 131 132 133 namespace loguru 134 { 135 using namespace std::chrono; 136 137 #if LOGURU_WITH_FILEABS 138 struct FileAbs 139 { 140 char path[PATH_MAX]; 141 char mode_str[4]; 142 Verbosity verbosity; 143 struct stat st; 144 FILE* fp; 145 bool is_reopening = false; // to prevent recursive call in file_reopen. 146 decltype(steady_clock::now()) last_check_time = steady_clock::now(); 147 }; 148 #else 149 typedef FILE* FileAbs; 150 #endif 151 152 struct Callback 153 { 154 std::string id; 155 log_handler_t callback; 156 void* user_data; 157 Verbosity verbosity; // Does not change! 158 close_handler_t close; 159 flush_handler_t flush; 160 unsigned indentation; 161 }; 162 163 using CallbackVec = std::vector<Callback>; 164 165 using StringPair = std::pair<std::string, std::string>; 166 using StringPairList = std::vector<StringPair>; 167 168 const auto s_start_time = steady_clock::now(); 169 170 Verbosity g_stderr_verbosity = Verbosity_0; 171 bool g_colorlogtostderr = true; 172 unsigned g_flush_interval_ms = 0; 173 bool g_preamble = true; 174 175 Verbosity g_internal_verbosity = Verbosity_0; 176 177 // Preamble details 178 bool g_preamble_date = true; 179 bool g_preamble_time = true; 180 bool g_preamble_uptime = true; 181 bool g_preamble_thread = true; 182 bool g_preamble_file = true; 183 bool g_preamble_verbose = true; 184 bool g_preamble_pipe = true; 185 186 static std::recursive_mutex s_mutex; 187 static Verbosity s_max_out_verbosity = Verbosity_OFF; 188 static std::string s_argv0_filename; 189 static std::string s_arguments; 190 static char s_current_dir[PATH_MAX]; 191 static CallbackVec s_callbacks; 192 static fatal_handler_t s_fatal_handler = nullptr; 193 static verbosity_to_name_t s_verbosity_to_name_callback = nullptr; 194 static name_to_verbosity_t s_name_to_verbosity_callback = nullptr; 195 static StringPairList s_user_stack_cleanups; 196 static bool s_strip_file_path = true; 197 static std::atomic<unsigned> s_stderr_indentation { 0 }; 198 199 // For periodic flushing: 200 static std::thread* s_flush_thread = nullptr; 201 static bool s_needs_flushing = false; 202 __anon6d705b3f0102()203 static const bool s_terminal_has_color = [](){ 204 #ifdef _WIN32 205 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING 206 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 207 #endif 208 209 HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); 210 if (hOut != INVALID_HANDLE_VALUE) { 211 DWORD dwMode = 0; 212 GetConsoleMode(hOut, &dwMode); 213 dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; 214 return SetConsoleMode(hOut, dwMode) != 0; 215 } 216 return false; 217 #else 218 if (const char* term = getenv("TERM")) { 219 return 0 == strcmp(term, "cygwin") 220 || 0 == strcmp(term, "linux") 221 || 0 == strcmp(term, "rxvt-unicode-256color") 222 || 0 == strcmp(term, "screen") 223 || 0 == strcmp(term, "screen-256color") 224 || 0 == strcmp(term, "screen.xterm-256color") 225 || 0 == strcmp(term, "tmux-256color") 226 || 0 == strcmp(term, "xterm") 227 || 0 == strcmp(term, "xterm-256color") 228 || 0 == strcmp(term, "xterm-termite") 229 || 0 == strcmp(term, "xterm-color"); 230 } else { 231 return false; 232 } 233 #endif 234 }(); 235 236 static void print_preamble_header(char* out_buff, size_t out_buff_size); 237 238 #if LOGURU_PTLS_NAMES 239 static pthread_once_t s_pthread_key_once = PTHREAD_ONCE_INIT; 240 static pthread_key_t s_pthread_key_name; 241 make_pthread_key_name()242 void make_pthread_key_name() 243 { 244 (void)pthread_key_create(&s_pthread_key_name, free); 245 } 246 #endif 247 248 // ------------------------------------------------------------------------------ 249 // Colors 250 terminal_has_color()251 bool terminal_has_color() { return s_terminal_has_color; } 252 253 // Colors 254 255 #ifdef _WIN32 256 #define VTSEQ(ID) ("\x1b[1;" #ID "m") 257 #else 258 #define VTSEQ(ID) ("\x1b[" #ID "m") 259 #endif 260 terminal_black()261 const char* terminal_black() { return s_terminal_has_color ? VTSEQ(30) : ""; } terminal_red()262 const char* terminal_red() { return s_terminal_has_color ? VTSEQ(31) : ""; } terminal_green()263 const char* terminal_green() { return s_terminal_has_color ? VTSEQ(32) : ""; } terminal_yellow()264 const char* terminal_yellow() { return s_terminal_has_color ? VTSEQ(33) : ""; } terminal_blue()265 const char* terminal_blue() { return s_terminal_has_color ? VTSEQ(34) : ""; } terminal_purple()266 const char* terminal_purple() { return s_terminal_has_color ? VTSEQ(35) : ""; } terminal_cyan()267 const char* terminal_cyan() { return s_terminal_has_color ? VTSEQ(36) : ""; } terminal_light_gray()268 const char* terminal_light_gray() { return s_terminal_has_color ? VTSEQ(37) : ""; } terminal_white()269 const char* terminal_white() { return s_terminal_has_color ? VTSEQ(37) : ""; } terminal_light_red()270 const char* terminal_light_red() { return s_terminal_has_color ? VTSEQ(91) : ""; } terminal_dim()271 const char* terminal_dim() { return s_terminal_has_color ? VTSEQ(2) : ""; } 272 273 // Formating terminal_bold()274 const char* terminal_bold() { return s_terminal_has_color ? VTSEQ(1) : ""; } terminal_underline()275 const char* terminal_underline() { return s_terminal_has_color ? VTSEQ(4) : ""; } 276 277 // You should end each line with this! terminal_reset()278 const char* terminal_reset() { return s_terminal_has_color ? VTSEQ(0) : ""; } 279 280 // ------------------------------------------------------------------------------ 281 #if LOGURU_WITH_FILEABS 282 void file_reopen(void* user_data); to_file(void * user_data)283 inline FILE* to_file(void* user_data) { return reinterpret_cast<FileAbs*>(user_data)->fp; } 284 #else to_file(void * user_data)285 inline FILE* to_file(void* user_data) { return reinterpret_cast<FILE*>(user_data); } 286 #endif 287 file_log(void * user_data,const Message & message)288 void file_log(void* user_data, const Message& message) 289 { 290 #if LOGURU_WITH_FILEABS 291 FileAbs* file_abs = reinterpret_cast<FileAbs*>(user_data); 292 if (file_abs->is_reopening) { 293 return; 294 } 295 // It is better checking file change every minute/hour/day, 296 // instead of doing this every time we log. 297 // Here check_interval is set to zero to enable checking every time; 298 const auto check_interval = seconds(0); 299 if (duration_cast<seconds>(steady_clock::now() - file_abs->last_check_time) > check_interval) { 300 file_abs->last_check_time = steady_clock::now(); 301 file_reopen(user_data); 302 } 303 FILE* file = to_file(user_data); 304 if (!file) { 305 return; 306 } 307 #else 308 FILE* file = to_file(user_data); 309 #endif 310 fprintf(file, "%s%s%s%s\n", 311 message.preamble, message.indentation, message.prefix, message.message); 312 if (g_flush_interval_ms == 0) { 313 fflush(file); 314 } 315 } 316 file_close(void * user_data)317 void file_close(void* user_data) 318 { 319 FILE* file = to_file(user_data); 320 if (file) { 321 fclose(file); 322 } 323 #if LOGURU_WITH_FILEABS 324 delete reinterpret_cast<FileAbs*>(user_data); 325 #endif 326 } 327 file_flush(void * user_data)328 void file_flush(void* user_data) 329 { 330 FILE* file = to_file(user_data); 331 fflush(file); 332 } 333 334 #if LOGURU_WITH_FILEABS file_reopen(void * user_data)335 void file_reopen(void* user_data) 336 { 337 FileAbs * file_abs = reinterpret_cast<FileAbs*>(user_data); 338 struct stat st; 339 int ret; 340 if (!file_abs->fp || (ret = stat(file_abs->path, &st)) == -1 || (st.st_ino != file_abs->st.st_ino)) { 341 file_abs->is_reopening = true; 342 if (file_abs->fp) { 343 fclose(file_abs->fp); 344 } 345 if (!file_abs->fp) { 346 VLOG_F(g_internal_verbosity, "Reopening file '" LOGURU_FMT(s) "' due to previous error", file_abs->path); 347 } 348 else if (ret < 0) { 349 const auto why = errno_as_text(); 350 VLOG_F(g_internal_verbosity, "Reopening file '" LOGURU_FMT(s) "' due to '" LOGURU_FMT(s) "'", file_abs->path, why.c_str()); 351 } else { 352 VLOG_F(g_internal_verbosity, "Reopening file '" LOGURU_FMT(s) "' due to file changed", file_abs->path); 353 } 354 // try reopen current file. 355 if (!create_directories(file_abs->path)) { 356 LOG_F(ERROR, "Failed to create directories to '" LOGURU_FMT(s) "'", file_abs->path); 357 } 358 file_abs->fp = fopen(file_abs->path, file_abs->mode_str); 359 if (!file_abs->fp) { 360 LOG_F(ERROR, "Failed to open '" LOGURU_FMT(s) "'", file_abs->path); 361 } else { 362 stat(file_abs->path, &file_abs->st); 363 } 364 file_abs->is_reopening = false; 365 } 366 } 367 #endif 368 // ------------------------------------------------------------------------------ 369 370 // Helpers: 371 ~Text()372 Text::~Text() { free(_str); } 373 374 #if LOGURU_USE_FMTLIB vtextprintf(const char * format,fmt::format_args args)375 Text vtextprintf(const char* format, fmt::format_args args) 376 { 377 return Text(STRDUP(fmt::vformat(format, args).c_str())); 378 } 379 #else 380 LOGURU_PRINTF_LIKE(1, 0) vtextprintf(const char * format,va_list vlist)381 static Text vtextprintf(const char* format, va_list vlist) 382 { 383 #ifdef _WIN32 384 int bytes_needed = _vscprintf(format, vlist); 385 CHECK_F(bytes_needed >= 0, "Bad string format: '%s'", format); 386 char* buff = (char*)malloc(bytes_needed+1); 387 vsnprintf(buff, bytes_needed+1, format, vlist); 388 return Text(buff); 389 #else 390 char* buff = nullptr; 391 int result = vasprintf(&buff, format, vlist); 392 CHECK_F(result >= 0, "Bad string format: '" LOGURU_FMT(s) "'", format); 393 return Text(buff); 394 #endif 395 } 396 textprintf(const char * format,...)397 Text textprintf(const char* format, ...) 398 { 399 va_list vlist; 400 va_start(vlist, format); 401 auto result = vtextprintf(format, vlist); 402 va_end(vlist); 403 return result; 404 } 405 #endif 406 407 // Overloaded for variadic template matching. textprintf()408 Text textprintf() 409 { 410 return Text(static_cast<char*>(calloc(1, 1))); 411 } 412 indentation(unsigned depth)413 static const char* indentation(unsigned depth) 414 { 415 static const char buff[] = 416 ". . . . . . . . . . " ". . . . . . . . . . " 417 ". . . . . . . . . . " ". . . . . . . . . . " 418 ". . . . . . . . . . " ". . . . . . . . . . " 419 ". . . . . . . . . . " ". . . . . . . . . . " 420 ". . . . . . . . . . " ". . . . . . . . . . "; 421 static const size_t INDENTATION_WIDTH = 4; 422 static const size_t NUM_INDENTATIONS = (sizeof(buff) - 1) / INDENTATION_WIDTH; 423 depth = std::min<unsigned>(depth, NUM_INDENTATIONS); 424 return buff + INDENTATION_WIDTH * (NUM_INDENTATIONS - depth); 425 } 426 parse_args(int & argc,char * argv[],const char * verbosity_flag)427 static void parse_args(int& argc, char* argv[], const char* verbosity_flag) 428 { 429 int arg_dest = 1; 430 int out_argc = argc; 431 432 for (int arg_it = 1; arg_it < argc; ++arg_it) { 433 auto cmd = argv[arg_it]; 434 auto arg_len = strlen(verbosity_flag); 435 if (strncmp(cmd, verbosity_flag, arg_len) == 0 && !std::isalpha(cmd[arg_len], std::locale(""))) { 436 out_argc -= 1; 437 auto value_str = cmd + arg_len; 438 if (value_str[0] == '\0') { 439 // Value in separate argument 440 arg_it += 1; 441 CHECK_LT_F(arg_it, argc, "Missing verbosiy level after " LOGURU_FMT(s) "", verbosity_flag); 442 value_str = argv[arg_it]; 443 out_argc -= 1; 444 } 445 if (*value_str == '=') { value_str += 1; } 446 447 auto req_verbosity = get_verbosity_from_name(value_str); 448 if (req_verbosity != Verbosity_INVALID) { 449 g_stderr_verbosity = req_verbosity; 450 } else { 451 char* end = 0; 452 g_stderr_verbosity = static_cast<int>(strtol(value_str, &end, 10)); 453 CHECK_F(end && *end == '\0', 454 "Invalid verbosity. Expected integer, INFO, WARNING, ERROR or OFF, got '" LOGURU_FMT(s) "'", value_str); 455 } 456 } else { 457 argv[arg_dest++] = argv[arg_it]; 458 } 459 } 460 461 argc = out_argc; 462 argv[argc] = nullptr; 463 } 464 now_ns()465 static long long now_ns() 466 { 467 return duration_cast<nanoseconds>(high_resolution_clock::now().time_since_epoch()).count(); 468 } 469 470 // Returns the part of the path after the last / or \ (if any). filename(const char * path)471 const char* filename(const char* path) 472 { 473 for (auto ptr = path; *ptr; ++ptr) { 474 if (*ptr == '/' || *ptr == '\\') { 475 path = ptr + 1; 476 } 477 } 478 return path; 479 } 480 481 // ------------------------------------------------------------------------------ 482 on_atexit()483 static void on_atexit() 484 { 485 VLOG_F(g_internal_verbosity, "atexit"); 486 flush(); 487 } 488 489 static void install_signal_handlers(bool unsafe_signal_handler); 490 write_hex_digit(std::string & out,unsigned num)491 static void write_hex_digit(std::string& out, unsigned num) 492 { 493 DCHECK_LT_F(num, 16u); 494 if (num < 10u) { out.push_back(char('0' + num)); } 495 else { out.push_back(char('A' + num - 10)); } 496 } 497 write_hex_byte(std::string & out,uint8_t n)498 static void write_hex_byte(std::string& out, uint8_t n) 499 { 500 write_hex_digit(out, n >> 4u); 501 write_hex_digit(out, n & 0x0f); 502 } 503 escape(std::string & out,const std::string & str)504 static void escape(std::string& out, const std::string& str) 505 { 506 for (char c : str) { 507 /**/ if (c == '\a') { out += "\\a"; } 508 else if (c == '\b') { out += "\\b"; } 509 else if (c == '\f') { out += "\\f"; } 510 else if (c == '\n') { out += "\\n"; } 511 else if (c == '\r') { out += "\\r"; } 512 else if (c == '\t') { out += "\\t"; } 513 else if (c == '\v') { out += "\\v"; } 514 else if (c == '\\') { out += "\\\\"; } 515 else if (c == '\'') { out += "\\\'"; } 516 else if (c == '\"') { out += "\\\""; } 517 else if (c == ' ') { out += "\\ "; } 518 else if (0 <= c && c < 0x20) { // ASCI control character: 519 // else if (c < 0x20 || c != (c & 127)) { // ASCII control character or UTF-8: 520 out += "\\x"; 521 write_hex_byte(out, static_cast<uint8_t>(c)); 522 } else { out += c; } 523 } 524 } 525 errno_as_text()526 Text errno_as_text() 527 { 528 char buff[256]; 529 #if defined(__GLIBC__) && defined(_GNU_SOURCE) 530 // GNU Version 531 return Text(STRDUP(strerror_r(errno, buff, sizeof(buff)))); 532 #elif defined(__APPLE__) || _POSIX_C_SOURCE >= 200112L 533 // XSI Version 534 strerror_r(errno, buff, sizeof(buff)); 535 return Text(strdup(buff)); 536 #elif defined(_WIN32) 537 strerror_s(buff, sizeof(buff), errno); 538 return Text(STRDUP(buff)); 539 #else 540 // Not thread-safe. 541 return Text(STRDUP(strerror(errno))); 542 #endif 543 } 544 init(int & argc,char * argv[],const Options & options)545 void init(int& argc, char* argv[], const Options& options) 546 { 547 CHECK_GT_F(argc, 0, "Expected proper argc/argv"); 548 CHECK_EQ_F(argv[argc], nullptr, "Expected proper argc/argv"); 549 550 s_argv0_filename = filename(argv[0]); 551 552 #ifdef _WIN32 553 #define getcwd _getcwd 554 #endif 555 556 if (!getcwd(s_current_dir, sizeof(s_current_dir))) { 557 const auto error_text = errno_as_text(); 558 LOG_F(WARNING, "Failed to get current working directory: " LOGURU_FMT(s) "", error_text.c_str()); 559 } 560 561 s_arguments = ""; 562 for (int i = 0; i < argc; ++i) { 563 escape(s_arguments, argv[i]); 564 if (i + 1 < argc) { 565 s_arguments += " "; 566 } 567 } 568 569 if (options.verbosity_flag) { 570 parse_args(argc, argv, options.verbosity_flag); 571 } 572 573 if (const auto main_thread_name = options.main_thread_name) { 574 #if LOGURU_PTLS_NAMES || LOGURU_WINTHREADS 575 set_thread_name(main_thread_name); 576 #elif LOGURU_PTHREADS 577 char old_thread_name[16] = {0}; 578 auto this_thread = pthread_self(); 579 #if defined(__APPLE__) || defined(__linux__) 580 pthread_getname_np(this_thread, old_thread_name, sizeof(old_thread_name)); 581 #endif 582 if (old_thread_name[0] == 0) { 583 #ifdef __APPLE__ 584 pthread_setname_np(main_thread_name); 585 #elif defined(__FreeBSD__) || (defined(__OpenBSD__)||defined(__DragonFly__)) 586 pthread_set_name_np(this_thread, main_thread_name); 587 #elif defined(__linux__) 588 pthread_setname_np(this_thread, main_thread_name); 589 #endif 590 } 591 #endif // LOGURU_PTHREADS 592 } 593 594 if (g_stderr_verbosity >= Verbosity_INFO) { 595 if (g_preamble) { 596 char preamble_explain[LOGURU_PREAMBLE_WIDTH]; 597 print_preamble_header(preamble_explain, sizeof(preamble_explain)); 598 if (g_colorlogtostderr && s_terminal_has_color) { 599 fprintf(stderr, "%s%s%s\n", terminal_reset(), terminal_dim(), preamble_explain); 600 } else { 601 fprintf(stderr, "%s\n", preamble_explain); 602 } 603 } 604 fflush(stderr); 605 } 606 VLOG_F(g_internal_verbosity, "arguments: " LOGURU_FMT(s) "", s_arguments.c_str()); 607 if (strlen(s_current_dir) != 0) 608 { 609 VLOG_F(g_internal_verbosity, "Current dir: " LOGURU_FMT(s) "", s_current_dir); 610 } 611 VLOG_F(g_internal_verbosity, "stderr verbosity: " LOGURU_FMT(d) "", g_stderr_verbosity); 612 VLOG_F(g_internal_verbosity, "-----------------------------------"); 613 614 install_signal_handlers(options.unsafe_signal_handler); 615 616 atexit(on_atexit); 617 } 618 shutdown()619 void shutdown() 620 { 621 VLOG_F(g_internal_verbosity, "loguru::shutdown()"); 622 remove_all_callbacks(); 623 set_fatal_handler(nullptr); 624 set_verbosity_to_name_callback(nullptr); 625 set_name_to_verbosity_callback(nullptr); 626 } 627 write_date_time(char * buff,size_t buff_size)628 void write_date_time(char* buff, size_t buff_size) 629 { 630 auto now = system_clock::now(); 631 long long ms_since_epoch = duration_cast<milliseconds>(now.time_since_epoch()).count(); 632 time_t sec_since_epoch = time_t(ms_since_epoch / 1000); 633 tm time_info; 634 localtime_r(&sec_since_epoch, &time_info); 635 snprintf(buff, buff_size, "%04d%02d%02d_%02d%02d%02d.%03lld", 636 1900 + time_info.tm_year, 1 + time_info.tm_mon, time_info.tm_mday, 637 time_info.tm_hour, time_info.tm_min, time_info.tm_sec, ms_since_epoch % 1000); 638 } 639 argv0_filename()640 const char* argv0_filename() 641 { 642 return s_argv0_filename.c_str(); 643 } 644 arguments()645 const char* arguments() 646 { 647 return s_arguments.c_str(); 648 } 649 current_dir()650 const char* current_dir() 651 { 652 return s_current_dir; 653 } 654 home_dir()655 const char* home_dir() 656 { 657 #ifdef _WIN32 658 auto user_profile = getenv("USERPROFILE"); 659 CHECK_F(user_profile != nullptr, "Missing USERPROFILE"); 660 return user_profile; 661 #else // _WIN32 662 auto home = getenv("HOME"); 663 CHECK_F(home != nullptr, "Missing HOME"); 664 return home; 665 #endif // _WIN32 666 } 667 suggest_log_path(const char * prefix,char * buff,unsigned buff_size)668 void suggest_log_path(const char* prefix, char* buff, unsigned buff_size) 669 { 670 if (prefix[0] == '~') { 671 snprintf(buff, buff_size - 1, "%s%s", home_dir(), prefix + 1); 672 } else { 673 snprintf(buff, buff_size - 1, "%s", prefix); 674 } 675 676 // Check for terminating / 677 size_t n = strlen(buff); 678 if (n != 0) { 679 if (buff[n - 1] != '/') { 680 CHECK_F(n + 2 < buff_size, "Filename buffer too small"); 681 buff[n] = '/'; 682 buff[n + 1] = '\0'; 683 } 684 } 685 686 strncat(buff, s_argv0_filename.c_str(), buff_size - strlen(buff) - 1); 687 strncat(buff, "/", buff_size - strlen(buff) - 1); 688 write_date_time(buff + strlen(buff), buff_size - strlen(buff)); 689 strncat(buff, ".log", buff_size - strlen(buff) - 1); 690 } 691 create_directories(const char * file_path_const)692 bool create_directories(const char* file_path_const) 693 { 694 CHECK_F(file_path_const && *file_path_const); 695 char* file_path = STRDUP(file_path_const); 696 for (char* p = strchr(file_path + 1, '/'); p; p = strchr(p + 1, '/')) { 697 *p = '\0'; 698 699 #ifdef _WIN32 700 if (_mkdir(file_path) == -1) { 701 #else 702 if (mkdir(file_path, 0755) == -1) { 703 #endif 704 if (errno != EEXIST) { 705 LOG_F(ERROR, "Failed to create directory '" LOGURU_FMT(s) "'", file_path); 706 LOG_IF_F(ERROR, errno == EACCES, "EACCES"); 707 LOG_IF_F(ERROR, errno == ENAMETOOLONG, "ENAMETOOLONG"); 708 LOG_IF_F(ERROR, errno == ENOENT, "ENOENT"); 709 LOG_IF_F(ERROR, errno == ENOTDIR, "ENOTDIR"); 710 LOG_IF_F(ERROR, errno == ELOOP, "ELOOP"); 711 712 *p = '/'; 713 free(file_path); 714 return false; 715 } 716 } 717 *p = '/'; 718 } 719 free(file_path); 720 return true; 721 } 722 bool add_file(const char* path_in, FileMode mode, Verbosity verbosity) 723 { 724 char path[PATH_MAX]; 725 if (path_in[0] == '~') { 726 snprintf(path, sizeof(path) - 1, "%s%s", home_dir(), path_in + 1); 727 } else { 728 snprintf(path, sizeof(path) - 1, "%s", path_in); 729 } 730 731 if (!create_directories(path)) { 732 LOG_F(ERROR, "Failed to create directories to '" LOGURU_FMT(s) "'", path); 733 } 734 735 const char* mode_str = (mode == FileMode::Truncate ? "w" : "a"); 736 auto file = fopen(path, mode_str); 737 if (!file) { 738 LOG_F(ERROR, "Failed to open '" LOGURU_FMT(s) "'", path); 739 return false; 740 } 741 #if LOGURU_WITH_FILEABS 742 FileAbs* file_abs = new FileAbs(); // this is deleted in file_close; 743 snprintf(file_abs->path, sizeof(file_abs->path) - 1, "%s", path); 744 snprintf(file_abs->mode_str, sizeof(file_abs->mode_str) - 1, "%s", mode_str); 745 stat(file_abs->path, &file_abs->st); 746 file_abs->fp = file; 747 file_abs->verbosity = verbosity; 748 add_callback(path_in, file_log, file_abs, verbosity, file_close, file_flush); 749 #else 750 add_callback(path_in, file_log, file, verbosity, file_close, file_flush); 751 #endif 752 753 if (mode == FileMode::Append) { 754 fprintf(file, "\n\n\n\n\n"); 755 } 756 if (!s_arguments.empty()) { 757 fprintf(file, "arguments: %s\n", s_arguments.c_str()); 758 } 759 if (strlen(s_current_dir) != 0) { 760 fprintf(file, "Current dir: %s\n", s_current_dir); 761 } 762 fprintf(file, "File verbosity level: %d\n", verbosity); 763 if (g_preamble) { 764 char preamble_explain[LOGURU_PREAMBLE_WIDTH]; 765 print_preamble_header(preamble_explain, sizeof(preamble_explain)); 766 fprintf(file, "%s\n", preamble_explain); 767 } 768 fflush(file); 769 770 VLOG_F(g_internal_verbosity, "Logging to '" LOGURU_FMT(s) "', mode: '" LOGURU_FMT(s) "', verbosity: " LOGURU_FMT(d) "", path, mode_str, verbosity); 771 return true; 772 } 773 774 // Will be called right before abort(). 775 void set_fatal_handler(fatal_handler_t handler) 776 { 777 s_fatal_handler = handler; 778 } 779 780 fatal_handler_t get_fatal_handler() 781 { 782 return s_fatal_handler; 783 } 784 785 void set_verbosity_to_name_callback(verbosity_to_name_t callback) 786 { 787 s_verbosity_to_name_callback = callback; 788 } 789 790 void set_name_to_verbosity_callback(name_to_verbosity_t callback) 791 { 792 s_name_to_verbosity_callback = callback; 793 } 794 795 void add_stack_cleanup(const char* find_this, const char* replace_with_this) 796 { 797 if (strlen(find_this) <= strlen(replace_with_this)) { 798 LOG_F(WARNING, "add_stack_cleanup: the replacement should be shorter than the pattern!"); 799 return; 800 } 801 802 s_user_stack_cleanups.push_back(StringPair(find_this, replace_with_this)); 803 } 804 805 static void on_callback_change() 806 { 807 s_max_out_verbosity = Verbosity_OFF; 808 for (const auto& callback : s_callbacks) { 809 s_max_out_verbosity = std::max(s_max_out_verbosity, callback.verbosity); 810 } 811 } 812 813 void add_callback( 814 const char* id, 815 log_handler_t callback, 816 void* user_data, 817 Verbosity verbosity, 818 close_handler_t on_close, 819 flush_handler_t on_flush) 820 { 821 std::lock_guard<std::recursive_mutex> lock(s_mutex); 822 s_callbacks.push_back(Callback{id, callback, user_data, verbosity, on_close, on_flush, 0}); 823 on_callback_change(); 824 } 825 826 // Returns a custom verbosity name if one is available, or nullptr. 827 // See also set_verbosity_to_name_callback. 828 const char* get_verbosity_name(Verbosity verbosity) 829 { 830 auto name = s_verbosity_to_name_callback 831 ? (*s_verbosity_to_name_callback)(verbosity) 832 : nullptr; 833 834 // Use standard replacements if callback fails: 835 if (!name) 836 { 837 if (verbosity <= Verbosity_FATAL) { 838 name = "FATL"; 839 } else if (verbosity == Verbosity_ERROR) { 840 name = "ERR"; 841 } else if (verbosity == Verbosity_WARNING) { 842 name = "WARN"; 843 } else if (verbosity == Verbosity_INFO) { 844 name = "INFO"; 845 } 846 } 847 848 return name; 849 } 850 851 // Returns Verbosity_INVALID if the name is not found. 852 // See also set_name_to_verbosity_callback. 853 Verbosity get_verbosity_from_name(const char* name) 854 { 855 auto verbosity = s_name_to_verbosity_callback 856 ? (*s_name_to_verbosity_callback)(name) 857 : Verbosity_INVALID; 858 859 // Use standard replacements if callback fails: 860 if (verbosity == Verbosity_INVALID) { 861 if (strcmp(name, "OFF") == 0) { 862 verbosity = Verbosity_OFF; 863 } else if (strcmp(name, "INFO") == 0) { 864 verbosity = Verbosity_INFO; 865 } else if (strcmp(name, "WARNING") == 0) { 866 verbosity = Verbosity_WARNING; 867 } else if (strcmp(name, "ERROR") == 0) { 868 verbosity = Verbosity_ERROR; 869 } else if (strcmp(name, "FATAL") == 0) { 870 verbosity = Verbosity_FATAL; 871 } 872 } 873 874 return verbosity; 875 } 876 877 bool remove_callback(const char* id) 878 { 879 std::lock_guard<std::recursive_mutex> lock(s_mutex); 880 auto it = std::find_if(begin(s_callbacks), end(s_callbacks), [&](const Callback& c) { return c.id == id; }); 881 if (it != s_callbacks.end()) { 882 if (it->close) { it->close(it->user_data); } 883 s_callbacks.erase(it); 884 on_callback_change(); 885 return true; 886 } else { 887 LOG_F(ERROR, "Failed to locate callback with id '" LOGURU_FMT(s) "'", id); 888 return false; 889 } 890 } 891 892 void remove_all_callbacks() 893 { 894 std::lock_guard<std::recursive_mutex> lock(s_mutex); 895 for (auto& callback : s_callbacks) { 896 if (callback.close) { 897 callback.close(callback.user_data); 898 } 899 } 900 s_callbacks.clear(); 901 on_callback_change(); 902 } 903 904 // Returns the maximum of g_stderr_verbosity and all file/custom outputs. 905 Verbosity current_verbosity_cutoff() 906 { 907 return g_stderr_verbosity > s_max_out_verbosity ? 908 g_stderr_verbosity : s_max_out_verbosity; 909 } 910 911 #if LOGURU_WINTHREADS 912 char* get_thread_name_win32() 913 { 914 __declspec( thread ) static char thread_name[LOGURU_THREADNAME_WIDTH + 1] = {0}; 915 return &thread_name[0]; 916 } 917 #endif // LOGURU_WINTHREADS 918 919 void set_thread_name(const char* name) 920 { 921 #if LOGURU_PTLS_NAMES 922 (void)pthread_once(&s_pthread_key_once, make_pthread_key_name); 923 (void)pthread_setspecific(s_pthread_key_name, STRDUP(name)); 924 925 #elif LOGURU_PTHREADS 926 #ifdef __APPLE__ 927 pthread_setname_np(name); 928 #elif defined(__FreeBSD__) || (defined(__OpenBSD__)||defined(__DragonFly__)) 929 pthread_set_name_np(pthread_self(), name); 930 #elif defined(__linux__) 931 pthread_setname_np(pthread_self(), name); 932 #endif 933 #elif LOGURU_WINTHREADS 934 strncpy_s(get_thread_name_win32(), LOGURU_THREADNAME_WIDTH + 1, name, _TRUNCATE); 935 #else // LOGURU_PTHREADS 936 (void)name; 937 #endif // LOGURU_PTHREADS 938 } 939 940 #if LOGURU_PTLS_NAMES 941 const char* get_thread_name_ptls() 942 { 943 (void)pthread_once(&s_pthread_key_once, make_pthread_key_name); 944 return static_cast<const char*>(pthread_getspecific(s_pthread_key_name)); 945 } 946 #endif // LOGURU_PTLS_NAMES 947 948 void get_thread_name(char* buffer, unsigned long long length, bool right_align_hext_id) 949 { 950 #ifdef _WIN32 951 (void)right_align_hext_id; 952 #endif 953 CHECK_NE_F(length, 0u, "Zero length buffer in get_thread_name"); 954 CHECK_NOTNULL_F(buffer, "nullptr in get_thread_name"); 955 #if LOGURU_PTHREADS 956 auto thread = pthread_self(); 957 #if LOGURU_PTLS_NAMES 958 if (const char* name = get_thread_name_ptls()) { 959 snprintf(buffer, length, "%s", name); 960 } else { 961 buffer[0] = 0; 962 } 963 #elif defined(__APPLE__) || defined(__linux__) 964 pthread_getname_np(thread, buffer, length); 965 #else 966 buffer[0] = 0; 967 #endif 968 969 if (buffer[0] == 0) { 970 #ifdef __APPLE__ 971 uint64_t thread_id; 972 pthread_threadid_np(thread, &thread_id); 973 #elif defined(__FreeBSD__) 974 long thread_id; 975 (void)thr_self(&thread_id); 976 #elif (defined(__OpenBSD__)||defined(__DragonFly__)) 977 unsigned thread_id = -1; 978 #else 979 uint64_t thread_id = thread; 980 #endif 981 if (right_align_hext_id) { 982 snprintf(buffer, length, "%*X", static_cast<int>(length - 1), static_cast<unsigned>(thread_id)); 983 } else { 984 snprintf(buffer, length, "%X", static_cast<unsigned>(thread_id)); 985 } 986 } 987 #elif LOGURU_WINTHREADS 988 if (const char* name = get_thread_name_win32()) { 989 snprintf(buffer, (size_t)length, "%s", name); 990 } else { 991 buffer[0] = 0; 992 } 993 #else // !LOGURU_WINTHREADS && !LOGURU_WINTHREADS 994 buffer[0] = 0; 995 #endif 996 997 } 998 999 // ------------------------------------------------------------------------ 1000 // Stack traces 1001 1002 #if LOGURU_STACKTRACES 1003 Text demangle(const char* name) 1004 { 1005 int status = -1; 1006 char* demangled = abi::__cxa_demangle(name, 0, 0, &status); 1007 Text result{status == 0 ? demangled : STRDUP(name)}; 1008 return result; 1009 } 1010 1011 #if LOGURU_RTTI 1012 template <class T> 1013 std::string type_name() 1014 { 1015 auto demangled = demangle(typeid(T).name()); 1016 return demangled.c_str(); 1017 } 1018 #endif // LOGURU_RTTI 1019 1020 static const StringPairList REPLACE_LIST = { 1021 #if LOGURU_RTTI 1022 { type_name<std::string>(), "std::string" }, 1023 { type_name<std::wstring>(), "std::wstring" }, 1024 { type_name<std::u16string>(), "std::u16string" }, 1025 { type_name<std::u32string>(), "std::u32string" }, 1026 #endif // LOGURU_RTTI 1027 { "std::__1::", "std::" }, 1028 { "__thiscall ", "" }, 1029 { "__cdecl ", "" }, 1030 }; 1031 1032 void do_replacements(const StringPairList& replacements, std::string& str) 1033 { 1034 for (auto&& p : replacements) { 1035 if (p.first.size() <= p.second.size()) { 1036 // On gcc, "type_name<std::string>()" is "std::string" 1037 continue; 1038 } 1039 1040 size_t it; 1041 while ((it=str.find(p.first)) != std::string::npos) { 1042 str.replace(it, p.first.size(), p.second); 1043 } 1044 } 1045 } 1046 1047 std::string prettify_stacktrace(const std::string& input) 1048 { 1049 std::string output = input; 1050 1051 do_replacements(s_user_stack_cleanups, output); 1052 do_replacements(REPLACE_LIST, output); 1053 1054 try { 1055 std::regex std_allocator_re(R"(,\s*std::allocator<[^<>]+>)"); 1056 output = std::regex_replace(output, std_allocator_re, std::string("")); 1057 1058 std::regex template_spaces_re(R"(<\s*([^<> ]+)\s*>)"); 1059 output = std::regex_replace(output, template_spaces_re, std::string("<$1>")); 1060 } catch (std::regex_error&) { 1061 // Probably old GCC. 1062 } 1063 1064 return output; 1065 } 1066 1067 std::string stacktrace_as_stdstring(int skip) 1068 { 1069 // From https://gist.github.com/fmela/591333 1070 void* callstack[128]; 1071 const auto max_frames = sizeof(callstack) / sizeof(callstack[0]); 1072 int num_frames = backtrace(callstack, max_frames); 1073 char** symbols = backtrace_symbols(callstack, num_frames); 1074 1075 std::string result; 1076 // Print stack traces so the most relevant ones are written last 1077 // Rationale: http://yellerapp.com/posts/2015-01-22-upside-down-stacktraces.html 1078 for (int i = num_frames - 1; i >= skip; --i) { 1079 char buf[1024]; 1080 Dl_info info; 1081 if (dladdr(callstack[i], &info) && info.dli_sname) { 1082 char* demangled = NULL; 1083 int status = -1; 1084 if (info.dli_sname[0] == '_') { 1085 demangled = abi::__cxa_demangle(info.dli_sname, 0, 0, &status); 1086 } 1087 snprintf(buf, sizeof(buf), "%-3d %*p %s + %zd\n", 1088 i - skip, int(2 + sizeof(void*) * 2), callstack[i], 1089 status == 0 ? demangled : 1090 info.dli_sname == 0 ? symbols[i] : info.dli_sname, 1091 static_cast<char*>(callstack[i]) - static_cast<char*>(info.dli_saddr)); 1092 free(demangled); 1093 } else { 1094 snprintf(buf, sizeof(buf), "%-3d %*p %s\n", 1095 i - skip, int(2 + sizeof(void*) * 2), callstack[i], symbols[i]); 1096 } 1097 result += buf; 1098 } 1099 free(symbols); 1100 1101 if (num_frames == max_frames) { 1102 result = "[truncated]\n" + result; 1103 } 1104 1105 if (!result.empty() && result[result.size() - 1] == '\n') { 1106 result.resize(result.size() - 1); 1107 } 1108 1109 return prettify_stacktrace(result); 1110 } 1111 1112 #else // LOGURU_STACKTRACES 1113 Text demangle(const char* name) 1114 { 1115 return Text(STRDUP(name)); 1116 } 1117 1118 std::string stacktrace_as_stdstring(int) 1119 { 1120 // No stacktraces available on this platform" 1121 return ""; 1122 } 1123 1124 #endif // LOGURU_STACKTRACES 1125 1126 Text stacktrace(int skip) 1127 { 1128 auto str = stacktrace_as_stdstring(skip + 1); 1129 return Text(STRDUP(str.c_str())); 1130 } 1131 1132 // ------------------------------------------------------------------------ 1133 1134 static void print_preamble_header(char* out_buff, size_t out_buff_size) 1135 { 1136 if (out_buff_size == 0) { return; } 1137 out_buff[0] = '\0'; 1138 long pos = 0; 1139 if (g_preamble_date && pos < out_buff_size) { 1140 pos += snprintf(out_buff + pos, out_buff_size - pos, "date "); 1141 } 1142 if (g_preamble_time && pos < out_buff_size) { 1143 pos += snprintf(out_buff + pos, out_buff_size - pos, "time "); 1144 } 1145 if (g_preamble_uptime && pos < out_buff_size) { 1146 pos += snprintf(out_buff + pos, out_buff_size - pos, "( uptime ) "); 1147 } 1148 if (g_preamble_thread && pos < out_buff_size) { 1149 pos += snprintf(out_buff + pos, out_buff_size - pos, "[%-*s]", LOGURU_THREADNAME_WIDTH, " thread name/id"); 1150 } 1151 if (g_preamble_file && pos < out_buff_size) { 1152 pos += snprintf(out_buff + pos, out_buff_size - pos, "%*s:line ", LOGURU_FILENAME_WIDTH, "file"); 1153 } 1154 if (g_preamble_verbose && pos < out_buff_size) { 1155 pos += snprintf(out_buff + pos, out_buff_size - pos, " v"); 1156 } 1157 if (g_preamble_pipe && pos < out_buff_size) { 1158 pos += snprintf(out_buff + pos, out_buff_size - pos, "| "); 1159 } 1160 } 1161 1162 static void print_preamble(char* out_buff, size_t out_buff_size, Verbosity verbosity, const char* file, unsigned line) 1163 { 1164 if (out_buff_size == 0) { return; } 1165 out_buff[0] = '\0'; 1166 if (!g_preamble) { return; } 1167 long long ms_since_epoch = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count(); 1168 time_t sec_since_epoch = time_t(ms_since_epoch / 1000); 1169 tm time_info; 1170 localtime_r(&sec_since_epoch, &time_info); 1171 1172 auto uptime_ms = duration_cast<milliseconds>(steady_clock::now() - s_start_time).count(); 1173 auto uptime_sec = uptime_ms / 1000.0; 1174 1175 char thread_name[LOGURU_THREADNAME_WIDTH + 1] = {0}; 1176 get_thread_name(thread_name, LOGURU_THREADNAME_WIDTH + 1, true); 1177 1178 if (s_strip_file_path) { 1179 file = filename(file); 1180 } 1181 1182 char level_buff[6]; 1183 const char* custom_level_name = get_verbosity_name(verbosity); 1184 if (custom_level_name) { 1185 snprintf(level_buff, sizeof(level_buff) - 1, "%s", custom_level_name); 1186 } else { 1187 snprintf(level_buff, sizeof(level_buff) - 1, "% 4d", verbosity); 1188 } 1189 1190 long pos = 0; 1191 1192 if (g_preamble_date && pos < out_buff_size) { 1193 pos += snprintf(out_buff + pos, out_buff_size - pos, "%04d-%02d-%02d ", 1194 1900 + time_info.tm_year, 1 + time_info.tm_mon, time_info.tm_mday); 1195 } 1196 if (g_preamble_time && pos < out_buff_size) { 1197 pos += snprintf(out_buff + pos, out_buff_size - pos, "%02d:%02d:%02d.%03lld ", 1198 time_info.tm_hour, time_info.tm_min, time_info.tm_sec, ms_since_epoch % 1000); 1199 } 1200 if (g_preamble_uptime && pos < out_buff_size) { 1201 pos += snprintf(out_buff + pos, out_buff_size - pos, "(%8.3fs) ", 1202 uptime_sec); 1203 } 1204 if (g_preamble_thread && pos < out_buff_size) { 1205 pos += snprintf(out_buff + pos, out_buff_size - pos, "[%-*s]", 1206 LOGURU_THREADNAME_WIDTH, thread_name); 1207 } 1208 if (g_preamble_file && pos < out_buff_size) { 1209 char shortened_filename[LOGURU_FILENAME_WIDTH + 1]; 1210 snprintf(shortened_filename, LOGURU_FILENAME_WIDTH + 1, "%s", file); 1211 pos += snprintf(out_buff + pos, out_buff_size - pos, "%*s:%-5u ", 1212 LOGURU_FILENAME_WIDTH, shortened_filename, line); 1213 } 1214 if (g_preamble_verbose && pos < out_buff_size) { 1215 pos += snprintf(out_buff + pos, out_buff_size - pos, "%4s", 1216 level_buff); 1217 } 1218 if (g_preamble_pipe && pos < out_buff_size) { 1219 pos += snprintf(out_buff + pos, out_buff_size - pos, "| "); 1220 } 1221 } 1222 1223 // stack_trace_skip is just if verbosity == FATAL. 1224 static void log_message(int stack_trace_skip, Message& message, bool with_indentation, bool abort_if_fatal) 1225 { 1226 const auto verbosity = message.verbosity; 1227 std::lock_guard<std::recursive_mutex> lock(s_mutex); 1228 1229 if (message.verbosity == Verbosity_FATAL) { 1230 auto st = loguru::stacktrace(stack_trace_skip + 2); 1231 if (!st.empty()) { 1232 RAW_LOG_F(ERROR, "Stack trace:\n" LOGURU_FMT(s) "", st.c_str()); 1233 } 1234 1235 auto ec = loguru::get_error_context(); 1236 if (!ec.empty()) { 1237 RAW_LOG_F(ERROR, "" LOGURU_FMT(s) "", ec.c_str()); 1238 } 1239 } 1240 1241 if (with_indentation) { 1242 message.indentation = indentation(s_stderr_indentation); 1243 } 1244 1245 if (verbosity <= g_stderr_verbosity) { 1246 if (g_colorlogtostderr && s_terminal_has_color) { 1247 if (verbosity > Verbosity_WARNING) { 1248 fprintf(stderr, "%s%s%s%s%s%s%s%s\n", 1249 terminal_reset(), 1250 terminal_dim(), 1251 message.preamble, 1252 message.indentation, 1253 verbosity == Verbosity_INFO ? terminal_reset() : "", // un-dim for info 1254 message.prefix, 1255 message.message, 1256 terminal_reset()); 1257 } else { 1258 fprintf(stderr, "%s%s%s%s%s%s%s\n", 1259 terminal_reset(), 1260 verbosity == Verbosity_WARNING ? terminal_yellow() : terminal_red(), 1261 message.preamble, 1262 message.indentation, 1263 message.prefix, 1264 message.message, 1265 terminal_reset()); 1266 } 1267 } else { 1268 fprintf(stderr, "%s%s%s%s\n", 1269 message.preamble, message.indentation, message.prefix, message.message); 1270 } 1271 1272 if (g_flush_interval_ms == 0) { 1273 fflush(stderr); 1274 } else { 1275 s_needs_flushing = true; 1276 } 1277 } 1278 1279 for (auto& p : s_callbacks) { 1280 if (verbosity <= p.verbosity) { 1281 if (with_indentation) { 1282 message.indentation = indentation(p.indentation); 1283 } 1284 p.callback(p.user_data, message); 1285 if (g_flush_interval_ms == 0) { 1286 if (p.flush) { p.flush(p.user_data); } 1287 } else { 1288 s_needs_flushing = true; 1289 } 1290 } 1291 } 1292 1293 if (g_flush_interval_ms > 0 && !s_flush_thread) { 1294 s_flush_thread = new std::thread([](){ 1295 for (;;) { 1296 if (s_needs_flushing) { 1297 flush(); 1298 } 1299 std::this_thread::sleep_for(std::chrono::milliseconds(g_flush_interval_ms)); 1300 } 1301 }); 1302 } 1303 1304 if (message.verbosity == Verbosity_FATAL) { 1305 flush(); 1306 1307 if (s_fatal_handler) { 1308 s_fatal_handler(message); 1309 flush(); 1310 } 1311 1312 if (abort_if_fatal) { 1313 #if LOGURU_CATCH_SIGABRT && !defined(_WIN32) 1314 // Make sure we don't catch our own abort: 1315 signal(SIGABRT, SIG_DFL); 1316 #endif 1317 abort(); 1318 } 1319 } 1320 } 1321 1322 // stack_trace_skip is just if verbosity == FATAL. 1323 void log_to_everywhere(int stack_trace_skip, Verbosity verbosity, 1324 const char* file, unsigned line, 1325 const char* prefix, const char* buff) 1326 { 1327 char preamble_buff[LOGURU_PREAMBLE_WIDTH]; 1328 print_preamble(preamble_buff, sizeof(preamble_buff), verbosity, file, line); 1329 auto message = Message{verbosity, file, line, preamble_buff, "", prefix, buff}; 1330 log_message(stack_trace_skip + 1, message, true, true); 1331 } 1332 1333 #if LOGURU_USE_FMTLIB 1334 void vlog(Verbosity verbosity, const char* file, unsigned line, const char* format, fmt::format_args args) 1335 { 1336 auto formatted = fmt::vformat(format, args); 1337 log_to_everywhere(1, verbosity, file, line, "", formatted.c_str()); 1338 } 1339 1340 void raw_vlog(Verbosity verbosity, const char* file, unsigned line, const char* format, fmt::format_args args) 1341 { 1342 auto formatted = fmt::vformat(format, args); 1343 auto message = Message{verbosity, file, line, "", "", "", formatted.c_str()}; 1344 log_message(1, message, false, true); 1345 } 1346 #else 1347 void log(Verbosity verbosity, const char* file, unsigned line, const char* format, ...) 1348 { 1349 va_list vlist; 1350 va_start(vlist, format); 1351 auto buff = vtextprintf(format, vlist); 1352 log_to_everywhere(1, verbosity, file, line, "", buff.c_str()); 1353 va_end(vlist); 1354 } 1355 1356 void raw_log(Verbosity verbosity, const char* file, unsigned line, const char* format, ...) 1357 { 1358 va_list vlist; 1359 va_start(vlist, format); 1360 auto buff = vtextprintf(format, vlist); 1361 auto message = Message{verbosity, file, line, "", "", "", buff.c_str()}; 1362 log_message(1, message, false, true); 1363 va_end(vlist); 1364 } 1365 #endif 1366 1367 void flush() 1368 { 1369 std::lock_guard<std::recursive_mutex> lock(s_mutex); 1370 fflush(stderr); 1371 for (const auto& callback : s_callbacks) 1372 { 1373 if (callback.flush) { 1374 callback.flush(callback.user_data); 1375 } 1376 } 1377 s_needs_flushing = false; 1378 } 1379 1380 LogScopeRAII::LogScopeRAII(Verbosity verbosity, const char* file, unsigned line, const char* format, ...) 1381 : _verbosity(verbosity), _file(file), _line(line) 1382 { 1383 if (verbosity <= current_verbosity_cutoff()) { 1384 std::lock_guard<std::recursive_mutex> lock(s_mutex); 1385 _indent_stderr = (verbosity <= g_stderr_verbosity); 1386 _start_time_ns = now_ns(); 1387 va_list vlist; 1388 va_start(vlist, format); 1389 vsnprintf(_name, sizeof(_name), format, vlist); 1390 log_to_everywhere(1, _verbosity, file, line, "{ ", _name); 1391 va_end(vlist); 1392 1393 if (_indent_stderr) { 1394 ++s_stderr_indentation; 1395 } 1396 1397 for (auto& p : s_callbacks) { 1398 if (verbosity <= p.verbosity) { 1399 ++p.indentation; 1400 } 1401 } 1402 } else { 1403 _file = nullptr; 1404 } 1405 } 1406 1407 LogScopeRAII::~LogScopeRAII() 1408 { 1409 if (_file) { 1410 std::lock_guard<std::recursive_mutex> lock(s_mutex); 1411 if (_indent_stderr && s_stderr_indentation > 0) { 1412 --s_stderr_indentation; 1413 } 1414 for (auto& p : s_callbacks) { 1415 // Note: Callback indentation cannot change! 1416 if (_verbosity <= p.verbosity) { 1417 // in unlikely case this callback is new 1418 if (p.indentation > 0) { 1419 --p.indentation; 1420 } 1421 } 1422 } 1423 #if LOGURU_VERBOSE_SCOPE_ENDINGS 1424 auto duration_sec = (now_ns() - _start_time_ns) / 1e9; 1425 #if LOGURU_USE_FMTLIB 1426 auto buff = textprintf("{:.{}f} s: {:s}", duration_sec, LOGURU_SCOPE_TIME_PRECISION, _name); 1427 #else 1428 auto buff = textprintf("%.*f s: %s", LOGURU_SCOPE_TIME_PRECISION, duration_sec, _name); 1429 #endif 1430 log_to_everywhere(1, _verbosity, _file, _line, "} ", buff.c_str()); 1431 #else 1432 log_to_everywhere(1, _verbosity, _file, _line, "}", ""); 1433 #endif 1434 } 1435 } 1436 1437 #if LOGURU_USE_FMTLIB 1438 void vlog_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, const char* format, fmt::format_args args) 1439 { 1440 auto formatted = fmt::vformat(format, args); 1441 log_to_everywhere(stack_trace_skip + 1, Verbosity_FATAL, file, line, expr, formatted.c_str()); 1442 abort(); // log_to_everywhere already does this, but this makes the analyzer happy. 1443 } 1444 #else 1445 void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, const char* format, ...) 1446 { 1447 va_list vlist; 1448 va_start(vlist, format); 1449 auto buff = vtextprintf(format, vlist); 1450 log_to_everywhere(stack_trace_skip + 1, Verbosity_FATAL, file, line, expr, buff.c_str()); 1451 va_end(vlist); 1452 abort(); // log_to_everywhere already does this, but this makes the analyzer happy. 1453 } 1454 #endif 1455 1456 void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line) 1457 { 1458 log_and_abort(stack_trace_skip + 1, expr, file, line, " "); 1459 } 1460 1461 // ---------------------------------------------------------------------------- 1462 // Streams: 1463 1464 #if LOGURU_USE_FMTLIB 1465 template<typename... Args> 1466 std::string vstrprintf(const char* format, const Args&... args) 1467 { 1468 auto text = textprintf(format, args...); 1469 std::string result = text.c_str(); 1470 return result; 1471 } 1472 1473 template<typename... Args> 1474 std::string strprintf(const char* format, const Args&... args) 1475 { 1476 return vstrprintf(format, args...); 1477 } 1478 #else 1479 std::string vstrprintf(const char* format, va_list vlist) 1480 { 1481 auto text = vtextprintf(format, vlist); 1482 std::string result = text.c_str(); 1483 return result; 1484 } 1485 1486 std::string strprintf(const char* format, ...) 1487 { 1488 va_list vlist; 1489 va_start(vlist, format); 1490 auto result = vstrprintf(format, vlist); 1491 va_end(vlist); 1492 return result; 1493 } 1494 #endif 1495 1496 #if LOGURU_WITH_STREAMS 1497 1498 StreamLogger::~StreamLogger() noexcept(false) 1499 { 1500 auto message = _ss.str(); 1501 log(_verbosity, _file, _line, LOGURU_FMT(s), message.c_str()); 1502 } 1503 1504 AbortLogger::~AbortLogger() noexcept(false) 1505 { 1506 auto message = _ss.str(); 1507 loguru::log_and_abort(1, _expr, _file, _line, LOGURU_FMT(s), message.c_str()); 1508 } 1509 1510 #endif // LOGURU_WITH_STREAMS 1511 1512 // ---------------------------------------------------------------------------- 1513 // 888888 88""Yb 88""Yb dP"Yb 88""Yb dP""b8 dP"Yb 88b 88 888888 888888 Yb dP 888888 1514 // 88__ 88__dP 88__dP dP Yb 88__dP dP `" dP Yb 88Yb88 88 88__ YbdP 88 1515 // 88"" 88"Yb 88"Yb Yb dP 88"Yb Yb Yb dP 88 Y88 88 88"" dPYb 88 1516 // 888888 88 Yb 88 Yb YbodP 88 Yb YboodP YbodP 88 Y8 88 888888 dP Yb 88 1517 // ---------------------------------------------------------------------------- 1518 1519 struct StringStream 1520 { 1521 std::string str; 1522 }; 1523 1524 // Use this in your EcPrinter implementations. 1525 void stream_print(StringStream& out_string_stream, const char* text) 1526 { 1527 out_string_stream.str += text; 1528 } 1529 1530 // ---------------------------------------------------------------------------- 1531 1532 using ECPtr = EcEntryBase*; 1533 1534 #if defined(_WIN32) || (defined(__APPLE__) && !TARGET_OS_IPHONE) 1535 #ifdef __APPLE__ 1536 #define LOGURU_THREAD_LOCAL __thread 1537 #else 1538 #define LOGURU_THREAD_LOCAL thread_local 1539 #endif 1540 static LOGURU_THREAD_LOCAL ECPtr thread_ec_ptr = nullptr; 1541 1542 ECPtr& get_thread_ec_head_ref() 1543 { 1544 return thread_ec_ptr; 1545 } 1546 #else // !thread_local 1547 static pthread_once_t s_ec_pthread_once = PTHREAD_ONCE_INIT; 1548 static pthread_key_t s_ec_pthread_key; 1549 1550 void free_ec_head_ref(void* io_error_context) 1551 { 1552 delete reinterpret_cast<ECPtr*>(io_error_context); 1553 } 1554 1555 void ec_make_pthread_key() 1556 { 1557 (void)pthread_key_create(&s_ec_pthread_key, free_ec_head_ref); 1558 } 1559 1560 ECPtr& get_thread_ec_head_ref() 1561 { 1562 (void)pthread_once(&s_ec_pthread_once, ec_make_pthread_key); 1563 auto ec = reinterpret_cast<ECPtr*>(pthread_getspecific(s_ec_pthread_key)); 1564 if (ec == nullptr) { 1565 ec = new ECPtr(nullptr); 1566 (void)pthread_setspecific(s_ec_pthread_key, ec); 1567 } 1568 return *ec; 1569 } 1570 #endif // !thread_local 1571 1572 // ---------------------------------------------------------------------------- 1573 1574 EcHandle get_thread_ec_handle() 1575 { 1576 return get_thread_ec_head_ref(); 1577 } 1578 1579 Text get_error_context() 1580 { 1581 return get_error_context_for(get_thread_ec_head_ref()); 1582 } 1583 1584 Text get_error_context_for(const EcEntryBase* ec_head) 1585 { 1586 std::vector<const EcEntryBase*> stack; 1587 while (ec_head) { 1588 stack.push_back(ec_head); 1589 ec_head = ec_head->_previous; 1590 } 1591 std::reverse(stack.begin(), stack.end()); 1592 1593 StringStream result; 1594 if (!stack.empty()) { 1595 result.str += "------------------------------------------------\n"; 1596 for (auto entry : stack) { 1597 const auto description = std::string(entry->_descr) + ":"; 1598 #if LOGURU_USE_FMTLIB 1599 auto prefix = textprintf("[ErrorContext] {.{}s}:{:-5u} {:-20s} ", 1600 filename(entry->_file), LOGURU_FILENAME_WIDTH, entry->_line, description.c_str()); 1601 #else 1602 auto prefix = textprintf("[ErrorContext] %*s:%-5u %-20s ", 1603 LOGURU_FILENAME_WIDTH, filename(entry->_file), entry->_line, description.c_str()); 1604 #endif 1605 result.str += prefix.c_str(); 1606 entry->print_value(result); 1607 result.str += "\n"; 1608 } 1609 result.str += "------------------------------------------------"; 1610 } 1611 return Text(STRDUP(result.str.c_str())); 1612 } 1613 1614 EcEntryBase::EcEntryBase(const char* file, unsigned line, const char* descr) 1615 : _file(file), _line(line), _descr(descr) 1616 { 1617 EcEntryBase*& ec_head = get_thread_ec_head_ref(); 1618 _previous = ec_head; 1619 ec_head = this; 1620 } 1621 1622 EcEntryBase::~EcEntryBase() 1623 { 1624 get_thread_ec_head_ref() = _previous; 1625 } 1626 1627 // ------------------------------------------------------------------------ 1628 1629 Text ec_to_text(const char* value) 1630 { 1631 // Add quotes around the string to make it obvious where it begin and ends. 1632 // This is great for detecting erroneous leading or trailing spaces in e.g. an identifier. 1633 auto str = "\"" + std::string(value) + "\""; 1634 return Text{STRDUP(str.c_str())}; 1635 } 1636 1637 Text ec_to_text(char c) 1638 { 1639 // Add quotes around the character to make it obvious where it begin and ends. 1640 std::string str = "'"; 1641 1642 auto write_hex_digit = [&](unsigned num) 1643 { 1644 if (num < 10u) { str += char('0' + num); } 1645 else { str += char('a' + num - 10); } 1646 }; 1647 1648 auto write_hex_16 = [&](uint16_t n) 1649 { 1650 write_hex_digit((n >> 12u) & 0x0f); 1651 write_hex_digit((n >> 8u) & 0x0f); 1652 write_hex_digit((n >> 4u) & 0x0f); 1653 write_hex_digit((n >> 0u) & 0x0f); 1654 }; 1655 1656 if (c == '\\') { str += "\\\\"; } 1657 else if (c == '\"') { str += "\\\""; } 1658 else if (c == '\'') { str += "\\\'"; } 1659 else if (c == '\0') { str += "\\0"; } 1660 else if (c == '\b') { str += "\\b"; } 1661 else if (c == '\f') { str += "\\f"; } 1662 else if (c == '\n') { str += "\\n"; } 1663 else if (c == '\r') { str += "\\r"; } 1664 else if (c == '\t') { str += "\\t"; } 1665 else if (0 <= c && c < 0x20) { 1666 str += "\\u"; 1667 write_hex_16(static_cast<uint16_t>(c)); 1668 } else { str += c; } 1669 1670 str += "'"; 1671 1672 return Text{STRDUP(str.c_str())}; 1673 } 1674 1675 #define DEFINE_EC(Type) \ 1676 Text ec_to_text(Type value) \ 1677 { \ 1678 auto str = std::to_string(value); \ 1679 return Text{STRDUP(str.c_str())}; \ 1680 } 1681 1682 DEFINE_EC(int) 1683 DEFINE_EC(unsigned int) 1684 DEFINE_EC(long) 1685 DEFINE_EC(unsigned long) 1686 DEFINE_EC(long long) 1687 DEFINE_EC(unsigned long long) 1688 DEFINE_EC(float) 1689 DEFINE_EC(double) 1690 DEFINE_EC(long double) 1691 1692 #undef DEFINE_EC 1693 1694 Text ec_to_text(EcHandle ec_handle) 1695 { 1696 Text parent_ec = get_error_context_for(ec_handle); 1697 char* with_newline = reinterpret_cast<char*>(malloc(strlen(parent_ec.c_str()) + 2)); 1698 with_newline[0] = '\n'; 1699 strcpy(with_newline + 1, parent_ec.c_str()); 1700 return Text(with_newline); 1701 } 1702 1703 // ---------------------------------------------------------------------------- 1704 1705 } // namespace loguru 1706 1707 // ---------------------------------------------------------------------------- 1708 // .dP"Y8 88 dP""b8 88b 88 db 88 .dP"Y8 1709 // `Ybo." 88 dP `" 88Yb88 dPYb 88 `Ybo." 1710 // o.`Y8b 88 Yb "88 88 Y88 dP__Yb 88 .o o.`Y8b 1711 // 8bodP' 88 YboodP 88 Y8 dP""""Yb 88ood8 8bodP' 1712 // ---------------------------------------------------------------------------- 1713 1714 #ifdef _WIN32 1715 namespace loguru { install_signal_handlers(bool unsafe_signal_handler)1716 void install_signal_handlers(bool unsafe_signal_handler) 1717 { 1718 (void)unsafe_signal_handler; 1719 // TODO: implement signal handlers on windows 1720 } 1721 } // namespace loguru 1722 1723 #else // _WIN32 1724 1725 namespace loguru 1726 { 1727 struct Signal 1728 { 1729 int number; 1730 const char* name; 1731 }; 1732 const Signal ALL_SIGNALS[] = { 1733 #if LOGURU_CATCH_SIGABRT 1734 { SIGABRT, "SIGABRT" }, 1735 #endif 1736 { SIGBUS, "SIGBUS" }, 1737 { SIGFPE, "SIGFPE" }, 1738 { SIGILL, "SIGILL" }, 1739 { SIGINT, "SIGINT" }, 1740 { SIGSEGV, "SIGSEGV" }, 1741 { SIGTERM, "SIGTERM" }, 1742 }; 1743 write_to_stderr(const char * data,size_t size)1744 void write_to_stderr(const char* data, size_t size) 1745 { 1746 auto result = write(STDERR_FILENO, data, size); 1747 (void)result; // Ignore errors. 1748 } 1749 write_to_stderr(const char * data)1750 void write_to_stderr(const char* data) 1751 { 1752 write_to_stderr(data, strlen(data)); 1753 } 1754 call_default_signal_handler(int signal_number)1755 void call_default_signal_handler(int signal_number) 1756 { 1757 struct sigaction sig_action; 1758 memset(&sig_action, 0, sizeof(sig_action)); 1759 sigemptyset(&sig_action.sa_mask); 1760 sig_action.sa_handler = SIG_DFL; 1761 sigaction(signal_number, &sig_action, NULL); 1762 kill(getpid(), signal_number); 1763 } 1764 1765 static bool s_unsafe_signal_handler = false; 1766 signal_handler(int signal_number,siginfo_t *,void *)1767 void signal_handler(int signal_number, siginfo_t*, void*) 1768 { 1769 const char* signal_name = "UNKNOWN SIGNAL"; 1770 1771 for (const auto& s : ALL_SIGNALS) { 1772 if (s.number == signal_number) { 1773 signal_name = s.name; 1774 break; 1775 } 1776 } 1777 1778 // -------------------------------------------------------------------- 1779 /* There are few things that are safe to do in a signal handler, 1780 but writing to stderr is one of them. 1781 So we first print out what happened to stderr so we're sure that gets out, 1782 then we do the unsafe things, like logging the stack trace. 1783 */ 1784 1785 if (g_colorlogtostderr && s_terminal_has_color) { 1786 write_to_stderr(terminal_reset()); 1787 write_to_stderr(terminal_bold()); 1788 write_to_stderr(terminal_light_red()); 1789 } 1790 write_to_stderr("\n"); 1791 write_to_stderr("Loguru caught a signal: "); 1792 write_to_stderr(signal_name); 1793 write_to_stderr("\n"); 1794 if (g_colorlogtostderr && s_terminal_has_color) { 1795 write_to_stderr(terminal_reset()); 1796 } 1797 1798 // -------------------------------------------------------------------- 1799 1800 if (s_unsafe_signal_handler) { 1801 // -------------------------------------------------------------------- 1802 /* Now we do unsafe things. This can for example lead to deadlocks if 1803 the signal was triggered from the system's memory management functions 1804 and the code below tries to do allocations. 1805 */ 1806 1807 flush(); 1808 char preamble_buff[LOGURU_PREAMBLE_WIDTH]; 1809 print_preamble(preamble_buff, sizeof(preamble_buff), Verbosity_FATAL, "", 0); 1810 auto message = Message{Verbosity_FATAL, "", 0, preamble_buff, "", "Signal: ", signal_name}; 1811 try { 1812 log_message(1, message, false, false); 1813 } catch (...) { 1814 // This can happed due to s_fatal_handler. 1815 write_to_stderr("Exception caught and ignored by Loguru signal handler.\n"); 1816 } 1817 flush(); 1818 1819 // -------------------------------------------------------------------- 1820 } 1821 1822 call_default_signal_handler(signal_number); 1823 } 1824 install_signal_handlers(bool unsafe_signal_handler)1825 void install_signal_handlers(bool unsafe_signal_handler) 1826 { 1827 s_unsafe_signal_handler = unsafe_signal_handler; 1828 1829 struct sigaction sig_action; 1830 memset(&sig_action, 0, sizeof(sig_action)); 1831 sigemptyset(&sig_action.sa_mask); 1832 sig_action.sa_flags |= SA_SIGINFO; 1833 sig_action.sa_sigaction = &signal_handler; 1834 for (const auto& s : ALL_SIGNALS) { 1835 CHECK_F(sigaction(s.number, &sig_action, NULL) != -1, 1836 "Failed to install handler for " LOGURU_FMT(s) "", s.name); 1837 } 1838 } 1839 } // namespace loguru 1840 1841 #endif // _WIN32 1842 1843 #ifdef _WIN32 1844 #ifdef _MSC_VER 1845 #pragma warning(pop) 1846 #endif // _MSC_VER 1847 #endif // _WIN32 1848 1849 #endif // LOGURU_IMPLEMENTATION 1850