Skip to content
Extraits de code Groupes Projets

Add clean logs support + dependent MR

Fusionnées Kubat a demandé de fusionner log-macros vers master
2 files
+ 7
1
Comparer les modifications
  • Côte à côte
  • En ligne

Fichiers

src/Lib/Log.hh 0 → 100644
+ 300
0
#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) << '\''
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 {
// NOTE: The order matters:
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>;
// WARN: The order matters!
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;
};
// Class used to check the messageQueue and send needFlush or something like
// that messages. It needs to be moved to its own thread and connected with the
// queue version of the QObject::connect method.
template <class Mutex, class MessageQueue>
requires MutexType<Mutex>
class LogSinkUpdater final {
VIVY_UNMOVABLE_OBJECT(LogSinkUpdater)
Mutex *const messageQueueLock;
MessageQueue *const messageQueue;
using size_type = typename MessageQueue::size_type;
public:
explicit LogSinkUpdater(Mutex *const lock, MessageQueue *const queue)
: messageQueueLock(lock)
, messageQueue(queue)
{
if (lock == nullptr || queue == nullptr)
throw std::logic_error("Can't pass null pointers for the queue and its lock");
}
bool isMessageAvailable() const noexcept
{
std::lock_guard<Mutex> lockGuard(*messageQueueLock);
size_type size = messageQueue->size();
return size != 0;
}
};
// LogSinkUpdater controller, needed because templated Q_OBJECT are not
// supported by Qt. This controller is specialized for a std implementation of
// the message queue. For a Qt version use the following types:
// using MutexType = QMutex;
// using MessageQueueType = QVector<QTuple<QStringView, LogMessage>>;
class StlLogSinkUpdater final : public QThread {
Q_OBJECT
VIVY_UNMOVABLE_OBJECT(StlLogSinkUpdater)
public:
using MutexType = std::mutex;
using MessageQueueType = std::vector<std::tuple<const std::string_view, LogMessage>>;
explicit StlLogSinkUpdater(MutexType *const, MessageQueueType *const) noexcept;
void run() noexcept override;
private:
LogSinkUpdater<MutexType, MessageQueueType> updater;
signals:
void messageAvailable();
};
// 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 : QObject {
Q_OBJECT
VIVY_UNMOVABLE_OBJECT(LogSink)
explicit LogSink() noexcept;
public:
static std::shared_ptr<LogSink> newSink() noexcept;
~LogSink() noexcept override;
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:
StlLogSinkUpdater *workerThread;
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;
};
}
Chargement en cours