Spaces:
Build error
Build error
// -------------------------------- | |
// | |
// Basic usage: | |
// | |
// -------- | |
// | |
// The LOG() and LOG_TEE() macros are ready to go by default | |
// they do not require any initialization. | |
// | |
// LOGLN() and LOG_TEELN() are variants which automatically | |
// include \n character at the end of the log string. | |
// | |
// LOG() behaves exactly like printf, by default writing to a logfile. | |
// LOG_TEE() additionally, prints to the screen too ( mimics Unix tee command ). | |
// | |
// Default logfile is named | |
// "llama.<threadID>.log" | |
// Default LOG_TEE() secondary output target is | |
// stderr | |
// | |
// Logs can be dynamically disabled or enabled using functions: | |
// log_disable() | |
// and | |
// log_enable() | |
// | |
// A log target can be changed with: | |
// log_set_target( string ) | |
// creating and opening, or re-opening a file by string filename | |
// or | |
// log_set_target( FILE* ) | |
// allowing to point at stderr, stdout, or any valid FILE* file handler. | |
// | |
// -------- | |
// | |
// End of Basic usage. | |
// | |
// -------------------------------- | |
// Specifies a log target. | |
// default uses log_handler() with "llama.log" log file | |
// this can be changed, by defining LOG_TARGET | |
// like so: | |
// | |
// #define LOG_TARGET (a valid FILE*) | |
// #include "log.h" | |
// | |
// or it can be simply redirected to stdout or stderr | |
// like so: | |
// | |
// #define LOG_TARGET stderr | |
// #include "log.h" | |
// | |
// The log target can also be redirected to a diffrent function | |
// like so: | |
// | |
// #define LOG_TARGET log_handler_diffrent() | |
// #include "log.h" | |
// | |
// FILE* log_handler_diffrent() | |
// { | |
// return stderr; | |
// } | |
// | |
// or: | |
// | |
// #define LOG_TARGET log_handler_another_one("somelog.log") | |
// #include "log.h" | |
// | |
// FILE* log_handler_another_one(char*filename) | |
// { | |
// static FILE* logfile = nullptr; | |
// (...) | |
// if( !logfile ) | |
// { | |
// fopen(...) | |
// } | |
// (...) | |
// return logfile | |
// } | |
// | |
// Utility to obtain "pid" like unique process id and use it when creating log files. | |
inline std::string log_get_pid() | |
{ | |
static std::string pid; | |
if (pid.empty()) | |
{ | |
// std::this_thread::get_id() is the most portable way of obtaining a "process id" | |
// it's not the same as "pid" but is unique enough to solve multiple instances | |
// trying to write to the same log. | |
std::stringstream ss; | |
ss << std::this_thread::get_id(); | |
pid = ss.str(); | |
} | |
return pid; | |
} | |
// Utility function for generating log file names with unique id based on thread id. | |
// invocation with log_filename_generator( "llama", "log" ) creates a string "llama.<number>.log" | |
// where the number is a runtime id of the current thread. | |
// INTERNAL, DO NOT USE | |
inline std::string log_filename_generator_impl(const std::string & log_file_basename, const std::string & log_file_extension) | |
{ | |
std::stringstream buf; | |
buf << log_file_basename; | |
buf << "."; | |
buf << log_get_pid(); | |
buf << "."; | |
buf << log_file_extension; | |
return buf.str(); | |
} | |
// Utility for turning #define values into string literals | |
// so we can have a define for stderr and | |
// we can print "stderr" instead of literal stderr, etc. | |
// Allows disabling timestamps. | |
// in order to disable, define LOG_NO_TIMESTAMPS | |
// like so: | |
// | |
// #define LOG_NO_TIMESTAMPS | |
// #include "log.h" | |
// | |
// Allows disabling file/line/function prefix | |
// in order to disable, define LOG_NO_FILE_LINE_FUNCTION | |
// like so: | |
// | |
// #define LOG_NO_FILE_LINE_FUNCTION | |
// #include "log.h" | |
// | |
// Utility for synchronizing log configuration state | |
// since std::optional was introduced only in c++17 | |
enum LogTriState | |
{ | |
LogTriStateSame, | |
LogTriStateFalse, | |
LogTriStateTrue | |
}; | |
// INTERNAL, DO NOT USE | |
// USE LOG() INSTEAD | |
// | |
// INTERNAL, DO NOT USE | |
// USE LOG_TEE() INSTEAD | |
// | |
// The '\0' as a last argument, is a trick to bypass the silly | |
// "warning: ISO C++11 requires at least one argument for the "..." in a variadic macro" | |
// so we can have a single macro which can be called just like printf. | |
// Main LOG macro. | |
// behaves like printf, and supports arguments the exact same way. | |
// | |
// Main TEE macro. | |
// does the same as LOG | |
// and | |
// simultaneously writes stderr. | |
// | |
// Secondary target can be changed just like LOG_TARGET | |
// by defining LOG_TEE_TARGET | |
// | |
// LOG macro variants with auto endline. | |
// INTERNAL, DO NOT USE | |
inline FILE *log_handler1_impl(bool change = false, LogTriState disable = LogTriStateSame, const std::string & filename = LOG_DEFAULT_FILE_NAME, FILE *target = nullptr) | |
{ | |
static bool _initialized{false}; | |
static bool _disabled{(filename.empty() && target == nullptr)}; | |
static std::string log_current_filename{filename}; | |
static FILE *log_current_target{target}; | |
static FILE *logfile = nullptr; | |
if (change) | |
{ | |
if (disable == LogTriStateTrue) | |
{ | |
// Disable primary target | |
_disabled = true; | |
} | |
// If previously disabled, only enable, and keep previous target | |
else if (disable == LogTriStateFalse) | |
{ | |
_disabled = false; | |
} | |
// Otherwise, process the arguments | |
else if (log_current_filename != filename || log_current_target != target) | |
{ | |
_initialized = false; | |
} | |
} | |
if (_disabled) | |
{ | |
// Log is disabled | |
return nullptr; | |
} | |
if (_initialized) | |
{ | |
// with fallback in case something went wrong | |
return logfile ? logfile : stderr; | |
} | |
// do the (re)initialization | |
if (target != nullptr) | |
{ | |
if (logfile != nullptr && logfile != stdout && logfile != stderr) | |
{ | |
fclose(logfile); | |
} | |
log_current_filename = LOG_DEFAULT_FILE_NAME; | |
log_current_target = target; | |
logfile = target; | |
} | |
else | |
{ | |
if (log_current_filename != filename) | |
{ | |
if (logfile != nullptr && logfile != stdout && logfile != stderr) | |
{ | |
fclose(logfile); | |
} | |
} | |
logfile = fopen(filename.c_str(), "w"); | |
} | |
if (!logfile) | |
{ | |
// Verify whether the file was opened, otherwise fallback to stderr | |
logfile = stderr; | |
fprintf(stderr, "Failed to open logfile '%s' with error '%s'\n", filename.c_str(), std::strerror(errno)); | |
fflush(stderr); | |
// At this point we let the init flag be to true below, and let the target fallback to stderr | |
// otherwise we would repeatedly fopen() which was already unsuccessful | |
} | |
_initialized = true; | |
return logfile ? logfile : stderr; | |
} | |
// INTERNAL, DO NOT USE | |
inline FILE *log_handler2_impl(bool change = false, LogTriState disable = LogTriStateSame, FILE *target = nullptr, const std::string & filename = LOG_DEFAULT_FILE_NAME) | |
{ | |
return log_handler1_impl(change, disable, filename, target); | |
} | |
// Disables logs entirely at runtime. | |
// Makes LOG() and LOG_TEE() produce no output, | |
// untill enabled back. | |
// INTERNAL, DO NOT USE | |
inline FILE *log_disable_impl() | |
{ | |
return log_handler1_impl(true, LogTriStateTrue); | |
} | |
// Enables logs at runtime. | |
// INTERNAL, DO NOT USE | |
inline FILE *log_enable_impl() | |
{ | |
return log_handler1_impl(true, LogTriStateFalse); | |
} | |
// Sets target fir logs, either by a file name or FILE* pointer (stdout, stderr, or any valid FILE*) | |
// INTERNAL, DO NOT USE | |
inline FILE *log_set_target_impl(const std::string & filename) { return log_handler1_impl(true, LogTriStateSame, filename); } | |
inline FILE *log_set_target_impl(FILE *target) { return log_handler2_impl(true, LogTriStateSame, target); } | |
// INTERNAL, DO NOT USE | |
inline FILE *log_handler() { return log_handler1_impl(); } | |
inline void log_test() | |
{ | |
log_disable(); | |
LOG("01 Hello World to nobody, because logs are disabled!\n"); | |
log_enable(); | |
LOG("02 Hello World to default output, which is \"%s\" ( Yaaay, arguments! )!\n", LOG_STRINGIZE(LOG_TARGET)); | |
LOG_TEE("03 Hello World to **both** default output and " LOG_TEE_TARGET_STRING "!\n"); | |
log_set_target(stderr); | |
LOG("04 Hello World to stderr!\n"); | |
LOG_TEE("05 Hello World TEE with double printing to stderr prevented!\n"); | |
log_set_target(LOG_DEFAULT_FILE_NAME); | |
LOG("06 Hello World to default log file!\n"); | |
log_set_target(stdout); | |
LOG("07 Hello World to stdout!\n"); | |
log_set_target(LOG_DEFAULT_FILE_NAME); | |
LOG("08 Hello World to default log file again!\n"); | |
log_disable(); | |
LOG("09 Hello World _1_ into the void!\n"); | |
log_enable(); | |
LOG("10 Hello World back from the void ( you should not see _1_ in the log or the output )!\n"); | |
log_disable(); | |
log_set_target("llama.anotherlog.log"); | |
LOG("11 Hello World _2_ to nobody, new target was selected but logs are still disabled!\n"); | |
log_enable(); | |
LOG("12 Hello World this time in a new file ( you should not see _2_ in the log or the output )?\n"); | |
log_set_target("llama.yetanotherlog.log"); | |
LOG("13 Hello World this time in yet new file?\n"); | |
log_set_target(log_filename_generator("llama_autonamed", "log")); | |
LOG("14 Hello World in log with generated filename!\n"); | |
LOG_TEE("15 Hello msvc TEE without arguments\n"); | |
LOG_TEE("16 Hello msvc TEE with (%d)(%s) arguments\n", 1, "test"); | |
LOG_TEELN("17 Hello msvc TEELN without arguments\n"); | |
LOG_TEELN("18 Hello msvc TEELN with (%d)(%s) arguments\n", 1, "test"); | |
LOG("19 Hello msvc LOG without arguments\n"); | |
LOG("20 Hello msvc LOG with (%d)(%s) arguments\n", 1, "test"); | |
LOGLN("21 Hello msvc LOGLN without arguments\n"); | |
LOGLN("22 Hello msvc LOGLN with (%d)(%s) arguments\n", 1, "test"); | |
} | |
inline bool log_param_single_parse(const std::string & param) | |
{ | |
if ( param == "--log-test") | |
{ | |
log_test(); | |
return true; | |
} | |
if ( param == "--log-disable") | |
{ | |
log_disable(); | |
return true; | |
} | |
if ( param == "--log-enable") | |
{ | |
log_enable(); | |
return true; | |
} | |
return false; | |
} | |
inline bool log_param_pair_parse(bool check_but_dont_parse, const std::string & param, const std::string & next = std::string()) | |
{ | |
if ( param == "--log-file") | |
{ | |
if (!check_but_dont_parse) | |
{ | |
log_set_target(log_filename_generator(next.empty() ? "unnamed" : next, "log")); | |
} | |
return true; | |
} | |
return false; | |
} | |
inline void log_print_usage() | |
{ | |
printf("log options:\n"); | |
/* format | |
printf(" -h, --help show this help message and exit\n");*/ | |
/* spacing | |
printf("__-param----------------Description\n");*/ | |
printf(" --log-test Run simple logging test\n"); | |
printf(" --log-disable Disable trace logs\n"); | |
printf(" --log-enable Enable trace logs\n"); | |
printf(" --log-file Specify a log filename (without extension)\n"); | |
printf(" Log file will be tagged with unique ID and written as \"<name>.<ID>.log\"\n"); /* */ | |
} | |
// INTERNAL, DO NOT USE | |
inline void log_dump_cmdline_impl(int argc, char **argv) | |
{ | |
std::stringstream buf; | |
for (int i = 0; i < argc; ++i) | |
{ | |
if (std::string(argv[i]).find(' ') != std::string::npos) | |
{ | |
buf << " \"" << argv[i] <<"\""; | |
} | |
else | |
{ | |
buf << " " << argv[i]; | |
} | |
} | |
LOGLN("Cmd:%s", buf.str().c_str()); | |
} | |
inline std::string log_var_to_string_impl(bool var) | |
{ | |
return var ? "true" : "false"; | |
} | |
inline std::string log_var_to_string_impl(std::string var) | |
{ | |
return var; | |
} | |
inline std::string log_var_to_string_impl(const std::vector<int> & var) | |
{ | |
std::stringstream buf; | |
buf << "[ "; | |
bool first = true; | |
for (auto e : var) | |
{ | |
if (first) | |
{ | |
first = false; | |
} | |
else | |
{ | |
buf << ", "; | |
} | |
buf << std::to_string(e); | |
} | |
buf << " ]"; | |
return buf.str(); | |
} | |