#define SATDUMP_DLL_EXPORT 1 #include "logger.h" #include #include #include #include #ifdef __ANDROID__ #include static char ag_LogTag[] = "SatDump"; #endif #if defined(_WIN32) #include #include #endif #include "init.h" // Logger and sinks. We got a console sink and file sink #ifdef __ANDROID__ std::shared_ptr console_sink; #elif defined(_WIN32) std::shared_ptr console_sink; #else std::shared_ptr console_sink; #endif std::shared_ptr file_sink; SATDUMP_DLL std::shared_ptr logger; // We need a place to buffer log messages during initialization // to make sure the messages end up in log sinks added later in // the initialization process std::vector init_log_buffer; bool init_buffer_active = true; namespace slog { const std::string log_schar[] = {"T", "D", "I", "W", "E", "C"}; const std::string colors[] = {"\033[37m", "\033[36m", "\033[32m", "\033[33m\033[1m", "\033[31m\033[1m", "\033[1m\033[41m"}; template std::string vformat(const char *fmt, T &&...args) { // Allocate a buffer on the stack that's big enough for us almost // all the time. size_t size = 1024; std::vector buf; buf.resize(size); // Try to vsnprintf into our buffer. size_t needed = snprintf((char *)&buf[0], size, fmt, args...); // NB. On Windows, vsnprintf returns -1 if the string didn't fit the // buffer. On Linux & OSX, it returns the length it would have needed. if (needed <= size) { // It fit fine the first time, we're done. return std::string(&buf[0]); } else { // vsnprintf reported that it wanted to write more characters // than we allotted. So do a malloc of the right size and try again. // This doesn't happen very often if we chose our initial size // well. size = needed; buf.resize(size); needed = snprintf((char *)&buf[0], size, fmt, args...); return std::string(&buf[0]); } } std::string LoggerSink::format_log(LogMsg m, bool color, int *cpos) { time_t ct = time(0); std::tm *tmr = gmtime(&ct); std::string timestamp = (tmr->tm_hour < 10 ? "0" : "") + std::to_string(tmr->tm_hour) + ":" + // Hour (tmr->tm_min < 10 ? "0" : "") + std::to_string(tmr->tm_min) + ":" + // Min (tmr->tm_sec < 10 ? "0" : "") + std::to_string(tmr->tm_sec) + " - " + // Sec (tmr->tm_mday < 10 ? "0" : "") + std::to_string(tmr->tm_mday) + "/" + // Day (tmr->tm_mon + 1 < 10 ? "0" : "") + std::to_string(tmr->tm_mon + 1) + "/" + // Mon (tmr->tm_year < 10 ? "0" : "") + std::to_string(tmr->tm_year + 1900); // Year if (cpos != nullptr) *cpos = timestamp.size() + 3; if (color) return vformat("[%s] %s(%s) %s\033[m\n", timestamp.c_str(), colors[m.lvl].c_str(), log_schar[m.lvl].c_str(), m.str.c_str()); else return vformat("[%s] (%s) %s\n", timestamp.c_str(), log_schar[m.lvl].c_str(), m.str.c_str()); } void StdOutSink::receive(LogMsg log) { if (log.lvl >= sink_lvl) { std::string s = format_log(log, true); fwrite(s.c_str(), sizeof(char), s.size(), stderr); } } #if defined(_WIN32) const int colors_win[] = {FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, FOREGROUND_GREEN | FOREGROUND_BLUE, FOREGROUND_GREEN, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, FOREGROUND_RED | FOREGROUND_INTENSITY, BACKGROUND_RED | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY}; int win_set_foreground_color(HANDLE &out_handle_, int attribs) { CONSOLE_SCREEN_BUFFER_INFO orig_buffer_info; if (!::GetConsoleScreenBufferInfo(out_handle_, &orig_buffer_info)) { // just return white if failed getting console info return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; } // change only the foreground bits (lowest 4 bits) auto new_attribs = static_cast(attribs) | (orig_buffer_info.wAttributes & 0xfff0); auto ignored = ::SetConsoleTextAttribute(out_handle_, static_cast(new_attribs)); (void)(ignored); return orig_buffer_info.wAttributes; // return orig attribs } void WinOutSink::receive(LogMsg log) { if (log.lvl >= sink_lvl) { int color_pos = 0; std::string s = format_log(log, false, &color_pos); std::string s1 = s.substr(0, color_pos); std::string s2 = s.substr(color_pos, s.size() - color_pos - 1); std::string s3 = s.substr(s.size() - 1, 1); HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); fwrite(s1.c_str(), sizeof(char), s1.size(), stderr); int rst = win_set_foreground_color(hConsole, colors_win[log.lvl]); fwrite(s2.c_str(), sizeof(char), s2.size(), stderr); SetConsoleTextAttribute(hConsole, rst); fwrite(s3.c_str(), sizeof(char), s3.size(), stderr); } } #endif FileSink::FileSink(std::string path) { int suffix = 0; std::string log_path = path + ".log"; while (std::filesystem::exists(log_path)) { suffix++; log_path = path + "-" + std::to_string(suffix) + ".log"; } outf = std::ofstream(log_path); } FileSink::~FileSink() { outf.close(); } void FileSink::receive(LogMsg log) { if (log.lvl >= sink_lvl) { std::string s = format_log(log, false); outf.write(s.c_str(), s.size()); outf.flush(); // Too annoying when there's a segault not letting it flush } } void Logger::log(LogLevel lvl, std::string v) { sink_mtx.lock(); LogMsg m; m.str = v; m.lvl = lvl; // Push to init buffer to dump to sinks added later if (init_buffer_active) { // Once 1000 messages have been logged, assume all sinks have // been attached and we were not notified. Stop buffering if (init_log_buffer.size() >= 1000) completeLoggerInit(); else init_log_buffer.push_back(m); } // Push to current sinks if (m.lvl >= logger_lvl) for (auto &l : sinks) l->receive(m); sink_mtx.unlock(); } void Logger::logf(LogLevel lvl, std::string fmt, va_list args) { std::string output; output.resize(1024); va_list argscopy; va_copy(argscopy, args); size_t size = vsnprintf((char *)output.c_str(), output.size(), fmt.c_str(), args); if (size <= output.size()) { log(lvl, output); } else { output.resize(size + 1); size = vsnprintf((char *)output.c_str(), output.size(), fmt.c_str(), argscopy); log(lvl, output); } } #ifdef __ANDROID__ const android_LogPriority log_lvls_a[] = { ANDROID_LOG_VERBOSE, ANDROID_LOG_DEBUG, ANDROID_LOG_INFO, ANDROID_LOG_WARN, ANDROID_LOG_ERROR, ANDROID_LOG_FATAL, }; void AndroidSink::receive(LogMsg log) { if (log.lvl >= sink_lvl) { std::string s = format_log(log, false); __android_log_print(log_lvls_a[log.lvl], ag_LogTag, "%s", log.str.c_str()); } } #endif void Logger::set_level(LogLevel lvl) { logger_lvl = lvl; } LogLevel Logger::get_level() { return logger_lvl; } void Logger::add_sink(std::shared_ptr sink) { sink_mtx.lock(); for (LogMsg &msg : init_log_buffer) sink->receive(msg); sinks.push_back(sink); sink_mtx.unlock(); } void Logger::del_sink(std::shared_ptr sink) { sink_mtx.lock(); auto it = std::find_if(sinks.begin(), sinks.end(), [&sink](std::shared_ptr &c) { return sink.get() == c.get(); }); if (it != sinks.end()) sinks.erase(it); sink_mtx.unlock(); } } void initLogger() { try { // Initialize everything #ifdef __ANDROID__ console_sink = std::make_shared(); #elif defined(_WIN32) console_sink = std::make_shared(); #else console_sink = std::make_shared(); #endif logger = std::make_shared(); logger->set_level(slog::LOG_TRACE); console_sink->set_level(slog::LOG_TRACE); logger->add_sink(console_sink); } catch (std::exception &e) { std::cout << e.what() << std::endl; exit(1); } } void initFileSink() { try { // Make sure the user folder is created if (!std::filesystem::exists(std::filesystem::path(satdump::user_path + "/"))) std::filesystem::create_directories(std::filesystem::path(satdump::user_path + "/")); // Clear old logs char timebuffer[16]; struct tm *timeinfo; time_t current_time = time(NULL); timeinfo = localtime(¤t_time); strftime(timebuffer, sizeof(timebuffer), "%Y%m%d", timeinfo); int delete_before = atoi(timebuffer) - 2; for (const auto &user_file : std::filesystem::directory_iterator(satdump::user_path + "/")) { std::string file_name = user_file.path().filename().string(); if (file_name.size() < 26) continue; int log_date = atoi(file_name.substr(8, 8).c_str()); if (log_date != 0 && log_date < delete_before) std::filesystem::remove(user_file); } // Create new log strftime(timebuffer, sizeof(timebuffer), "%Y%m%dT%H%M%S", timeinfo); file_sink = std::make_shared(satdump::user_path + "/satdump-" + std::string(timebuffer)); logger->add_sink(file_sink); // file_sink->set_pattern("[%D - %T] (%L) %v"); file_sink->set_level(slog::LOG_TRACE); } catch (std::exception &e) { logger->error("Error initializing log file: %s", e.what()); return; } } void completeLoggerInit() { init_log_buffer.clear(); init_buffer_active = false; } void setConsoleLevel(slog::LogLevel level) { // Just change our log level console_sink->set_level(level); }