Sélectionner une révision Git
Log.hh 7,03 Kio
#pragma once
#include "Utils.hh"
// Create a logger with a category
#define VIVY_GET_LOGGER(sink, cat) std::make_shared<Logger>((sink).get(), 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__) };
// Install logger for the object.
#define VIVY_LOGGABLE_OBJECT(sink, name, logger) \
std::shared_ptr<Logger> logger{ VIVY_GET_LOGGER(sink, name) }; \
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); }
#define VIVY_LOG_CTOR(classname) logDebug() << #classname "::CTOR "
#define VIVY_LOG_DTOR(classname) logDebug() << #classname "::DTOR "
// Install logger, use the global LogSink
#define VIVY_APP_LOGGABLE_OBJECT(name, logger) \
VIVY_LOGGABLE_OBJECT(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.
enum class LogLevel : int {
Critical = std::numeric_limits<int>::max(), // Will trigger qFatal
Error = 4,
Warning = 3,
Info = 2,
Debug = 1,
None = std::numeric_limits<int>::min() // In option setup to disable logs
};
// 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)
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 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;
}
private:
std::mutex messageQueueLock{};
std::vector<std::tuple<const std::string_view, LogMessage>> messageQueue;
std::vector<std::shared_ptr<LogSinkDispatcher>> logDispatchers;
};
// 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 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 };
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 QString &) noexcept;
LogMessage &operator<<(const char *) noexcept;
LogMessage &operator<<(const char) noexcept;
LogMessage &operator<<(const int) noexcept;
LogMessage &operator<<(const 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) noexcept;
};
}