//! Logging utilities for the Support Library //! Using Standard C++ #pragma once #include #include #include #include #include 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(source); auto cmpbits = std::bit_cast(component); return static_cast((static_cast(srcbits) << 16 | cmpbits)); } /// Splits a channel ID into its individual components. constexpr static void ChannelIdToComponents(ChannelId id, MessageSource& src, MessageComponentSource& component) { src = static_cast((static_cast(id) & 0xffff0000) >> 16); component = static_cast(static_cast(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 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 inline void Debug(std::string_view fmt, Args... args) { VOut(MessageSeverity::Debug, fmt, std::make_format_args(std::forward(args)...)); } template inline void Info(std::string_view fmt, Args... args) { VOut(MessageSeverity::Info, fmt, std::make_format_args(std::forward(args)...)); } template inline void Warning(std::string_view fmt, Args... args) { VOut(MessageSeverity::Warning, fmt, std::make_format_args(std::forward(args)...)); } template inline void Error(std::string_view fmt, Args... args) { VOut(MessageSeverity::Error, fmt, std::make_format_args(std::forward(args)...)); } template inline void Fatal(std::string_view fmt, Args... args) { VOut(MessageSeverity::Fatal, fmt, std::make_format_args(std::forward(args)...)); } private: void VOut(MessageSeverity severity, std::string_view format, std::format_args args); ChannelId channelId; }; template constexpr void LogDebug(std::string_view format, Args... args) { Logger::Global().Debug(format, std::forward(args)...); } template constexpr void LogInfo(std::string_view format, Args... args) { Logger::Global().Info(format, std::forward(args)...); } template constexpr void LogWarning(std::string_view format, Args... args) { Logger::Global().Warning(format, std::forward(args)...); } template constexpr void LogError(std::string_view format, Args... args) { Logger::Global().Error(format, std::forward(args)...); } template constexpr void LogFatal(std::string_view format, Args... args) { Logger::Global().Fatal(format, std::forward(args)...); } } // namespace base