/* * This file is part of the Flowee project * Copyright (C) 2017-2020 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef LOGITEM_H #define LOGITEM_H #include #include #include "tinyformat.h" #include #include namespace Log { class Item; class SilentItem; class Channel; class ManagerPrivate; /** * Alteration options to be used to pass into a log stream. */ enum StreamAlteration { Fixed, //< Equivalent to std::fixed Scientficic, //< Equivalent to std::scientific Hex, //< Equivalent to std::hex Dec, //< Equivalent to std::dec Oct //< Equivalent to std::oct }; /// \internal struct __Precision { int value; }; /// Equivalent to std::setprecision(), but for LogItem /// \see StreamAlteration __Precision precision(int amount); enum Verbosity { DebugLevel = 1, InfoLevel, WarningLevel, CriticalLevel, FatalLevel }; /** * @brief The Sections enum is a way to enable/disable logging by sections of code. * Sections in logging work in two levels. We have groups with enum 0, 1000, 2000, etc. * And under the groups are a small set of named sub-sections. * * Calling logInfo(2020) will use section "Networking" and an unnamed sub-section 2020. * A user can enable/disable whole sections, like "Networking" and that would make this log item * be marked as disabled. Or the user can disable/enable specific sections like 2020 to * only have his/her class be logged. * * By not passing any section to logInfo() you select the group Global, which is typically * used for general end-user information regardless of where in the code it is located. */ enum Sections { Global = 0, Bitcoin = 1000, BlockValidation, TxValidation, Bench, Mining, UTXO = 1100, Networking = 2000, Net, Addrman, Proxy, NWM, Tor, ThinBlocks, ExpeditedBlocks, DSProof, P2PNet, // this is the new p2p protocol code (2020 and later). // "legacy" apis RPC = 2100, LibEvent, HTTP, ZMQ, // modern fancy apis ApiServer = 2500, MonitorService, BlockNotifactionService, SearchEngine = 2800, DB = 3000, Coindb, Internals = 4000, Mempool, MempoolRej, Random, Wallet = 5000, SelectCoins, FeeEstimation, QtGui = 6000, // Point-of-Sale POS = 7000 }; /** * @brief The Manager class is meant to be a singleton that owns the logging settings. * This class actually distributes the logging lines to the different channels and * it allows a log-item to know it has been disabled and as such can avoid actual logging. */ class Manager { public: /// please use instance() instead. Manager(); ~Manager(); /** * Returns true if the section has been enabled. */ bool isEnabled(short section, Log::Verbosity verbosity) const; /** * Returns the mapping of an old-style text based section to our internal one. */ short section(const char *category); static Manager* instance(); /// This is only called by the Item to log its data on the logging channels. void log(Item *item); /// Request files to be closed and opened anew. void reopenLogFiles(); /** * Load a simple setup that prints all logging to stdout and nothing to file. * This code behaves differently if in release mode, in that case nothing is logged instead of everything. * @param testNameFunction a function that will return a c-string that is prefixed to each line to indicate * which test-function they are in. */ void loadDefaultTestSetup(const std::function &testNameFunction); /** * @brief parseConfig reads the config file logs.conf from the datadir. * The config file is a list of channels to output to and the log sections with * log-verbosity. * Example file; * * * # Anything behind a '#' is a comment * * channel console * # linenumber / methodname / filename only work for developer-builds. (configure --enable-dev-setup) * option linenumber false * option methodname true * option filename true * # timestamp-format. No argments means no timestamp, adding 'date' implies 'time'. * option timestamp date time millisecond * * # just mentioning 'channel file' will enable it. * channel file * # all options available for console are available for file too. * option path [path] * * # Log sections from Log::Sections and verbosity * # default value for all log sections that are not specifically added here is `info` * 1000 quiet # multiple of 1000 is a group, changes apply to all unset items in that group (1000-1999) * 1007 debug # override group setting. * * # silent only shows fatal * # quiet only shows critical and fatal * # info shows warning, info, critical and fatal * # debug shows everything. * */ void parseConfig(const boost::filesystem::path &configfile, const boost::filesystem::path &logfilename); static const std::string §ionString(short section); /** * remove all logging channels. * @see addConsoleChannel() * @see addFileChannel() */ void clearChannels(); /// add a console output channel. void addConsoleChannel(bool printSections = true); /// add a file logging channel void addFileChannel(const boost::filesystem::path &logfilename, bool printSections = true); void clearLogLevels(Log::Verbosity defaultVerbosity = Log::WarningLevel); void setLogLevel(short section, Log::Verbosity verbosity); private: ManagerPrivate *d; }; /// Items are instantiated by calling macros like logInfo(), use this to stream your data into the item. /// One item instance represents one line in the logs, a linefeed is auto-appended if needed. class Item { public: explicit Item(const char *filename, int lineNumber, const char *methodName, short section, short verbosity = InfoLevel); explicit Item(short verbosity = InfoLevel); Item(const Item &other); ~Item(); inline Item &nospace() { d->space = false; return *this; } inline Item &space() { d->space = true; d->stream << ' '; return *this; } inline Item &maybespace() { if (d->space) d->stream << ' '; return *this; } inline bool useSpace() const { return d->space; } /// returns the verbosity that this item has been instantiated with. /// @see Log::Verbosity inline int verbosity() const { return d->verbosity; } /// Returns true if the logging is enabled for this item. /// It is safe to skip more expensive operations if the item is disabled. inline bool isEnabled() const { return d->on; } /// The application Section this log item was created in. @see Log::Sections short section() const; inline Item &operator<<(bool val) { if(d->on)d->stream << (val?"true":"false"); return maybespace(); } inline Item &operator<<(char c) { if(d->on)d->stream << c; return maybespace(); } inline Item &operator<<(const char *c) { if(d->on)d->stream << c; return maybespace(); } inline Item &operator<<(int i) { if(d->on)d->stream << i; return maybespace(); } inline Item &operator<<(double val) { if(d->on)d->stream << val; return maybespace(); } inline Item &operator<<(const std::string &str) { if(d->on){d->stream << '"' << str << '"';} return maybespace(); } inline Item &operator<<(short val) { if(d->on)d->stream << val ; return maybespace(); } inline Item &operator<<(unsigned short val) { if(d->on)d->stream << val ; return maybespace(); } inline Item &operator<<(unsigned int val) { if(d->on)d->stream << val ; return maybespace(); } inline Item &operator<<(long val) { if(d->on)d->stream << val ; return maybespace(); } inline Item &operator<<(unsigned long val) { if(d->on)d->stream << val ; return maybespace(); } inline Item &operator<<(long long val) { if(d->on)d->stream << val ; return maybespace(); } inline Item &operator<<(unsigned long long val) { if(d->on)d->stream << val ; return maybespace(); } inline Item &operator<<(float val) { if(d->on)d->stream << val ; return maybespace(); } inline Item &operator<<(long double val) { if(d->on)d->stream << val ; return maybespace(); } inline Item &operator<<(const void* val) { if(d->on){if (!val)d->stream << "(nullptr)";else d->stream << val;} return maybespace(); } inline Item &operator<<(std::nullptr_t) { if(d->on)d->stream << "(nullptr)"; return maybespace(); } inline Item &operator<<(std::ostream& (*pf)(std::ostream&)) { if(d->on)d->stream << pf; return *this; } inline Item &operator<<(std::ios& (*pf)(std::ios&)) { if(d->on)d->stream << pf; return *this; } inline Item &operator<<(std::ios_base& (*pf)(std::ios_base&)) { if(d->on)d->stream << pf; return *this; } Item& operator<<(StreamAlteration alteration); inline Item& operator<<(__Precision p) { d->stream << std::setprecision(p.value); return *this; } Item operator=(const Item &other) = delete; private: friend class Manager; friend class MessageLogger; struct State { State(const char*filename, int lineNumber, const char *methodName, short section) : section(section), lineNum(lineNumber), filename(filename), methodName(methodName) {} std::ostringstream stream; bool space; bool on; short verbosity; short ref; short section; const int lineNum; const char *filename; const char *methodName; } *d; }; #ifndef LOG_DEFAULT_SECTION #define LOG_DEFAULT_SECTION 0 #endif /// This class should likely never be used manually, it purely exists to create Item and SilentItem instances. class MessageLogger { public: MessageLogger(); explicit MessageLogger(const char *filename, int lineNumber, const char *methodName); inline Item debug(short section = LOG_DEFAULT_SECTION) { return Log::Item(m_file, m_line, m_method, section, Log::DebugLevel); } inline Item warning(short section = LOG_DEFAULT_SECTION) { return Log::Item(m_file, m_line, m_method, section, Log::WarningLevel); } inline Item info(short section = LOG_DEFAULT_SECTION) { return Log::Item(m_file, m_line, m_method, section, Log::InfoLevel); } inline Item critical(short section = LOG_DEFAULT_SECTION) { return Log::Item(m_file, m_line, m_method, section, Log::CriticalLevel); } inline Item fatal(short section = LOG_DEFAULT_SECTION) { return Log::Item(m_file, m_line, m_method, section, Log::FatalLevel); } inline Log::SilentItem noDebug(int = 0); #define MAKE_LOGGER_FUNCTIONS(n) \ template \ inline void infoCompat(const char *section, const char* format, TINYFORMAT_VARARGS(n)) { \ Item item(m_file, m_line, m_method, Manager::instance()->section(section)); \ if (format && item.isEnabled()) \ item.d->stream << tfm::format(format, TINYFORMAT_PASSARGS(n)); \ } \ template \ inline Item debug(const char* format, TINYFORMAT_VARARGS(n)) { \ Item item(m_file, m_line, m_method, Log::Global, DebugLevel); \ item.d->stream << tfm::format(format, TINYFORMAT_PASSARGS(n)); \ return item; \ } \ template \ inline Item info(const char* format, TINYFORMAT_VARARGS(n)) { \ Item item(m_file, m_line, m_method, Log::Global, InfoLevel); \ item.d->stream << tfm::format(format, TINYFORMAT_PASSARGS(n)); \ return item; \ } \ template \ inline Item warning(const char* format, TINYFORMAT_VARARGS(n)) { \ Item item(m_file, m_line, m_method, Log::Global, WarningLevel); \ item.d->stream << tfm::format(format, TINYFORMAT_PASSARGS(n)); \ return item; \ } \ template \ inline Item critical(const char* format, TINYFORMAT_VARARGS(n)) { \ Item item(m_file, m_line, m_method, Log::Global, CriticalLevel); \ item.d->stream << tfm::format(format, TINYFORMAT_PASSARGS(n)); \ return item; \ } \ template \ inline Item fatal(const char* format, TINYFORMAT_VARARGS(n)) { \ Item item(m_file, m_line, m_method, Log::Global, FatalLevel); \ item.d->stream << tfm::format(format, TINYFORMAT_PASSARGS(n)); \ return item; \ } TINYFORMAT_FOREACH_ARGNUM(MAKE_LOGGER_FUNCTIONS) inline void infoCompat(const char *section, const char* format = 0) { Item item(m_file, m_line, m_method, Manager::instance()->section(section)); item.d->stream << format; } inline Item debug(const char* format) { Item item(m_file, m_line, m_method, Log::Global, DebugLevel); item.d->stream << format; return item; } inline Item info(const char* format) { Item item(m_file, m_line, m_method, Log::Global); item.d->stream << format; return item; } inline Item warning(const char* format) { Item item(m_file, m_line, m_method, Log::Global, WarningLevel); item.d->stream << format; return item; } inline Item critical(const char* format) { Item item(m_file, m_line, m_method, Log::Global, CriticalLevel); item.d->stream << format; return item; } inline Item fatal(const char* format) { Item item(m_file, m_line, m_method, Log::Global, FatalLevel); item.d->stream << format; return item; } private: const int m_line; const char *m_file, *m_method; }; /// A no-logging item. class SilentItem { public: inline int verbosity() const { return FatalLevel; } inline SilentItem &nospace() { return *this; } inline SilentItem &space() { return *this; } inline SilentItem &maybespace() { return *this; } inline SilentItem &operator<<(char) { return *this; } inline SilentItem &operator<<(const char *) { return *this; } inline SilentItem &operator<<(int) { return *this; } inline SilentItem &operator<<(double) { return *this; } inline SilentItem &operator<<(const std::string&) { return *this; } inline SilentItem &operator<<(bool) { return *this; } inline SilentItem &operator<<(short) { return *this; } inline SilentItem &operator<<(unsigned short) { return *this; } inline SilentItem &operator<<(unsigned int) { return *this; } inline SilentItem &operator<<(long) { return *this; } inline SilentItem &operator<<(unsigned long) { return *this; } inline SilentItem &operator<<(long long) { return *this; } inline SilentItem &operator<<(unsigned long long) { return *this; } inline SilentItem &operator<<(float) { return *this; } inline SilentItem &operator<<(long double) { return *this; } inline SilentItem &operator<<(void*) { return *this; } inline SilentItem &operator<<(std::nullptr_t) { return *this; } inline SilentItem &operator<<(std::ostream& (*pf)(std::ostream&)) { return *this; } inline SilentItem &operator<<(std::ios& (*pf)(std::ios&)) { return *this; } inline SilentItem &operator<<(std::ios_base& (*pf)(std::ios_base&)) { return *this; } inline SilentItem& operator<<(StreamAlteration) { return *this; } inline SilentItem& operator<<(__Precision) { return *this; } }; inline SilentItem MessageLogger::noDebug(int) { return SilentItem(); } } // namespace Log bool InterpretBool(const std::string& strValue); #ifdef BCH_LOGCONTEXT #define BCH_MESSAGELOG_FILE __FILE__ #define BCH_MESSAGELOG_LINE __LINE__ #define BCH_MESSAGELOG_FUNC __PRETTY_FUNCTION__ #else #define BCH_MESSAGELOG_FILE nullptr #define BCH_MESSAGELOG_LINE 0 #define BCH_MESSAGELOG_FUNC nullptr #endif #define logDebug Log::MessageLogger(BCH_MESSAGELOG_FILE, BCH_MESSAGELOG_LINE, BCH_MESSAGELOG_FUNC).debug #define logInfo Log::MessageLogger(BCH_MESSAGELOG_FILE, BCH_MESSAGELOG_LINE, BCH_MESSAGELOG_FUNC).info #define logWarning Log::MessageLogger(BCH_MESSAGELOG_FILE, BCH_MESSAGELOG_LINE, BCH_MESSAGELOG_FUNC).warning #define logCritical Log::MessageLogger(BCH_MESSAGELOG_FILE, BCH_MESSAGELOG_LINE, BCH_MESSAGELOG_FUNC).critical #define logFatal Log::MessageLogger(BCH_MESSAGELOG_FILE, BCH_MESSAGELOG_LINE, BCH_MESSAGELOG_FUNC).fatal #define BCH_NO_DEBUG_MACRO while (false) Log::MessageLogger().noDebug #if defined(BCH_NO_DEBUG_OUTPUT) # undef logDebug # define logDebug BCH_NO_DEBUG_MACRO #endif #if defined(BCH_NO_INFO_OUTPUT) # undef logInfo # define logInfo BCH_NO_DEBUG_MACRO #endif #if defined(BCH_NO_WARNING_OUTPUT) # undef logWarning # define logWarning BCH_NO_DEBUG_MACRO #endif #include template inline Log::Item operator<<(Log::Item item, const std::atomic &atomic) { if (item.isEnabled()) item << atomic.load(); return item; } template inline Log::SilentItem operator<<(Log::SilentItem item, const std::atomic&) { return item; } Log::Item operator<<(Log::Item item, const std::exception &ex); inline Log::SilentItem operator<<(Log::SilentItem item, const std::exception &ex) { return item; } #include template inline Log::Item operator<<(Log::Item item, const std::vector &vector) { if (item.isEnabled()) { const bool old = item.useSpace(); item.nospace() << '('; for (size_t i = 0; i < vector.size(); ++i) { if (i) item << ','; item << vector[i]; } item << ')'; if (old) return item.space(); } return item; } template inline Log::SilentItem operator<<(Log::SilentItem item, const std::vector&) { return item; } template inline Log::Item operator<<(Log::Item item, const std::pair &p) { const bool old = item.useSpace(); item.nospace() << "pair<" << p.first << "," << p.second << ">"; if (old) return item.space(); return item; } #ifdef QSTRING_H inline Log::SilentItem operator<<(Log::SilentItem item, const QString&) { return item; } inline Log::Item operator<<(Log::Item item, const QString &s) { return item << s.toLocal8Bit().data(); } #endif #include template inline Log::Item operator<<(Log::Item item, const std::set &set) { if (item.isEnabled()) { const bool old = item.useSpace(); item.nospace() << '('; bool first = true; for (const V &v : set) { if (!first) item << ','; first = false; item << v; } item << ')'; if (old) return item.space(); } return item; } template inline Log::SilentItem operator<<(Log::SilentItem item, const std::set&) { return item; } #include template inline Log::Item operator<<(Log::Item item, const std::list &set) { if (item.isEnabled()) { const bool old = item.useSpace(); item.nospace() << '('; bool first = true; for (const V &v : set) { if (!first) item << ','; first = false; item << v; } item << ')'; if (old) return item.space(); } return item; } template inline Log::SilentItem operator<<(Log::SilentItem item, const std::list&) { return item; } #ifdef QLIST_H template inline Log::Item operator<<(Log::Item item, const QList &list) { if (item.isEnabled()) { const bool old = item.useSpace(); item.nospace() << '('; bool first = true; for (const V &v : list) { if (!first) item << ','; first = false; item << v; } item << ')'; if (old) return item.space(); } return item; } template inline Log::SilentItem operator<<(Log::SilentItem item, const QList&) { return item; } #endif #endif