// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/logging.h" #include "td/utils/ExitGuard.h" #include "td/utils/port/Clocks.h" #include "td/utils/port/StdStreams.h" #include "td/utils/port/thread_local.h" #include "td/utils/Slice.h" #include "td/utils/Time.h" #include #include #include #include #if TD_ANDROID #include #define ALOG_TAG "DLTD" #elif TD_TIZEN #include #define DLOG_TAG "DLTD" #elif TD_EMSCRIPTEN #include #endif namespace td { LogOptions log_options; TD_THREAD_LOCAL const char *Logger::tag_ = nullptr; TD_THREAD_LOCAL const char *Logger::tag2_ = nullptr; Logger::Logger(LogInterface &log, const LogOptions &options, int log_level, Slice file_name, int line_num, Slice comment) : Logger(log, options, log_level) { if (log_level == VERBOSITY_NAME(PLAIN) && &options == &log_options) { return; } if (!options_.add_info) { return; } if (ExitGuard::is_exited()) { return; } // log level sb_ << '['; if (static_cast(log_level) < 10) { sb_ << ' ' << static_cast('0' + log_level); } else { sb_ << log_level; } sb_ << ']'; // thread id auto thread_id = get_thread_id(); sb_ << "[t"; if (static_cast(thread_id) < 10) { sb_ << ' ' << static_cast('0' + thread_id); } else { sb_ << thread_id; } sb_ << ']'; // timestamp auto time = Clocks::system(); auto unix_time = static_cast(time); auto nanoseconds = static_cast((time - unix_time) * 1e9); sb_ << '[' << unix_time << '.'; uint32 limit = 100000000; while (nanoseconds < limit && limit > 1) { sb_ << '0'; limit /= 10; } sb_ << nanoseconds << ']'; // file : line if (!file_name.empty()) { auto last_slash_ = static_cast(file_name.size()) - 1; while (last_slash_ >= 0 && file_name[last_slash_] != '/' && file_name[last_slash_] != '\\') { last_slash_--; } file_name = file_name.substr(last_slash_ + 1); sb_ << '[' << file_name << ':' << static_cast(line_num) << ']'; } // context from tag_ if (tag_ != nullptr && *tag_) { sb_ << "[#" << Slice(tag_) << ']'; } // context from tag2_ if (tag2_ != nullptr && *tag2_) { sb_ << "[!" << Slice(tag2_) << ']'; } // comment (e.g. condition in LOG_IF) if (!comment.empty()) { sb_ << "[&" << comment << ']'; } sb_ << '\t'; } Logger::~Logger() { if (ExitGuard::is_exited()) { return; } if (options_.fix_newlines) { sb_ << '\n'; auto slice = as_cslice(); if (slice.back() != '\n') { slice.back() = '\n'; } while (slice.size() > 1 && slice[slice.size() - 2] == '\n') { slice.back() = '\0'; slice = MutableCSlice(slice.begin(), slice.begin() + slice.size() - 1); } log_.append(slice, log_level_); } else { log_.append(as_cslice(), log_level_); } } TsCerr::TsCerr() { enterCritical(); } TsCerr::~TsCerr() { exitCritical(); } TsCerr &TsCerr::operator<<(Slice slice) { auto &fd = Stderr(); if (fd.empty()) { return *this; } double end_time = 0; while (!slice.empty()) { auto res = fd.write(slice); if (res.is_error()) { if (res.error().code() == EPIPE) { break; } // Resource temporary unavailable if (end_time == 0) { end_time = Time::now() + 0.01; } else if (Time::now() > end_time) { break; } continue; } slice.remove_prefix(res.ok()); } return *this; } void TsCerr::enterCritical() { while (lock_.test_and_set(std::memory_order_acquire) && !ExitGuard::is_exited()) { // spin } } void TsCerr::exitCritical() { lock_.clear(std::memory_order_release); } TsCerr::Lock TsCerr::lock_ = ATOMIC_FLAG_INIT; void TsLog::enter_critical() { while (lock_.test_and_set(std::memory_order_acquire) && !ExitGuard::is_exited()) { // spin } } void TsLog::exit_critical() { lock_.clear(std::memory_order_release); } class DefaultLog : public LogInterface { public: void append(CSlice slice, int log_level) override { #if TD_ANDROID switch (log_level) { case VERBOSITY_NAME(FATAL): __android_log_write(ANDROID_LOG_FATAL, ALOG_TAG, slice.c_str()); break; case VERBOSITY_NAME(ERROR): __android_log_write(ANDROID_LOG_ERROR, ALOG_TAG, slice.c_str()); break; case VERBOSITY_NAME(WARNING): __android_log_write(ANDROID_LOG_WARN, ALOG_TAG, slice.c_str()); break; case VERBOSITY_NAME(INFO): __android_log_write(ANDROID_LOG_INFO, ALOG_TAG, slice.c_str()); break; default: __android_log_write(ANDROID_LOG_DEBUG, ALOG_TAG, slice.c_str()); break; } #elif TD_TIZEN switch (log_level) { case VERBOSITY_NAME(FATAL): dlog_print(DLOG_ERROR, DLOG_TAG, "%s", slice.c_str()); break; case VERBOSITY_NAME(ERROR): dlog_print(DLOG_ERROR, DLOG_TAG, "%s", slice.c_str()); break; case VERBOSITY_NAME(WARNING): dlog_print(DLOG_WARN, DLOG_TAG, "%s", slice.c_str()); break; case VERBOSITY_NAME(INFO): dlog_print(DLOG_INFO, DLOG_TAG, "%s", slice.c_str()); break; default: dlog_print(DLOG_DEBUG, DLOG_TAG, "%s", slice.c_str()); break; } #elif TD_EMSCRIPTEN switch (log_level) { case VERBOSITY_NAME(FATAL): emscripten_log( EM_LOG_ERROR | EM_LOG_CONSOLE | EM_LOG_C_STACK | EM_LOG_JS_STACK | EM_LOG_DEMANGLE | EM_LOG_FUNC_PARAMS, "%s", slice.c_str()); EM_ASM(throw(UTF8ToString($0)), slice.c_str()); break; case VERBOSITY_NAME(ERROR): emscripten_log(EM_LOG_ERROR | EM_LOG_CONSOLE, "%s", slice.c_str()); break; case VERBOSITY_NAME(WARNING): emscripten_log(EM_LOG_WARN | EM_LOG_CONSOLE, "%s", slice.c_str()); break; default: emscripten_log(EM_LOG_CONSOLE, "%s", slice.c_str()); break; } #elif !TD_WINDOWS Slice color; switch (log_level) { case VERBOSITY_NAME(FATAL): case VERBOSITY_NAME(ERROR): color = Slice(TC_RED); break; case VERBOSITY_NAME(WARNING): color = Slice(TC_YELLOW); break; case VERBOSITY_NAME(INFO): color = Slice(TC_CYAN); break; } if (!slice.empty() && slice.back() == '\n') { TsCerr() << color << slice.substr(0, slice.size() - 1) << TC_EMPTY "\n"; } else { TsCerr() << color << slice << TC_EMPTY; } #else // TODO: color TsCerr() << slice; #endif if (log_level == VERBOSITY_NAME(FATAL)) { process_fatal_error(slice); } } void rotate() override { } }; static DefaultLog default_log; LogInterface *const default_log_interface = &default_log; LogInterface *log_interface = default_log_interface; static OnFatalErrorCallback on_fatal_error_callback = nullptr; static bool use_death_handler = true; void set_log_fatal_error_callback(OnFatalErrorCallback callback) { on_fatal_error_callback = callback; } void set_log_disable_death_handler(bool disabled) { use_death_handler = !disabled; } void process_fatal_error(CSlice message) { auto callback = on_fatal_error_callback; if (callback) { callback(message); } std::abort(); } namespace { std::mutex sdl_mutex; int sdl_cnt = 0; int sdl_verbosity = 0; } // namespace ScopedDisableLog::ScopedDisableLog() { std::unique_lock guard(sdl_mutex); if (sdl_cnt == 0) { sdl_verbosity = set_verbosity_level(std::numeric_limits::min()); } sdl_cnt++; } ScopedDisableLog::~ScopedDisableLog() { std::unique_lock guard(sdl_mutex); sdl_cnt--; if (sdl_cnt == 0) { set_verbosity_level(sdl_verbosity); } } static ExitGuard exit_guard; } // namespace td