aboutsummaryrefslogtreecommitdiff
path: root/src/logging.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/logging.cpp')
-rw-r--r--src/logging.cpp335
1 files changed, 335 insertions, 0 deletions
diff --git a/src/logging.cpp b/src/logging.cpp
new file mode 100644
index 0000000000..a352e106e5
--- /dev/null
+++ b/src/logging.cpp
@@ -0,0 +1,335 @@
+// Copyright (c) 2009-2010 Satoshi Nakamoto
+// Copyright (c) 2009-2020 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <logging.h>
+#include <util/threadnames.h>
+#include <util/string.h>
+#include <util/time.h>
+
+#include <algorithm>
+#include <array>
+#include <mutex>
+
+const char * const DEFAULT_DEBUGLOGFILE = "debug.log";
+
+BCLog::Logger& LogInstance()
+{
+/**
+ * NOTE: the logger instances is leaked on exit. This is ugly, but will be
+ * cleaned up by the OS/libc. Defining a logger as a global object doesn't work
+ * since the order of destruction of static/global objects is undefined.
+ * Consider if the logger gets destroyed, and then some later destructor calls
+ * LogPrintf, maybe indirectly, and you get a core dump at shutdown trying to
+ * access the logger. When the shutdown sequence is fully audited and tested,
+ * explicit destruction of these objects can be implemented by changing this
+ * from a raw pointer to a std::unique_ptr.
+ * Since the ~Logger() destructor is never called, the Logger class and all
+ * its subclasses must have implicitly-defined destructors.
+ *
+ * This method of initialization was originally introduced in
+ * ee3374234c60aba2cc4c5cd5cac1c0aefc2d817c.
+ */
+ static BCLog::Logger* g_logger{new BCLog::Logger()};
+ return *g_logger;
+}
+
+bool fLogIPs = DEFAULT_LOGIPS;
+
+static int FileWriteStr(const std::string &str, FILE *fp)
+{
+ return fwrite(str.data(), 1, str.size(), fp);
+}
+
+bool BCLog::Logger::StartLogging()
+{
+ StdLockGuard scoped_lock(m_cs);
+
+ assert(m_buffering);
+ assert(m_fileout == nullptr);
+
+ if (m_print_to_file) {
+ assert(!m_file_path.empty());
+ m_fileout = fsbridge::fopen(m_file_path, "a");
+ if (!m_fileout) {
+ return false;
+ }
+
+ setbuf(m_fileout, nullptr); // unbuffered
+
+ // Add newlines to the logfile to distinguish this execution from the
+ // last one.
+ FileWriteStr("\n\n\n\n\n", m_fileout);
+ }
+
+ // dump buffered messages from before we opened the log
+ m_buffering = false;
+ while (!m_msgs_before_open.empty()) {
+ const std::string& s = m_msgs_before_open.front();
+
+ if (m_print_to_file) FileWriteStr(s, m_fileout);
+ if (m_print_to_console) fwrite(s.data(), 1, s.size(), stdout);
+ for (const auto& cb : m_print_callbacks) {
+ cb(s);
+ }
+
+ m_msgs_before_open.pop_front();
+ }
+ if (m_print_to_console) fflush(stdout);
+
+ return true;
+}
+
+void BCLog::Logger::DisconnectTestLogger()
+{
+ StdLockGuard scoped_lock(m_cs);
+ m_buffering = true;
+ if (m_fileout != nullptr) fclose(m_fileout);
+ m_fileout = nullptr;
+ m_print_callbacks.clear();
+}
+
+void BCLog::Logger::EnableCategory(BCLog::LogFlags flag)
+{
+ m_categories |= flag;
+}
+
+bool BCLog::Logger::EnableCategory(const std::string& str)
+{
+ BCLog::LogFlags flag;
+ if (!GetLogCategory(flag, str)) return false;
+ EnableCategory(flag);
+ return true;
+}
+
+void BCLog::Logger::DisableCategory(BCLog::LogFlags flag)
+{
+ m_categories &= ~flag;
+}
+
+bool BCLog::Logger::DisableCategory(const std::string& str)
+{
+ BCLog::LogFlags flag;
+ if (!GetLogCategory(flag, str)) return false;
+ DisableCategory(flag);
+ return true;
+}
+
+bool BCLog::Logger::WillLogCategory(BCLog::LogFlags category) const
+{
+ return (m_categories.load(std::memory_order_relaxed) & category) != 0;
+}
+
+bool BCLog::Logger::DefaultShrinkDebugFile() const
+{
+ return m_categories == BCLog::NONE;
+}
+
+struct CLogCategoryDesc {
+ BCLog::LogFlags flag;
+ std::string category;
+};
+
+const CLogCategoryDesc LogCategories[] =
+{
+ {BCLog::NONE, "0"},
+ {BCLog::NONE, "none"},
+ {BCLog::NET, "net"},
+ {BCLog::TOR, "tor"},
+ {BCLog::MEMPOOL, "mempool"},
+ {BCLog::HTTP, "http"},
+ {BCLog::BENCH, "bench"},
+ {BCLog::ZMQ, "zmq"},
+ {BCLog::WALLETDB, "walletdb"},
+ {BCLog::RPC, "rpc"},
+ {BCLog::ESTIMATEFEE, "estimatefee"},
+ {BCLog::ADDRMAN, "addrman"},
+ {BCLog::SELECTCOINS, "selectcoins"},
+ {BCLog::REINDEX, "reindex"},
+ {BCLog::CMPCTBLOCK, "cmpctblock"},
+ {BCLog::RAND, "rand"},
+ {BCLog::PRUNE, "prune"},
+ {BCLog::PROXY, "proxy"},
+ {BCLog::MEMPOOLREJ, "mempoolrej"},
+ {BCLog::LIBEVENT, "libevent"},
+ {BCLog::COINDB, "coindb"},
+ {BCLog::QT, "qt"},
+ {BCLog::LEVELDB, "leveldb"},
+ {BCLog::VALIDATION, "validation"},
+ {BCLog::I2P, "i2p"},
+ {BCLog::IPC, "ipc"},
+ {BCLog::LOCK, "lock"},
+ {BCLog::UTIL, "util"},
+ {BCLog::ALL, "1"},
+ {BCLog::ALL, "all"},
+};
+
+bool GetLogCategory(BCLog::LogFlags& flag, const std::string& str)
+{
+ if (str == "") {
+ flag = BCLog::ALL;
+ return true;
+ }
+ for (const CLogCategoryDesc& category_desc : LogCategories) {
+ if (category_desc.category == str) {
+ flag = category_desc.flag;
+ return true;
+ }
+ }
+ return false;
+}
+
+std::vector<LogCategory> BCLog::Logger::LogCategoriesList() const
+{
+ // Sort log categories by alphabetical order.
+ std::array<CLogCategoryDesc, std::size(LogCategories)> categories;
+ std::copy(std::begin(LogCategories), std::end(LogCategories), categories.begin());
+ std::sort(categories.begin(), categories.end(), [](auto a, auto b) { return a.category < b.category; });
+
+ std::vector<LogCategory> ret;
+ for (const CLogCategoryDesc& category_desc : categories) {
+ if (category_desc.flag == BCLog::NONE || category_desc.flag == BCLog::ALL) continue;
+ LogCategory catActive;
+ catActive.category = category_desc.category;
+ catActive.active = WillLogCategory(category_desc.flag);
+ ret.push_back(catActive);
+ }
+ return ret;
+}
+
+std::string BCLog::Logger::LogTimestampStr(const std::string& str)
+{
+ std::string strStamped;
+
+ if (!m_log_timestamps)
+ return str;
+
+ if (m_started_new_line) {
+ int64_t nTimeMicros = GetTimeMicros();
+ strStamped = FormatISO8601DateTime(nTimeMicros/1000000);
+ if (m_log_time_micros) {
+ strStamped.pop_back();
+ strStamped += strprintf(".%06dZ", nTimeMicros%1000000);
+ }
+ std::chrono::seconds mocktime = GetMockTime();
+ if (mocktime > 0s) {
+ strStamped += " (mocktime: " + FormatISO8601DateTime(count_seconds(mocktime)) + ")";
+ }
+ strStamped += ' ' + str;
+ } else
+ strStamped = str;
+
+ return strStamped;
+}
+
+namespace BCLog {
+ /** Belts and suspenders: make sure outgoing log messages don't contain
+ * potentially suspicious characters, such as terminal control codes.
+ *
+ * This escapes control characters except newline ('\n') in C syntax.
+ * It escapes instead of removes them to still allow for troubleshooting
+ * issues where they accidentally end up in strings.
+ */
+ std::string LogEscapeMessage(const std::string& str) {
+ std::string ret;
+ for (char ch_in : str) {
+ uint8_t ch = (uint8_t)ch_in;
+ if ((ch >= 32 || ch == '\n') && ch != '\x7f') {
+ ret += ch_in;
+ } else {
+ ret += strprintf("\\x%02x", ch);
+ }
+ }
+ return ret;
+ }
+} // namespace BCLog
+
+void BCLog::Logger::LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, const int source_line)
+{
+ StdLockGuard scoped_lock(m_cs);
+ std::string str_prefixed = LogEscapeMessage(str);
+
+ if (m_log_sourcelocations && m_started_new_line) {
+ str_prefixed.insert(0, "[" + RemovePrefix(source_file, "./") + ":" + ToString(source_line) + "] [" + logging_function + "] ");
+ }
+
+ if (m_log_threadnames && m_started_new_line) {
+ str_prefixed.insert(0, "[" + util::ThreadGetInternalName() + "] ");
+ }
+
+ str_prefixed = LogTimestampStr(str_prefixed);
+
+ m_started_new_line = !str.empty() && str[str.size()-1] == '\n';
+
+ if (m_buffering) {
+ // buffer if we haven't started logging yet
+ m_msgs_before_open.push_back(str_prefixed);
+ return;
+ }
+
+ if (m_print_to_console) {
+ // print to console
+ fwrite(str_prefixed.data(), 1, str_prefixed.size(), stdout);
+ fflush(stdout);
+ }
+ for (const auto& cb : m_print_callbacks) {
+ cb(str_prefixed);
+ }
+ if (m_print_to_file) {
+ assert(m_fileout != nullptr);
+
+ // reopen the log file, if requested
+ if (m_reopen_file) {
+ m_reopen_file = false;
+ FILE* new_fileout = fsbridge::fopen(m_file_path, "a");
+ if (new_fileout) {
+ setbuf(new_fileout, nullptr); // unbuffered
+ fclose(m_fileout);
+ m_fileout = new_fileout;
+ }
+ }
+ FileWriteStr(str_prefixed, m_fileout);
+ }
+}
+
+void BCLog::Logger::ShrinkDebugFile()
+{
+ // Amount of debug.log to save at end when shrinking (must fit in memory)
+ constexpr size_t RECENT_DEBUG_HISTORY_SIZE = 10 * 1000000;
+
+ assert(!m_file_path.empty());
+
+ // Scroll debug.log if it's getting too big
+ FILE* file = fsbridge::fopen(m_file_path, "r");
+
+ // Special files (e.g. device nodes) may not have a size.
+ size_t log_size = 0;
+ try {
+ log_size = fs::file_size(m_file_path);
+ } catch (const fs::filesystem_error&) {}
+
+ // If debug.log file is more than 10% bigger the RECENT_DEBUG_HISTORY_SIZE
+ // trim it down by saving only the last RECENT_DEBUG_HISTORY_SIZE bytes
+ if (file && log_size > 11 * (RECENT_DEBUG_HISTORY_SIZE / 10))
+ {
+ // Restart the file with some of the end
+ std::vector<char> vch(RECENT_DEBUG_HISTORY_SIZE, 0);
+ if (fseek(file, -((long)vch.size()), SEEK_END)) {
+ LogPrintf("Failed to shrink debug log file: fseek(...) failed\n");
+ fclose(file);
+ return;
+ }
+ int nBytes = fread(vch.data(), 1, vch.size(), file);
+ fclose(file);
+
+ file = fsbridge::fopen(m_file_path, "w");
+ if (file)
+ {
+ fwrite(vch.data(), 1, nBytes, file);
+ fclose(file);
+ }
+ }
+ else if (file != nullptr)
+ fclose(file);
+}