Anklang-0.3.0.dev595+g65331842 anklang-0.3.0.dev595+g65331842
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
logging.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include "logging.hh"
3#include "platform.hh"
4#include "path.hh"
5#include "strings.hh"
6#include "regex.hh"
7#include <fcntl.h>
8#include <cstdarg>
9#include <cstring>
10#ifndef NDEBUG
11#include <execinfo.h>
12#endif
13#ifdef ASE_WITH_CPPTRACE
14#include <cpptrace/cpptrace.hpp>
15#endif
16#include "internal.hh"
17
47namespace Ase {
48
49[[noreturn]] static void abort_debug_friendly (const char *msg, const char *file, int line, const char *func) noexcept;
50
51// == stdout stderr ==
53void
54stdio_flush (uint8 code, const String &txt) noexcept
55{
56 fflush (code != 'e' ? stderr : stdout); // preserve output ordering
57 fputs (txt.c_str(), code == 'e' ? stderr : stdout);
58 fflush (code == 'e' ? stderr : stdout);
59}
60
61// == Timestamp ==
64{
65 struct timeval tv = { 0, 0 };
66 gettimeofday (&tv, nullptr);
67 return tv.tv_sec * 1000000ULL + tv.tv_usec;
68}
69
71 uint64_t timestamp = 0;
72};
73
74static const StartupStats&
75logging_startup_stats()
76{
77 static StartupStats stats = {
78 .timestamp = timestamp_now(),
79 };
80 return stats;
81}
82static bool init_startup_stats = [] { logging_startup_stats(); return 1; } ();
83
84static std::string
85logging_timestamp (uint64_t stamp)
86{
87 const unsigned long long secs = stamp / 1000000ULL;
88 const unsigned long long usec = stamp - (secs * 1000000ULL);
89 return string_format ("%llu.%06llu", secs, usec);
90}
91
92// == Log File ==
93static std::mutex logging_buffer_mutex;
94static std::vector<std::string> *logging_buffer = nullptr;
95static int logging_fd = -2;
96
97static void
98loging_setup()
99{
100 if (logging_fd >= -1)
101 return;
102 std::lock_guard<std::mutex> locker (logging_buffer_mutex);
103 if (logging_fd >= -1)
104 return;
105 const uint64 programstart_timestamp = logging_startup_stats().timestamp;
106 if (!logging_buffer) {
107 logging_buffer = new std::vector<std::string>();
108 const time_t now = programstart_timestamp / 1000000;
109 struct tm stm{};
110 localtime_r (&now, &stm);
111 char tbuf[128] = { 0, };
112 strftime (tbuf, sizeof (tbuf) - 1, "%Y-%m-%d %H:%M:%S %z", &stm);
113 char pidbuf[64] = { 0, };
114 snprintf (pidbuf, sizeof (pidbuf) - 1, " pid=%u", getpid());
115 auto start_msg = logging_timestamp (programstart_timestamp) + " " + executable_name() + ": programstart=\"" + tbuf + "\"" + pidbuf + " executable=\"" + executable_path() + "\"\n";
116 logging_buffer->push_back (start_msg);
117 }
118 logging_fatal_warnings |= string_to_bool (String (string_option_find_value (getenv_ase_debug(), "fatal-warnings", "0", "0", true)));
119 // sigquit_on_abort = string_to_bool (String (string_option_find_value (d, "sigquit-on-abort", "0", "0", true)));
120}
121
122static void
123logging_to_file (const std::string &lines)
124{
125 loging_setup();
126 if (logging_fd == -1)
127 return;
128 if (logging_fd >= 0) {
129 long r;
130 do
131 r = write (logging_fd, lines.data(), lines.size());
132 while (r < 0 && (errno == EINTR || errno == EAGAIN));
133 return;
134 }
135 std::lock_guard<std::mutex> locker (logging_buffer_mutex);
136 if (logging_buffer)
137 logging_buffer->push_back (lines);
138}
139
140const char*
141rfind_debug_value (const char *kvlist, const char *key, const char *fallback)
142{
143 const std::string_view sv = string_option_find_value (kvlist, key, fallback, fallback, false);
144 return sv.data();
145}
146
147static Logging logging_level = TRACE;
148
149static Logging
150parse_log_level (const char *lvl, Logging fallback)
151{
152 static const struct { const char *name; Logging level; } levels[] = {
153 { "FATAL", FATAL },
154 { "ASSERTION", ASSERTION },
155 { "HINT", HINT },
156 { "INFO", INFO },
157 { "DIAG", DIAG },
158 { "TRACE", TRACE },
159 { "DEBUGALL", DEBUGALL },
160 };
161 for (int i = 0; i < sizeof (levels) / sizeof (levels[0]); i++)
162 if (strncasecmp (lvl, levels[i].name, strlen (levels[i].name)) == 0)
163 return levels[i].level;
164 return fallback;
165}
166
167bool
168logging_configure (bool to_file, Logging level)
169{
170 logging_level = level;
171 if (logging_level < FATAL) {
172 // ASE_DEBUG={all|trace|diag|loglevel=<level>} for logging
173 if (strcmp ("0", rfind_debug_value (getenv_ase_debug(), "all", "0")) != 0)
174 logging_level = TRACE;
175 else if (strcmp ("0", rfind_debug_value (getenv_ase_debug(), "trace", "0")) != 0)
176 logging_level = TRACE;
177 else if (strcmp ("0", rfind_debug_value (getenv_ase_debug(), "diag", "0")) != 0)
178 logging_level = DIAG;
179 else
180 logging_level = parse_log_level (rfind_debug_value (getenv_ase_debug(), "loglevel", ""), INFO);
181 }
182 loging_setup();
183 if (!to_file) {
184 std::lock_guard<std::mutex> locker (logging_buffer_mutex);
185 if (logging_fd >= 0)
186 return false; // logging file already configured
187 logging_fd = -1;
188 if (logging_buffer) {
189 delete logging_buffer;
190 logging_buffer = nullptr;
191 }
192 return true;
193 }
194 const String logdir = Path::join (Path::xdg_dir ("CACHE"), "anklang");
195 const String fname = string_format ("%s/%s-%08x.log", logdir, program_alias(), gethostid());
196 const int OFLAGS = O_CREAT | O_EXCL | O_WRONLY | O_NOCTTY | O_NOFOLLOW | O_CLOEXEC; // O_TRUNC
197 const int OMODE = 0640;
198 std::lock_guard<std::mutex> locker (logging_buffer_mutex);
199 if (logging_fd >= 0)
200 return false; // logging file already configured
201 errno = EBUSY;
202 if (!Path::mkdirs (logdir)) {
203 perror (string_format ("%s: failed to open log dir \"%s\"", program_alias(), logdir.c_str()).c_str());
204 return false;
205 }
206 logging_fd = open (fname.c_str(), OFLAGS, OMODE);
207 if (logging_fd < 0 && errno == EEXIST) {
208 const String oldname = fname + ".old";
209 if (rename (fname.c_str(), oldname.c_str()) < 0) {
210 perror (string_format ("%s: failed to rename \"%s\"", program_alias(), oldname.c_str()).c_str());
211 return false;
212 }
213 logging_fd = open (fname.c_str(), OFLAGS, OMODE);
214 }
215 if (logging_fd < 0) {
216 perror (string_format ("%s: failed to open log file \"%s\"", program_alias(), fname.c_str()).c_str());
217 return false;
218 }
219 // flush buffered messages
220 if (logging_buffer) {
221 long r;
222 for (const auto &lines : *logging_buffer)
223 do
224 r = write (logging_fd, lines.data(), lines.size());
225 while (r < 0 && (errno == EINTR || errno == EAGAIN));
226 delete logging_buffer;
227 logging_buffer = nullptr;
228 }
229 return true;
230}
231
232// == Stacktrace ==
234[[maybe_unused]] static std::string
235backtrace_command (const char *dbgr)
236{
237#if 0 && defined (__linux__)
238 bool allow_ptrace = true;
239 // disabling this check, so the debugger can show an appropriate error message
240 const char *const ptrace_scope = "/proc/sys/kernel/yama/ptrace_scope";
241 int fd = open (ptrace_scope, 0);
242 char b[8] = { 0 };
243 if (read (fd, b, 8) > 0)
244 allow_ptrace = b[0] == '0';
245 close (fd);
246 if (!allow_ptrace)
247 return "";
248#endif
249 char cmd[3192];
250 const char *const usr_bin_lldb = "/usr/bin/lldb";
251 if ((!dbgr || strcmp (dbgr, "lldb") == 0) &&
252 access (usr_bin_lldb, X_OK) == 0) {
253 snprintf (cmd, sizeof (cmd),
254 "%s -Q -x --batch -p %u "
255 "-o 'bt'", // 'bt all'
256 usr_bin_lldb, gettid());
257 return cmd;
258 }
259 const char *const usr_bin_gdb = "/usr/bin/gdb";
260 if ((!dbgr || strcmp (dbgr, "gdb") == 0) &&
261 access (usr_bin_gdb, X_OK) == 0) {
262 snprintf (cmd, sizeof (cmd),
263 "%s -q -n --nx -p %u --batch "
264 "-iex 'set auto-load python-scripts off' "
265 "-iex 'set script-extension off' "
266 "-ex 'set print address off' "
267 // "-ex 'set print frame-arguments none' "
268 "-ex 'backtrace 99' " // "-ex 'thread apply all backtrace 99' "
269 ">&2 2>/dev/null",
270 usr_bin_gdb, gettid());
271 return cmd;
272 }
273 return "";
274}
275
276// == Debugging ==
279
282
283const char*
284getenv_ase_debug()
285{
286 // cache $ASE_DEBUG and setup debug_any_enabled;
287 static const char *const ase_debug = [] {
288 const char *const d = getenv ("ASE_DEBUG");
289 ase_debugging_enabled = d && d[0];
290 return d ? d : "";
291 }();
292 return ase_debug;
293}
294
296bool
297debug_key_enabled (const char *conditional) noexcept
298{
299 const std::string_view sv = string_option_find_value (getenv_ase_debug(), conditional, "0", "0", true);
300 return string_to_bool (String (sv));
301}
302
305debug_key_value (const char *conditional)
306{
307 const std::string_view sv = string_option_find_value (getenv_ase_debug(), conditional, "", "", true);
308 return String (sv);
309}
310
311#ifndef NDEBUG
312static void
313print_backtrace (FILE *stdio, const std::vector<void*> &frames)
314{
315 using namespace AnsiColors;
316 const auto G = color (FG_GREEN), U = color (FG_BLUE), E = color (FG_RED), R = color (RESET);
317 const std::size_t n = frames.size();
318 char **symbols = backtrace_symbols (&frames[0], n);
319 if (!symbols) return;
320 static constexpr size_t MAXLEN = 4096;
321 char exe[MAXLEN], symb[MAXLEN], addr[MAXLEN];
322 bool indots = false;
323 for (std::size_t i = 0; i < n; i++) {
324 if (strlen (symbols[i]) <= MAXLEN) {
325 // scan: /bin/executable(_ZMangled+0x123) [0x456]
326 if (sscanf (symbols[i], "%[^(](%[^)]) [%[^]]", exe, symb, addr) == 3) {
327 char *offs = strchr (symb, '+');
328 if (offs)
329 *offs = 0;
330 std::string demangled = symb[0] ? cxx_demangle (symb) : "";
331 if (demangled.size()) {
332 if (demangled == symb)
333 demangled += "()";
334 fprintf (stdio, "#%zu%s %s in %s%s%s from %s\n", i, i < 10 ? " " : "", (U + addr + R).c_str(), (E + demangled + R).c_str(), offs[0] ? "+" : "", offs, (G + exe + R).c_str());
335 indots = false;
336 continue;
337 }
338 }
339 }
340 if (0) { // ugly fallback
341 fprintf (stdio, "#%zu%s %s\n", i, i < 10 ? " " : "", symbols[i]);
342 continue;
343 }
344 if (!indots) {
345 fprintf (stdio, " ...\n");
346 indots = true;
347 }
348 }
349 free (symbols);
350}
351#endif // NDEBUG
352
354static void
355logging_print_backtrace (const char *func)
356{
357 fflush (stdout);
358 fflush (stderr);
359#ifndef NDEBUG
360 const char *asedebug = getenv ("ASE_DEBUG"), *btr = asedebug ? strstr (asedebug, "backtrace") : nullptr;
361 std::string xdb_cmd;
362 if (btr) {
363 const char *xdb = !strncmp (&btr[9], "=lldb", 5) ? "lldb" : !strncmp (&btr[9], "=gdb", 4) ? "gdb" : nullptr;
364 xdb_cmd = backtrace_command (xdb);
365 }
366 if (!xdb_cmd.empty()) {
367 (void) system (xdb_cmd.c_str());
368 return;
369 }
370#ifdef ASE_WITH_CPPTRACE
371 cpptrace::generate_trace().print();
372 fflush (stdout);
373 fflush (stderr);
374 return;
375#endif // ASE_WITH_CPPTRACE
376 {
377 std::vector<void*> addrs (128);
378 const int n = backtrace (&addrs[0], addrs.size());
379 addrs.resize (n);
380 if (n) {
381 fprintf (stderr, "Stack Trace (most recent call first):\n");
382 print_backtrace (stderr, addrs);
383 fflush (stdout);
384 fflush (stderr);
385 return;
386 }
387 }
388#endif // NDEBUG
389 fprintf (stderr, "Stack Trace:\n");
390 fprintf (stderr, "#0 %p in %s\n", __builtin_return_address (0), func);
391 fprintf (stderr, " ...\n");
392 fflush (stderr);
393}
394
396[[noreturn]] static void
397logging_terminate_handler()
398{
399 String msg, what;
400 if (auto eptr = std::current_exception()) {
401 try {
403 } catch (const std::exception &e) {
404 msg = "Uncaught exception";
405 what = cxx_demangle (typeid (e).name());
406 if (e.what())
407 what += std::string (": ") + e.what();
408 } catch (...) {
409 msg = "Uncaught non-std exception";
410 }
411 } else
412 msg = "Terminate called without exception";
413 {
414 using namespace AnsiColors;
415 const auto R = color (FG_RED), B = color (BOLD), S = color (RESET);
416 ScopedPosixLocale posix_locale; // use POSIX locale for this scope
417 msg = B + executable_name() + ":" + S + " " + R + msg;
418 if (what.size())
419 msg += ":" + S + " " + what;
420 else
421 msg += S;
422 }
423 fflush (stdout);
424 fputs (string_format ("%s\n", msg).c_str(), stderr);
425 logging_print_backtrace (__func__);
426 abort();
427}
428
429void
430logging_handle_terminate ()
431{
432 std::set_terminate (logging_terminate_handler);
433#ifdef ASE_WITH_CPPTRACE
434 cpptrace::register_terminate_handler();
435#endif
436}
437
438static void
439logging (Logging level, const std::string &cond, std::string message, const char *filename, uint32_t line, const char *function_name) noexcept
440{
441 const bool pabort = level == FATAL || (logging_fatal_warnings && level <= WARN);
442 if (!pabort && level > logging_level)
443 return;
444 loging_setup();
445 using namespace AnsiColors;
446 const auto C = color (FG_CYAN), G = color (BOLD, FG_GREEN), U = color (FG_BLUE), Y = color (FG_YELLOW);
447 const auto R = color (FG_RED), B = color (BOLD), S = color (RESET);
448 ScopedPosixLocale posix_locale; // use POSIX locale for this scope
449 auto location = [&] {
450 String s;
451 if (filename) {
452 s = B + filename;
453 s += ":" + string_from_uint (line) + ":" + S;
454 if (function_name)
455 s += function_name + String (":");
456 } else
457 s = B + executable_name() + ":" + S;
458 return s;
459 };
460 String logprefix = logging_timestamp (timestamp_now()) + ':', printprefix = Y + logprefix + S, kind;
461 switch (level)
462 {
463 case FATAL:
464 printprefix = "";
465 kind = location() + ' ' + B + R + "fatal:" + S;
466 break;
467 case ASSERTION:
468 printprefix = "";
469 kind = location() + ' ' + B + R + "assertion failed:" + S;
470 if (message.empty())
471 message = R + "code unreached" + S;
472 break;
473 case WARN:
474 printprefix = executable_name() + ':';
475 kind = Y + "warning:" + S;
476 break;
477 case HINT:
478 printprefix = "";
479 kind = C + "Hint:" + S;
480 break;
481 case INFO:
482 kind = executable_name() + ':';
483 break;
484 case DIAG:
485 kind = executable_name() + ':';
486 break;
487 case TRACE:
488 kind = location();
489 break;
490 case DEBUGALL:
491 kind = cond.empty() ? location() : U + cond + ':' + S;
492 break;
493 }
494 String sout = printprefix.empty() ? "" : printprefix + ' ';
495 if (kind.size())
496 sout += kind + ' ';
498 const std::string HEXINT = "0[xX][0-9abcdefABCDEF]+";
499 const std::string FULLFLOAT = "([1-9][0-9]*|0)([.][0-9]*)?([eE][+-]?[0-9]+)?";
500 const std::string FRACTFLOAT = "[.][0-9]+([eE][+-]?[0-9]+)?";
501 const std::string NUMBER = HEXINT + "|" + FULLFLOAT + "|" + FRACTFLOAT;
502 std::string w = message;
503 w = Re::sub ("=(" + NUMBER + ")", "=" + Y + "$1" + S, w);
504 w = Re::sub ("=(\"(?:[^\"\\\\]|\\\\.)*\")", "=" + U + "$1" + S, w);
505 w = Re::sub (" (\\w+)=", " " + C + "$1" + S + "=", w);
506 w = Re::sub (": ([a-zA-Z.0-9_:-]+): ", ": " + G + "$1:" + S + " ", w);
507 w = Re::sub ("^(\\d+[.]\\d+):", Y + "$1:" + S, w, Re::M);
508 sout += w;
509 } else
510 sout += message;
511 if (sout.size() && sout[sout.size()-1] != '\n')
512 sout += '\n';
513 if (pabort && level > FATAL)
514 sout += "Aborting... (fatal-warnings)\n";
515 fflush (stdout); // preserve output ordering
516 fputs (sout.c_str(), stderr);
517 fflush (stderr); // some platforms (_WIN32) don't properly flush on '\n'
518 if (logprefix.size())
519 sout = logprefix + ' ' + sout;
520 sout = Re::sub ("\\x1b\\[[0-9;]*[mK]", "", sout, Re::M); // strip ansi-colors
521 logging_to_file (pabort ? sout + logging_timestamp (timestamp_now()) + ' ' + executable_name() + ": Aborting...\n" : sout);
522 if (pabort) {
523 logging_print_backtrace (__func__);
524 abort_debug_friendly (message.c_str(), filename, line, function_name);
525 }
526}
527
528void
529logging (Logging level, const std::string &message, const char *file, uint32_t line, const char *func) noexcept
530{
531 logging (level, "", message, file, line, func);
532}
533
535void
536logging_debug (const char *cond, const std::string &message) noexcept
537{
538 return_unless (!cond || debug_key_enabled (cond));
539 logging (cond ? DEBUGALL : DIAG, cond ? cond : "", message, nullptr, 0, nullptr);
540}
541
542void
543logging_abort (Logging level, const std::string &message, const char *file, uint32_t line, const char *func) noexcept
544{
545 logging (level, "", message, file, line, func);
546 for (;;)
547 abort();
548}
549
550} // Ase
551
552
553#undef NDEBUG // enable __GLIBC__ __assert_fail()
554#include <cassert>
555namespace Ase {
556static void
557abort_debug_friendly (const char *msg, const char *file, int line, const char *func) noexcept
558{
559#if defined (_ASSERT_H_DECLS) && defined(__GLIBC__)
560 // abort via GLIBC if possible, which allows 'print __abort_msg->msg' from apport/gdb
561 __assert_fail (msg && msg[0] ? msg : "assertion unreachable\n", file, line, func);
562#endif
563 for (;;)
564 abort();
565}
566} // Ase
#define EBUSY
abort
T c_str(T... args)
static String sub(const String &regex, const String &subst, const String &input, Flags=DEFAULT)
Substitute regex in input by sbref with backreferences $00…$99 or $&.
Definition regex.cc:249
close
T current_exception(T... args)
T data(T... args)
snprintf
T empty(T... args)
errno
fflush
fputs
free
getenv
gethostid
getpid
gettimeofday
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
Definition internal.hh:73
localtime_r
std::string color(Colors acolor, Colors c1, Colors c2, Colors c3, Colors c4, Colors c5, Colors c6)
Return ANSI code for the specified color if stdout & stderr should be colorized, see colorize_tty().
Definition platform.cc:191
bool colorize_tty(int fd)
Check whether the tty fd should use colorization, checks ASE_COLOR if fd == -1.
Definition platform.cc:162
bool mkdirs(const String &dirpath, uint mode)
Create the directories in dirpath with mode, check errno on false returns.
Definition path.cc:197
The Anklang C++ API namespace.
Definition api.hh:9
std::string string_format(const char *format, const Args &...args) __attribute__((__format__(__printf__
Format a string similar to sprintf(3) with support for std::string and std::ostringstream convertible...
void stdio_flush(uint8 code, const String &txt) noexcept
Handle stdout and stderr printing with flushing.
Definition logging.cc:54
uint64_t uint64
A 64-bit unsigned integer, use PRI*64 in format strings.
Definition cxxaux.hh:25
::std::string debug_key_value(const char *conditional)
Retrieve the value assigned to debug key conditional in $ASE_DEBUG.
Definition logging.cc:305
uint8_t uint8
An 8-bit unsigned integer.
Definition cxxaux.hh:22
const char * cxx_demangle(const std::type_info &typeinfo) noexcept
Demangle a std::typeinfo.name() string into a proper C++ type name.
Definition cxxaux.cc:19
std::string executable_path()
Retrieve the path to the currently running executable.
Definition platform.cc:735
void logging_debug(const char *cond, const std::string &message) noexcept
Print a debug/diag message, called from Ase::debug().
Definition logging.cc:536
uint64_t timestamp_now()
Current time in µseconds.
Definition logging.cc:63
String string_from_uint(uint64 value)
Convert a 64bit unsigned integer into a string.
Definition strings.cc:561
bool ase_debugging_enabled
Flag to optimize checks for debugging.
Definition logging.cc:278
bool logging_fatal_warnings
Global flag to cause the program to abort on warnings.
Definition logging.cc:281
String program_alias()
Retrieve the program name as used for logging or debug messages.
Definition platform.cc:817
std::string String
Convenience alias for std::string.
Definition cxxaux.hh:35
std::string executable_name()
Retrieve the name part of executable_path().
Definition platform.cc:742
bool string_to_bool(const String &string, bool fallback)
Interpret a string as boolean value.
Definition strings.cc:467
bool debug_key_enabled(const char *conditional) noexcept
Check if conditional is enabled by $ASE_DEBUG.
Definition logging.cc:297
std::string_view string_option_find_value(const char *string, const char *feature, const char *fallback, const char *denied, bool matchallnone)
Low level option search, avoids dynamic allocations.
Definition strings.cc:1397
open
perror
T push_back(T... args)
write
T rethrow_exception(T... args)
T set_terminate(T... args)
T size(T... args)
stderr
typedef uint64_t
strchr
strftime
strncmp
strstr
typedef time_t
T what(T... args)