213 lines
6.6 KiB
C++
213 lines
6.6 KiB
C++
|
//! Logging utilities for the Support Library
|
||
|
//! Using Standard C++ <format>
|
||
|
#pragma once
|
||
|
|
||
|
#include <chrono>
|
||
|
#include <cstdint>
|
||
|
#include <format>
|
||
|
#include <base/types.hpp>
|
||
|
#include <vector>
|
||
|
|
||
|
namespace base {
|
||
|
|
||
|
namespace logger_detail {
|
||
|
|
||
|
enum class MessageSeverity { Debug, Info, Warning, Error, Fatal };
|
||
|
|
||
|
/// A message source. In our tree, this is essentially defined as
|
||
|
/// each library the server's made up of.
|
||
|
enum class MessageSource : u16 { Main = 0x1000, Base, Http, Server };
|
||
|
|
||
|
/// A component source. This basically defines "what" part of a source
|
||
|
/// is creating the message. For instance, the HTTP library has multiple
|
||
|
/// components which can each log messages.
|
||
|
enum class MessageComponentSource : u16 {
|
||
|
// The "default" global logger channel
|
||
|
Main_Global = 0x1000,
|
||
|
|
||
|
Base_Assertions = 0x10,
|
||
|
|
||
|
Http_Server = 0x50,
|
||
|
Http_Router,
|
||
|
Http_WebSocketClient,
|
||
|
|
||
|
Server_Server = 0x60,
|
||
|
};
|
||
|
|
||
|
/// A channel ID. `enum class`es are used to avoid confusion with a normal u32,
|
||
|
/// and to also add addional interface type safety.
|
||
|
///
|
||
|
/// A channel ID looks something like:
|
||
|
/// ssssssss ssssssss cccccccc cccccccc
|
||
|
///
|
||
|
/// Where:
|
||
|
/// s: message source
|
||
|
/// c: message component source
|
||
|
enum class ChannelId : u32 {};
|
||
|
|
||
|
/// Create a channel ID from a source and component source.
|
||
|
constexpr static ChannelId MakeChannelId(MessageSource source, MessageComponentSource component) {
|
||
|
auto srcbits = std::bit_cast<u16>(source);
|
||
|
auto cmpbits = std::bit_cast<u16>(component);
|
||
|
|
||
|
return static_cast<ChannelId>((static_cast<u32>(srcbits) << 16 | cmpbits));
|
||
|
}
|
||
|
|
||
|
/// Splits a channel ID into its individual components.
|
||
|
constexpr static void ChannelIdToComponents(ChannelId id, MessageSource& src, MessageComponentSource& component) {
|
||
|
src = static_cast<MessageSource>((static_cast<u32>(id) & 0xffff0000) >> 16);
|
||
|
component = static_cast<MessageComponentSource>(static_cast<u32>(id) & 0x0000ffff);
|
||
|
}
|
||
|
|
||
|
/// The default global channel ID.
|
||
|
constexpr static auto ChannelGlobal = MakeChannelId(MessageSource::Main, MessageComponentSource::Main_Global);
|
||
|
|
||
|
/// Message data. This is only used by logger sinks.
|
||
|
struct MessageData {
|
||
|
std::chrono::system_clock::time_point time;
|
||
|
MessageSeverity severity;
|
||
|
|
||
|
ChannelId channelId; // the channel ID.
|
||
|
|
||
|
std::string message; // DO NOT SET THIS, IT WILL BE OVERWRITTEN AND I WILL BE VERY SAD -lily
|
||
|
};
|
||
|
|
||
|
/// A logger sink. Outputs messages to some device (a TTY), a file,
|
||
|
/// what have you. Basically a interface for the logger to spit stuff out.
|
||
|
///
|
||
|
/// # Notes
|
||
|
/// Sinks do not run on the main application thread. Instead, they run on a
|
||
|
/// single Support Library internal thread which the only purpose of is to
|
||
|
/// stream logger messages and output them, from a internally managed queue.
|
||
|
///
|
||
|
/// This is techinically a implementation detail, but for implementers of logger
|
||
|
/// sinks it's probably a good idea for them to know just in case.
|
||
|
///
|
||
|
/// Do note that you probably don't have to actually do any thread-safety magic
|
||
|
/// in your sink implementation, since it's only a single thread/etc...
|
||
|
struct Sink {
|
||
|
virtual void OutputMessage(std::string_view message) = 0;
|
||
|
};
|
||
|
|
||
|
/// Shared global state all loggers use.
|
||
|
struct LoggerGlobalState {
|
||
|
static LoggerGlobalState& The();
|
||
|
|
||
|
void AttachSink(Sink& sink);
|
||
|
|
||
|
void OutputMessage(const MessageData& data);
|
||
|
|
||
|
/// Get the current log level.
|
||
|
MessageSeverity GetLogLevel() const { return logLevel; }
|
||
|
|
||
|
/// Set the current log level.
|
||
|
void SetLogLevel(MessageSeverity newLogLevel) { logLevel = newLogLevel; }
|
||
|
|
||
|
private:
|
||
|
LoggerGlobalState();
|
||
|
~LoggerGlobalState();
|
||
|
|
||
|
std::vector<Sink*> sinks;
|
||
|
MessageSeverity logLevel { MessageSeverity::Info };
|
||
|
|
||
|
static void LoggerThread();
|
||
|
};
|
||
|
|
||
|
inline auto& GlobalState() {
|
||
|
return LoggerGlobalState::The();
|
||
|
}
|
||
|
|
||
|
} // namespace logger_detail
|
||
|
|
||
|
using logger_detail::ChannelId;
|
||
|
using logger_detail::MessageComponentSource;
|
||
|
using logger_detail::MessageSource;
|
||
|
|
||
|
using logger_detail::MakeChannelId;
|
||
|
|
||
|
/// Attach a sink to all Support loggers; allowing it to output logger messages.
|
||
|
inline void AttachSink(logger_detail::Sink& sink) {
|
||
|
logger_detail::GlobalState().AttachSink(sink);
|
||
|
}
|
||
|
|
||
|
inline logger_detail::MessageSeverity GetLogLevel() {
|
||
|
return logger_detail::GlobalState().GetLogLevel();
|
||
|
}
|
||
|
|
||
|
inline void SetLogLevel(logger_detail::MessageSeverity newLevel) {
|
||
|
return logger_detail::GlobalState().SetLogLevel(newLevel);
|
||
|
}
|
||
|
|
||
|
struct Logger {
|
||
|
using MessageSeverity = logger_detail::MessageSeverity;
|
||
|
using MessageData = logger_detail::MessageData;
|
||
|
using Sink = logger_detail::Sink;
|
||
|
|
||
|
/// Get the global instance of the logger.
|
||
|
static Logger& Global();
|
||
|
|
||
|
Logger() : Logger(logger_detail::ChannelGlobal) {}
|
||
|
|
||
|
constexpr explicit Logger(ChannelId channel) { this->channelId = channel; }
|
||
|
|
||
|
Logger(const Logger&) = delete;
|
||
|
Logger(Logger&&) = delete;
|
||
|
|
||
|
template <class... Args>
|
||
|
inline void Debug(std::string_view fmt, Args... args) {
|
||
|
VOut(MessageSeverity::Debug, fmt, std::make_format_args(std::forward<Args>(args)...));
|
||
|
}
|
||
|
|
||
|
template <class... Args>
|
||
|
inline void Info(std::string_view fmt, Args... args) {
|
||
|
VOut(MessageSeverity::Info, fmt, std::make_format_args(std::forward<Args>(args)...));
|
||
|
}
|
||
|
|
||
|
template <class... Args>
|
||
|
inline void Warning(std::string_view fmt, Args... args) {
|
||
|
VOut(MessageSeverity::Warning, fmt, std::make_format_args(std::forward<Args>(args)...));
|
||
|
}
|
||
|
|
||
|
template <class... Args>
|
||
|
inline void Error(std::string_view fmt, Args... args) {
|
||
|
VOut(MessageSeverity::Error, fmt, std::make_format_args(std::forward<Args>(args)...));
|
||
|
}
|
||
|
|
||
|
template <class... Args>
|
||
|
inline void Fatal(std::string_view fmt, Args... args) {
|
||
|
VOut(MessageSeverity::Fatal, fmt, std::make_format_args(std::forward<Args>(args)...));
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
void VOut(MessageSeverity severity, std::string_view format, std::format_args args);
|
||
|
|
||
|
ChannelId channelId;
|
||
|
};
|
||
|
|
||
|
template <class... Args>
|
||
|
constexpr void LogDebug(std::string_view format, Args... args) {
|
||
|
Logger::Global().Debug(format, std::forward<Args>(args)...);
|
||
|
}
|
||
|
|
||
|
template <class... Args>
|
||
|
constexpr void LogInfo(std::string_view format, Args... args) {
|
||
|
Logger::Global().Info(format, std::forward<Args>(args)...);
|
||
|
}
|
||
|
|
||
|
template <class... Args>
|
||
|
constexpr void LogWarning(std::string_view format, Args... args) {
|
||
|
Logger::Global().Warning(format, std::forward<Args>(args)...);
|
||
|
}
|
||
|
|
||
|
template <class... Args>
|
||
|
constexpr void LogError(std::string_view format, Args... args) {
|
||
|
Logger::Global().Error(format, std::forward<Args>(args)...);
|
||
|
}
|
||
|
|
||
|
template <class... Args>
|
||
|
constexpr void LogFatal(std::string_view format, Args... args) {
|
||
|
Logger::Global().Fatal(format, std::forward<Args>(args)...);
|
||
|
}
|
||
|
|
||
|
} // namespace base
|