572 lines
20 KiB
C++
572 lines
20 KiB
C++
/*
|
|
* This file is part of the Flowee project
|
|
* Copyright (C) 2017-2020 Tom Zander <tom@flowee.org>
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#ifndef LOGITEM_H
|
|
#define LOGITEM_H
|
|
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
|
|
#include "tinyformat.h"
|
|
|
|
#include <boost/filesystem/path.hpp>
|
|
#include <functional>
|
|
|
|
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(const Manager &o) = delete;
|
|
~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<const char*()> &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;
|
|
*
|
|
* <code>
|
|
* # Anything behind a '#' is a comment
|
|
* # Online docs; https://flowee.org/docs/hub/log-config/
|
|
*
|
|
* channel console
|
|
* # linenumber / methodname / filename only show 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.
|
|
* # path is optional, default goes to XDG dir (~/.local/share/flowee/app/app.log)
|
|
* option path [path]
|
|
*
|
|
* # Log sections from Log::Sections and verbosity
|
|
* # default value for all log sections that are not specifically added here is `warning`
|
|
* 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.
|
|
* </endcode>
|
|
*/
|
|
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);
|
|
|
|
Manager& operator=(const Manager &o) = delete;
|
|
|
|
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<TINYFORMAT_ARGTYPES(n)> \
|
|
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<TINYFORMAT_ARGTYPES(n)> \
|
|
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<TINYFORMAT_ARGTYPES(n)> \
|
|
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<TINYFORMAT_ARGTYPES(n)> \
|
|
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<TINYFORMAT_ARGTYPES(n)> \
|
|
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<TINYFORMAT_ARGTYPES(n)> \
|
|
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 <atomic>
|
|
template<class T>
|
|
inline Log::Item operator<<(Log::Item item, const std::atomic<T> &atomic) {
|
|
if (item.isEnabled()) item << atomic.load();
|
|
return item;
|
|
}
|
|
template<class T>
|
|
inline Log::SilentItem operator<<(Log::SilentItem item, const std::atomic<T>&) { 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 <vector>
|
|
|
|
template<class V>
|
|
inline Log::Item operator<<(Log::Item item, const std::vector<V> &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<class V>
|
|
inline Log::SilentItem operator<<(Log::SilentItem item, const std::vector<V>&) { return item; }
|
|
|
|
template<typename T1, typename T2>
|
|
inline Log::Item operator<<(Log::Item item, const std::pair<T1,T2> &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 <set>
|
|
|
|
template<class V>
|
|
inline Log::Item operator<<(Log::Item item, const std::set<V> &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<class V>
|
|
inline Log::SilentItem operator<<(Log::SilentItem item, const std::set<V>&) { return item; }
|
|
|
|
#include <list>
|
|
|
|
template<class V>
|
|
inline Log::Item operator<<(Log::Item item, const std::list<V> &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<class V>
|
|
inline Log::SilentItem operator<<(Log::SilentItem item, const std::list<V>&) { return item; }
|
|
|
|
#ifdef QLIST_H
|
|
template<class V>
|
|
inline Log::Item operator<<(Log::Item item, const QList<V> &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<class V>
|
|
inline Log::SilentItem operator<<(Log::SilentItem item, const QList<V>&) { return item; }
|
|
#endif
|
|
|
|
#endif
|