Sélectionner une révision Git
Log.hh 9,44 Kio
#pragma once
#include "Utils.hh"
// Create a logger with a category
#define VIVY_GET_LOGGER(sink, cat) (sink)->newLogger(std::string_view{ #cat })
#define VIVY_GET_LOGGER_BY_STORED_NAME(sink, cat) (sink)->newLogger(std::string_view{ cat })
// Log something in a logger
#define VIVY_LOG_WITH_LEVEL(log, level) (log)->logEvent(__FILE__, __func__, __LINE__, level)
#define VIVY_LOG_WARN(log) VIVY_LOG_WITH_LEVEL(log, Vivy::LogLevel::Warning)
#define VIVY_LOG_DEBUG(log) VIVY_LOG_WITH_LEVEL(log, Vivy::LogLevel::Debug)
#define VIVY_LOG_INFO(log) VIVY_LOG_WITH_LEVEL(log, Vivy::LogLevel::Info)
#define VIVY_LOG_ERR(log) VIVY_LOG_WITH_LEVEL(log, Vivy::LogLevel::Error)
#define VIVY_LOG_FATAL(log) VIVY_LOG_WITH_LEVEL(log, Vivy::LogLevel::Critical)
// Declare a sink, with the utility function/method `flushLogSink`. This is
// intended to be used in an object to not polluate namespaces.
#define VIVY_DCL_LOG_SINK(sink) \
std::shared_ptr<LogSink> sink{ LogSink::newSink() }; \
void flushLogSink() const noexcept { (sink)->flush(); }
// Declare a dispatch for a sink with no arguments in the constructor.
#define VIVY_DCL_LOG_DISPATCH(sink, name, dispatch) \
std::shared_ptr<dispatch> name{ (sink)->newDispatcher<dispatch>() };
// Declare a dispatch for a sink with arguments in the constructor.
#define VIVY_DCL_LOG_DISPATCH_WITH(sink, name, dispatch, ...) \
std::shared_ptr<dispatch> name{ (sink)->newDispatcher<dispatch>(__VA_ARGS__) };
#define VIVY_LOGGABLE_OBJECT_METHODS_ONLY(logger) \
LogMessage logFatal() const noexcept { return VIVY_LOG_FATAL(logger); } \
LogMessage logError() const noexcept { return VIVY_LOG_ERR(logger); } \
LogMessage logWarning() const noexcept { return VIVY_LOG_WARN(logger); } \
LogMessage logInfo() const noexcept { return VIVY_LOG_INFO(logger); } \
LogMessage logDebug() const noexcept { return VIVY_LOG_DEBUG(logger); }
// Install logger for the object.
#define VIVY_LOGGABLE_OBJECT(sink, name, logger) \
std::shared_ptr<Logger> logger{ VIVY_GET_LOGGER(sink, name) }; \
VIVY_LOGGABLE_OBJECT_METHODS_ONLY(logger)
// Install logger for the object. Here `name` must be a variable that can be
// used to create a std::stding_view.
#define VIVY_LOGGABLE_OBJECT_BY_STORED_NAME(sink, name, logger) \
std::shared_ptr<Logger> logger{ VIVY_GET_LOGGER_BY_STORED_NAME(sink, name) }; \
VIVY_LOGGABLE_OBJECT_METHODS_ONLY(logger)
#define VIVY_LOG_CTOR() logDebug() << "<<CTOR>> "
#define VIVY_LOG_DTOR() logDebug() << "<<DTOR>> "
#define VIVY_LOG_QUOTED(something) '\'' << (something) << '\''
// Install logger, use the global LogSink
#define VIVY_APP_LOGGABLE_OBJECT(name, logger) \
VIVY_LOGGABLE_OBJECT(vivyApp->getLogSink(), name, logger)
// Install logger, use the global LogSink, with a stored name like in the
// VIVY_LOGGABLE_OBJECT_BY_STORED_NAME macro.
#define VIVY_APP_LOGGABLE_OBJECT_BY_STORED_NAME(name, logger) \
VIVY_LOGGABLE_OBJECT_BY_STORED_NAME(vivyApp->getLogSink(), name, logger)
namespace Vivy
{
class LogSinkDispatcher;
class LogSink;
class Logger;
class LogMessage;
// The severity of an event. Critical will cause the LogSink to flush all
// messages to its emeters and then abort.
struct LogLevel final {
enum Level : int {
None, // In option setup to disable logs
Debug,
Info,
Warning,
Error,
Critical, // Will trigger qFatal
___MaxAndUnused
};
static const std::string_view toStdStringView(const LogLevel::Level) noexcept;
private:
using Array = std::array<const std::string_view, ___MaxAndUnused>;
static inline constexpr Array LevelsStringViews = { "None", "Debug", "Info",
"Warnin", "Error", "Critical" };
LogLevel() {}
};
// A LogSinkDispatcher will excavate LogMessages from the Sink and do something
// with them (save to a file, display in stderr/Vivy console, etc).
class LogSinkDispatcher {
VIVY_UNMOVABLE_OBJECT(LogSinkDispatcher)
const std::string dispatcherName;
protected:
explicit LogSinkDispatcher(const std::string_view name) noexcept
: dispatcherName(name)
{
}
public:
const std::string_view getDispatcherName() const noexcept { return dispatcherName; }
virtual ~LogSinkDispatcher() noexcept;
virtual void handleLogMessage(const std::string_view, const LogMessage &) noexcept = 0;
};
// The stderr dispatcher for logs
class StderrLogSinkDispatcher : public LogSinkDispatcher {
VIVY_UNMOVABLE_OBJECT(StderrLogSinkDispatcher)
static const std::string_view trunkFileName(const char *) noexcept;
static std::string reduceFileName(const std::string_view) noexcept;
public:
explicit StderrLogSinkDispatcher() noexcept;
void handleLogMessage(const std::string_view, const LogMessage &) noexcept override;
};
// LogSink is the parent logger. It will recieve messages and display them to a
// console or to std::cerr or to a file, etc. The save to over file, etc will
// be donne by the LogSinkDispatcher.
class LogSink {
VIVY_UNMOVABLE_OBJECT(LogSink)
explicit LogSink() noexcept = default;
public:
static std::shared_ptr<LogSink> newSink() noexcept;
~LogSink() noexcept;
void registerLogDispatcher(std::shared_ptr<LogSinkDispatcher>) noexcept;
void registerLogger(std::shared_ptr<Logger>) noexcept;
void recieveLogMessage(const Logger *const, LogMessage &&) noexcept;
void flush() noexcept;
template <Derived<LogSinkDispatcher> DispatcherType, typename... Args>
std::shared_ptr<DispatcherType> newDispatcher(Args &&...args) noexcept
{
std::shared_ptr<DispatcherType> dispatch =
std::make_shared<DispatcherType>(std::forward<Args>(args)...);
registerLogDispatcher(dispatch);
return dispatch;
}
std::shared_ptr<Logger> newLogger(const StringType auto &category) noexcept
{
std::shared_ptr<Logger> logger = std::make_shared<Logger>(this, category);
registerLogger(logger);
return logger;
}
private:
std::mutex messageQueueLock{};
std::vector<std::tuple<const std::string_view, LogMessage>> messageQueue;
std::vector<std::shared_ptr<LogSinkDispatcher>> logDispatchers;
std::vector<std::shared_ptr<Logger>> loggers;
};
// Message to be logged, constructed by a logger then send
class LogMessage final {
public:
using TimeStamp = chrono::milliseconds;
// The header of the LogMessage
struct Header final {
const char *fileName;
const char *functionName;
const LogLevel::Level severity;
const int lineNumberInFile;
};
private:
const Header messageHeader;
const TimeStamp timeStamp{ duration_cast<TimeStamp>(
chrono::system_clock::now().time_since_epoch()) };
static constexpr inline size_t messageBufferLength = 2048;
std::array<char, messageBufferLength> textBuffer{};
std::size_t indexInArray{ 0 };
const Logger *parentLogger{ nullptr };
static std::string pointerToString(const auto *ptr) noexcept
{
std::stringstream stream;
stream << "0x" << std::setfill('0') << std::setw(sizeof(std::intptr_t) * 2) << std::hex
<< reinterpret_cast<const std::intptr_t>(ptr);
return stream.str();
}
public:
VIVY_DISABLE_COPY_CTOR(LogMessage)
VIVY_DISABLE_ASSIGN_OPERATORS(LogMessage)
explicit LogMessage(const Logger *const, const Header) noexcept;
explicit LogMessage(LogMessage &&) noexcept;
~LogMessage() noexcept; // The message will be send on destruction
Header const &getHeader() const noexcept { return messageHeader; }
const std::string_view getTextBuffer() const noexcept;
const TimeStamp getTimeStamp() const noexcept { return timeStamp; }
LogMessage &&sink() noexcept;
LogMessage &operator<<(const std::string &) noexcept;
LogMessage &operator<<(const std::string_view) noexcept;
LogMessage &operator<<(const QString &) noexcept;
LogMessage &operator<<(const QVariant &) noexcept;
LogMessage &operator<<(const QFileInfo &) noexcept;
LogMessage &operator<<(const char *) noexcept;
LogMessage &operator<<(const double *) noexcept;
LogMessage &operator<<(const char) noexcept;
LogMessage &operator<<(const int) noexcept;
LogMessage &operator<<(const long) noexcept;
LogMessage &operator<<(const long long) noexcept;
LogMessage &operator<<(const unsigned char) noexcept;
LogMessage &operator<<(const unsigned int) noexcept;
LogMessage &operator<<(const unsigned long) noexcept;
LogMessage &operator<<(const unsigned long long) noexcept;
};
// A logger class, a client to LogSink. Will generate and send instances of
// LogMessage.
class Logger final : public std::enable_shared_from_this<Logger> {
VIVY_UNMOVABLE_OBJECT(Logger)
LogSink *const parentLogSink;
const std::string logCategory;
public:
// Templated constructor, construct from anything that can be considered as
// a string.
explicit Logger(LogSink *const sink, const StringType auto category) noexcept
: parentLogSink(sink)
, logCategory(Utils::anyStringToStdString(category))
{
}
const std::string_view getCategoryView() const noexcept { return logCategory; }
void sendLogMessage(LogMessage &&) const noexcept;
LogMessage logEvent(const char *fileName, const char *functionName, const int lineNumber,
const LogLevel::Level) noexcept;
};
}