SSX3LobbyServer/lib/base/logger.hpp

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