#include "log.hpp" #include #include #include "util.hpp" #ifdef __ANDROID__ #include #endif #include using namespace std::filesystem; namespace Looper::Log { const std::locale LogStream::c_locale = std::locale("C"); std::set LogStream::global_outputs; int LogStream::log_level = #ifdef DEBUG_MODE -2; #else 0; #endif std::set LogStream::get_used_outputs() { std::set used_outputs; if (my_log_level >= log_level) { for (auto my_output : outputs) { used_outputs.insert(my_output); } } for (auto global_output : global_outputs) { used_outputs.insert(global_output); } return used_outputs; } std::recursive_mutex log_mutex; void LogStream::_writec(const char chr) { if (nested) { for (auto &stream : streams) { stream->writec(chr); } } else { #ifdef __ANDROID__ bool is_newline = chr == '\n' || chr == '\r'; if (!is_newline) { line += chr; } else { for (auto logPriority : android_outputs) { __android_log_print(logPriority, "Looper", "%s", line.c_str()); } } #endif std::set used_outputs = get_used_outputs(); for (auto &file : used_outputs) { fwrite(&chr, 1, 1, file); fflush(file); } } } void LogStream::writec(const char chr) { log_mutex_guard guard(log_mutex); bool is_newline = (chr == '\n' || chr == '\r'); writeprefix(); _writec(chr); if (is_newline) { need_prefix = true; } } void LogStream::writeprefix() { log_mutex_guard guard(log_mutex); if (need_prefix) { need_prefix = false; for (auto name : names) { writec('['); writes(name); writes("] "); } } } void LogStream::writes(const char *msg) { log_mutex_guard guard(log_mutex); while (*msg != '\0') { writec(*(msg++)); } } void LogStream::writesn(const char *msg, size_t n) { log_mutex_guard guard(log_mutex); for (size_t i = 0; i < n && msg[i] != '\0'; i++) { writec(msg[i]); } } void LogStream::writes(std::string msg) { LogStream::writesn(msg.c_str(), msg.size()); } void LogStream::writeln(const char *msg) { log_mutex_guard guard(log_mutex); LogStream::writes(msg); LogStream::writec('\n'); } void LogStream::writeln_n(const char *msg, size_t n) { log_mutex_guard guard(log_mutex); LogStream::writesn(msg, n); LogStream::writec('\n'); } void LogStream::writeln(std::string msg) { log_mutex_guard guard(log_mutex); LogStream::writes(msg); LogStream::writec('\n'); } void LogStream::writef(const char *fmt, ...) { log_mutex_guard guard(log_mutex); va_list args; va_start(args, fmt); vwritef(fmt, args); va_end(args); } void LogStream::vwritef(const char *fmt, va_list args) { log_mutex_guard guard(log_mutex); const char *buf = vcformat(fmt, args); va_end(args); if (buf == NULL) { throw std::exception(); } LogStream::writes(buf); free((void*)buf); } void LogStream::vwritefln(const char *fmt, va_list args) { log_mutex_guard guard(log_mutex); vwritef(fmt, args); writec('\n'); } void LogStream::writefln(const char *fmt, ...) { log_mutex_guard guard(log_mutex); va_list args; va_start(args, fmt); vwritefln(fmt, args); va_end(args); } void LogStream::write_level_prefix(size_t level) { log_mutex_guard guard(log_mutex); for (size_t i = 0; i < level; i++) { writes(" "); } } void LogStream::write_level(size_t level, const char *msg) { log_mutex_guard guard(log_mutex); write_level_prefix(level); writeln(msg); } void LogStream::write_level(size_t level, std::string msg) { log_mutex_guard guard(log_mutex); write_level_prefix(level); writeln(msg); } void LogStream::vwritef_level(size_t level, const char *fmt, va_list args) { log_mutex_guard guard(log_mutex); write_level_prefix(level); vwritefln(fmt, args); } void LogStream::writef_level(size_t level, const char *fmt, ...) { log_mutex_guard guard(log_mutex); va_list args; va_start(args, fmt); vwritef_level(level, fmt, args); va_end(args); } LogStream::LogStream(std::initializer_list names, int log_level, bool nested, void *discriminator) : names(names), need_prefix(true), my_log_level(log_level), nested(nested) { } LogStream &LogStream::substream(std::string name) { if (!this->substreams.contains(name)) { this->substreams[name] = new LogStream({name}, {this}, my_log_level); } return *this->substreams[name]; } LogStream::LogStream(std::initializer_list names, std::initializer_list streams, int log_level) : LogStream(names, log_level, true, nullptr) { this->streams = std::set(streams); } #ifdef __ANDROID__ LogStream::LogStream(std::initializer_list names, std::initializer_list> outputs, int log_level) #else LogStream::LogStream(std::initializer_list names, std::initializer_list outputs, int log_level) #endif : LogStream(names, log_level, false, nullptr) { #ifdef __ANDROID__ std::set file_outputs; std::set android_outputs; for (auto output : outputs) { android_LogPriority *logPriority = std::get_if(&output); FILE **file = std::get_if(&output); if (logPriority != nullptr) { android_outputs.insert(*logPriority); } else if (file != nullptr){ file_outputs.insert(*file); } } this->android_outputs = android_outputs; this->outputs = file_outputs; #else this->outputs = std::set(outputs); #endif } LogStreamMap log_streams; void make_log_stream(int stream_id, log_stream_creation_function_t fn) { if (!log_streams.contains(stream_id)) { log_streams[stream_id] = std::stack(); } LogStream *new_stream = fn(stream_id); log_streams[stream_id].push(new_stream); } std::recursive_mutex log_stream_mutex; void init_logging_custom(log_stream_creation_function_t fn) { std::lock_guard guard(log_stream_mutex); make_log_stream(-2, fn); make_log_stream(-1, fn); make_log_stream(0, fn); make_log_stream(1, fn); make_log_stream(2, fn); } void pop_log_stream(int i) { log_streams[i].pop(); } void pop_log_streams() { std::lock_guard guard(log_stream_mutex); pop_log_stream(-2); pop_log_stream(-1); pop_log_stream(0); pop_log_stream(1); pop_log_stream(2); } LogStream *_init_logging_normal(int id) { std::string name = get_log_name_by_idx(id); #ifdef __ANDROID__ std::map stream_files = { {-2, ANDROID_LOG_DEBUG}, {-1, ANDROID_LOG_DEBUG}, {0, ANDROID_LOG_INFO}, {1, ANDROID_LOG_WARN}, {2, ANDROID_LOG_ERROR} }; #else std::map stream_files = { {-2, stderr}, {-1, stderr}, {0, stdout}, {1, stderr}, {2, stderr} }; #endif return new LogStream({name}, {stream_files[id]}, id); } std::string get_log_name_by_idx(int idx) { static std::map stream_names = { {-2, "TRACE"}, {-1, "DEBUG"}, {0, "INFO"}, {1, "WARNING"}, {2, "ERROR"}, {3, "FATAL"} }; return stream_names[idx]; } static FILE *log_file; void deinit_logging() { fclose(log_file); } void init_logging() { path prefs_path(get_prefs_path()); path log_dir = prefs_path / path("looper") / path("logs"); create_directories(log_dir); log_file = fopen((log_dir / path("looper.log")).c_str(), "a"); LogStream::global_outputs.insert(log_file); init_logging_custom(_init_logging_normal); atexit(deinit_logging); } LogStream &get_log_stream_by_level(int level) { return *log_streams[level].top(); } } extern "C" void write_log(int level, const char *log) { LOG(level).writes(log); } extern "C" void write_logln(int level, const char *log) { LOG(level).writeln(log); }