diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f2703a5..e03962b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -652,7 +652,7 @@ if (NOT CMAKE_CROSSCOMPILING) target_include_directories(tg_cli SYSTEM PRIVATE ${READLINE_INCLUDE_DIR}) target_compile_definitions(tg_cli PRIVATE -DUSE_READLINE=1) endif() - target_link_libraries(tg_cli PRIVATE memprof tdcore tdtl) + target_link_libraries(tg_cli PRIVATE memprof tdclient tdcore tdtl) add_dependencies(tg_cli tl_generate_json) endif() diff --git a/td/telegram/Log.cpp b/td/telegram/Log.cpp index a6b6e672..4c8a28b9 100644 --- a/td/telegram/Log.cpp +++ b/td/telegram/Log.cpp @@ -17,6 +17,12 @@ namespace td { static FileLog file_log; static TsLog ts_log(&file_log); static int64 max_log_file_size = 10 << 20; +static Log::FatalErrorCallbackPtr fatal_error_callback; + +static void fatal_error_callback_wrapper(CSlice message) { + CHECK(fatal_error_callback != nullptr); + fatal_error_callback(message.c_str()); +} void Log::set_file_path(string file_path) { if (file_path.empty()) { @@ -37,4 +43,14 @@ void Log::set_verbosity_level(int new_verbosity_level) { SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + new_verbosity_level); } +void Log::set_fatal_error_callback(FatalErrorCallbackPtr callback) { + if (callback == nullptr) { + fatal_error_callback = nullptr; + set_log_fatal_error_callback(nullptr); + } else { + fatal_error_callback = callback; + set_log_fatal_error_callback(fatal_error_callback_wrapper); + } +} + } // namespace td diff --git a/td/telegram/Log.h b/td/telegram/Log.h index fcd43aee..f7219158 100644 --- a/td/telegram/Log.h +++ b/td/telegram/Log.h @@ -56,6 +56,24 @@ class Log { * value greater than 5 and up to 1024 can be used to enable even more logging. */ static void set_verbosity_level(int new_verbosity_level); + + /** + * A type of callback function that will be called when a fatal error happens. + * + * \param error_message Null-terminated string with a description of a happened fatal error. + */ + using FatalErrorCallbackPtr = void (*)(const char *error_message); + + /** + * Sets the callback that will be called when a fatal error happens. + * None of the TDLib methods can be called from the callback. + * The TDLib will crash as soon as callback returns. + * By default the callback is not set. + * + * \param[in] callback Callback that will be called when a fatal error happens. + * Pass nullptr to remove the callback. + */ + static void set_fatal_error_callback(FatalErrorCallbackPtr callback); }; } // namespace td diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index ed9be04d..40e78db1 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -5,6 +5,7 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/telegram/ClientActor.h" +#include "td/telegram/Log.h" #include "td/telegram/td_api_json.h" @@ -42,6 +43,7 @@ #include #include // for strcmp #include +#include #include #include #include @@ -56,7 +58,7 @@ namespace td { -void dump_memory_usage() { +static void dump_memory_usage() { if (is_memprof_on()) { LOG(WARNING) << "memory_dump"; clear_thread_locals(); @@ -87,7 +89,7 @@ static int32 saved_point; static string saved_line; static std::atomic_flag readline_lock = ATOMIC_FLAG_INIT; -void deactivate_readline() { +static void deactivate_readline() { while (readline_lock.test_and_set(std::memory_order_acquire)) { // spin } @@ -100,7 +102,7 @@ void deactivate_readline() { rl_redisplay(); } -void reactivate_readline() { +static void reactivate_readline() { rl_set_prompt(prompt); rl_replace_line(saved_line.c_str(), 0); rl_point = saved_point; @@ -109,7 +111,7 @@ void reactivate_readline() { readline_lock.clear(std::memory_order_release); } -char *command_generator(const char *text, int state) { +static char *command_generator(const char *text, int state) { static vector commands{"GetContacts", "GetChats", "GetHistory", @@ -162,7 +164,7 @@ char *command_generator(const char *text, int state) { return nullptr; } -char **tg_cli_completion(const char *text, int start, int end) { +static char **tg_cli_completion(const char *text, int start, int end) { char **matches = nullptr; if (start == 0) { matches = rl_completion_matches(text, command_generator); @@ -2751,6 +2753,10 @@ class CliClient final : public Actor { quit(); } else if (op == "dnq" || op == "DumpNetQueries") { dump_pending_network_queries(); + } else if (op == "fatal") { + LOG(FATAL) << "Fatal!"; + } else if (op == "unreachable") { + UNREACHABLE(); } else { op_not_found_count++; } @@ -2864,26 +2870,31 @@ class CliClient final : public Actor { }; CliClient *CliClient::instance_ = nullptr; -void quit() { +static void quit() { CliClient::quit_instance(); } -void fail_signal(int sig) { +static void fail_signal(int sig) { signal_safe_write_signal_number(sig); while (true) { // spin forever to allow debugger to attach } } -void usage() { +static void usage() { //TODO: } +static void on_fatal_error(const char *error) { + std::cerr << "Fatal error: " << error << std::endl; +} + void main(int argc, char **argv) { ignore_signal(SignalType::HangUp).ensure(); ignore_signal(SignalType::Pipe).ensure(); set_signal_handler(SignalType::Error, fail_signal).ensure(); set_signal_handler(SignalType::Abort, fail_signal).ensure(); + td::Log::set_fatal_error_callback(on_fatal_error); CliLog cli_log; log_interface = &cli_log; diff --git a/td/telegram/td_log.cpp b/td/telegram/td_log.cpp index f82cd166..0bffe0c7 100644 --- a/td/telegram/td_log.cpp +++ b/td/telegram/td_log.cpp @@ -8,6 +8,8 @@ #include "td/telegram/Log.h" +#include + void td_set_log_file_path(const char *file_path) { td::Log::set_file_path(file_path == nullptr ? "" : file_path); } @@ -19,3 +21,7 @@ void td_set_log_max_file_size(long long max_file_size) { void td_set_log_verbosity_level(int new_verbosity_level) { td::Log::set_verbosity_level(new_verbosity_level); } + +void td_set_log_fatal_error_callback(td_log_fatal_error_callback_ptr callback) { + td::Log::set_fatal_error_callback(callback); +} diff --git a/td/telegram/td_log.h b/td/telegram/td_log.h index d3794edf..7aa8549c 100644 --- a/td/telegram/td_log.h +++ b/td/telegram/td_log.h @@ -23,8 +23,8 @@ extern "C" { * By default TDLib writes logs to stderr or an OS specific log. * Use this method to write the log to a file instead. * - * \param[in] file_path Path to a file where the internal TDLib log will be written. Use an empty path to - * switch back to the default logging behaviour. + * \param[in] file_path Null-terminated path to a file where the internal TDLib log will be written. + * Use an empty path to switch back to the default logging behaviour. */ TDJSON_EXPORT void td_set_log_file_path(const char *file_path); @@ -52,6 +52,24 @@ TDJSON_EXPORT void td_set_log_max_file_size(long long max_file_size); */ TDJSON_EXPORT void td_set_log_verbosity_level(int new_verbosity_level); +/** + * A type of callback function that will be called when a fatal error happens. + * + * \param error_message Null-terminated string with a description of a happened fatal error. + */ +using td_log_fatal_error_callback_ptr = void (*)(const char *error_message); + +/** + * Sets the callback that will be called when a fatal error happens. + * None of the TDLib methods can be called from the callback. + * The TDLib will crash as soon as callback returns. + * By default the callback is not set. + * + * \param[in] callback Callback that will be called when a fatal error happens. + * Pass NULL to remove the callback. + */ +TDJSON_EXPORT void td_set_log_fatal_error_callback(td_log_fatal_error_callback_ptr callback); + #ifdef __cplusplus } // extern "C" #endif diff --git a/tdclientjson_export_list b/tdclientjson_export_list index bb23be91..f1db3603 100644 --- a/tdclientjson_export_list +++ b/tdclientjson_export_list @@ -6,3 +6,4 @@ _td_json_client_execute _td_set_log_file_path _td_set_log_max_file_size _td_set_log_verbosity_level +_td_set_log_fatal_error_callback diff --git a/tdutils/td/utils/FileLog.h b/tdutils/td/utils/FileLog.h index 0520d025..7ca4249d 100644 --- a/tdutils/td/utils/FileLog.h +++ b/tdutils/td/utils/FileLog.h @@ -13,7 +13,6 @@ #include "td/utils/port/path.h" #include "td/utils/Slice.h" -#include #include namespace td { @@ -23,25 +22,25 @@ class FileLog : public LogInterface { static constexpr int DEFAULT_ROTATE_THRESHOLD = 10 * (1 << 20); public: - void append(CSlice xslice, int log_level) override { - Slice slice = xslice; + void append(CSlice cslice, int log_level) override { + Slice slice = cslice; while (!slice.empty()) { auto r_size = fd_.write(slice); if (r_size.is_error()) { - std::abort(); + process_fatal_error(r_size.error().message()); } auto written = r_size.ok(); size_ += static_cast(written); slice.remove_prefix(written); } if (log_level == VERBOSITY_NAME(FATAL)) { - std::abort(); + process_fatal_error(cslice); } if (size_ > rotate_threshold_) { auto status = rename(path_, path_ + ".old"); if (status.is_error()) { - std::abort(); + process_fatal_error(status.message()); } do_rotate(); } @@ -83,7 +82,7 @@ class FileLog : public LogInterface { fd_.close(); auto r_fd = FileFd::open(path_, FileFd::Create | FileFd::Truncate | FileFd::Write); if (r_fd.is_error()) { - std::abort(); + process_fatal_error(r_fd.error().message()); } fd_ = r_fd.move_as_ok(); Fd::duplicate(fd_.get_fd(), Fd::Stderr()).ignore(); diff --git a/tdutils/td/utils/MemoryLog.h b/tdutils/td/utils/MemoryLog.h index 113a46cc..7b0eb39c 100644 --- a/tdutils/td/utils/MemoryLog.h +++ b/tdutils/td/utils/MemoryLog.h @@ -57,6 +57,10 @@ class MemoryLog : public LogInterface { size_t printed = std::snprintf(&buffer_[start_pos + 1], magic_size - 1, "LOG:%08x: ", real_pos); CHECK(printed == magic_size - 2); buffer_[start_pos + magic_size - 1] = ' '; + + if (log_level == VERBOSITY_NAME(FATAL)) { + process_fatal_error(new_slice); + } } void rotate() override { diff --git a/tdutils/td/utils/logging.cpp b/tdutils/td/utils/logging.cpp index 95f4e928..9fa9a3f5 100644 --- a/tdutils/td/utils/logging.cpp +++ b/tdutils/td/utils/logging.cpp @@ -216,11 +216,7 @@ class DefaultLog : public LogInterface { TsCerr() << slice; #endif if (log_level == VERBOSITY_NAME(FATAL)) { - auto f = default_log_on_fatal_error; - if (f) { - f(slice); - } - std::abort(); + process_fatal_error(slice); } } void rotate() override { @@ -230,6 +226,19 @@ static DefaultLog default_log; LogInterface *const default_log_interface = &default_log; LogInterface *log_interface = default_log_interface; -OnFatalErrorF default_log_on_fatal_error = nullptr; + +static OnFatalErrorCallback on_fatal_error_callback = nullptr; + +void set_log_fatal_error_callback(OnFatalErrorCallback callback) { + on_fatal_error_callback = callback; +} + +void process_fatal_error(CSlice message) { + auto callback = on_fatal_error_callback; + if (callback) { + callback(message); + } + std::abort(); +} } // namespace td diff --git a/tdutils/td/utils/logging.h b/tdutils/td/utils/logging.h index cfff0bbd..3ed4f48f 100644 --- a/tdutils/td/utils/logging.h +++ b/tdutils/td/utils/logging.h @@ -30,7 +30,6 @@ #include "td/utils/StringBuilder.h" #include -#include #include #define PSTR_IMPL(...) ::td::Logger(::td::NullLog().ref(), 0, true).printf(__VA_ARGS__) @@ -98,7 +97,7 @@ inline bool no_return_func() { #define UNREACHABLE(...) \ LOG(FATAL, __VA_ARGS__); \ - std::abort() + ::td::process_fatal_error("Unreachable in " __FILE__ " at " TD_DEFINE_STR(__LINE__)) constexpr int VERBOSITY_NAME(PLAIN) = -1; constexpr int VERBOSITY_NAME(FATAL) = 0; @@ -148,8 +147,11 @@ class NullLog : public LogInterface { extern LogInterface *const default_log_interface; extern LogInterface *log_interface; -typedef void (*OnFatalErrorF)(CSlice msg); -extern OnFatalErrorF default_log_on_fatal_error; + +using OnFatalErrorCallback = void (*)(CSlice message); +void set_log_fatal_error_callback(OnFatalErrorCallback callback); + +[[noreturn]] void process_fatal_error(CSlice message); #define TC_RED "\e[1;31m" #define TC_BLUE "\e[1;34m"