2023-12-01 08:32:37 -05:00

210 lines
7.3 KiB
C++

#pragma once
#include <initializer_list>
#include "pros/rtos.hpp"
#define FMT_HEADER_ONLY
#include "fmt/core.h"
#include "fmt/args.h"
#include "lemlib/logger/message.hpp"
namespace lemlib {
/**
* @brief A base for any sink in LemLib to implement.
*
* Sinks are LemLib's abstraction for destinations that logged messages can be sent to. They are the backbone of the
* logging implementation. A sink could send information to anything, to stdout, to a file, or even the UI on the
* brain screen.
*/
class BaseSink {
public:
BaseSink() = default;
/**
* @brief Construct a new combined sink
*
* <h3> Example Usage </h3>
* @code
* lemlib::BaseSink combinedSink({lemlib::telemetrySink(), lemlib::infoSink()});
* combinedSink.info("This will be sent to both sinks!");
* @endcode
*
* @param sinks The sinks that will have messages sent to them when
*/
BaseSink(std::initializer_list<std::shared_ptr<BaseSink>> sinks);
/**
* @brief Set the lowest level.
* If this is a combined sink, this operation will
* apply for all the parent sinks.
* @param level
*
* If messages are logged that are below the lowest level, they will be ignored.
* The hierarchy of the levels is as follows:
* - INFO
* - DEBUG
* - WARN
* - ERROR
* - FATAL
*/
void setLowestLevel(Level level);
/**
* @brief Log a message at the given level
* If this is a combined sink, this operation will
* apply for all the parent sinks.
*
* @tparam T
* @param level The level at which to send the message.
* @param format The format that the message will use. Use "{}" as placeholders.
* @param args The values that will be substituted into the placeholders in the format.
*
* <h3> Example Usage </h3>
* @code
* sink.log(lemlib::Level::INFO, "{} from the logger!", "Hello");
* @endcode
*/
template <typename... T> void log(Level level, fmt::format_string<T...> format, T&&... args) {
if (!sinks.empty()) {
for (std::shared_ptr<BaseSink> sink : sinks) { sink->log(level, format, std::forward<T>(args)...); }
return;
}
if (level < lowestLevel) { return; }
// substitute the user's arguments into the format.
std::string messageString = fmt::format(format, std::forward<T>(args)...);
Message message = Message {.level = level, .time = pros::millis()};
// get the arguments
fmt::dynamic_format_arg_store<fmt::format_context> formattingArgs = getExtraFormattingArgs(message);
formattingArgs.push_back(fmt::arg("time", message.time));
formattingArgs.push_back(fmt::arg("level", message.level));
formattingArgs.push_back(fmt::arg("message", messageString));
std::string formattedString = fmt::vformat(logFormat, std::move(formattingArgs));
message.message = std::move(formattedString);
sendMessage(std::move(message));
}
/**
* @brief Log a message at the debug level.
* If this is a combined sink, this operation will
* apply for all the parent sinks.
* @tparam T
* @param format
* @param args
*/
template <typename... T> void debug(fmt::format_string<T...> format, T&&... args) {
log(Level::DEBUG, format, std::forward<T>(args)...);
}
/**
* @brief Log a message at the info level
* If this is a combined sink, this operation will
* apply for all the parent sinks.
* @tparam T
* @param format
* @param args
*/
template <typename... T> void info(fmt::format_string<T...> format, T&&... args) {
log(Level::INFO, format, std::forward<T>(args)...);
}
/**
* @brief Log a message at the warn level
* If this is a combined sink, this operation will
* apply for all the parent sinks.
* @tparam T
* @param format
* @param args
*/
template <typename... T> void warn(fmt::format_string<T...> format, T&&... args) {
log(Level::WARN, format, std::forward<T>(args)...);
}
/**
* @brief Log a message at the error level.
* If this is a combined sink, this operation will
* apply for all the parent sinks.
* @tparam T
* @param format
* @param args
*/
template <typename... T> void error(fmt::format_string<T...> format, T&&... args) {
log(Level::ERROR, format, std::forward<T>(args)...);
}
/**
* @brief Log a message at the fatal level
* If this is a combined sink, this operation will
* apply for all the parent sinks.
* @tparam T
* @param format
* @param args
*/
template <typename... T> void fatal(fmt::format_string<T...> format, T&&... args) {
log(Level::FATAL, format, std::forward<T>(args)...);
}
protected:
/**
* @brief Log the given message
*
* @param message
*/
virtual void sendMessage(const Message& message);
/**
* @brief Set the format of messages that the sink sends
*
* @param format
*
* Changing the format of the sink changes the way each logged message looks. The following named formatting
* specifiers can be used:
* - {time} The time the message was sent in milliseconds since the program started.
* - {level} The level of the logged message.
* - {message} The message itself.
*
* <h3> Example Usage </h3>
* @code
* infoSink()->setFormat("[LemLib] -- {time} -- {level}: {Message}");
* infoSink()->info("hello");
* // This will be formatted as:
* // "[LemLIb] -- 10 -- INFO: hello"
* @endcode
*/
void setFormat(const std::string& format);
/**
* @brief Get the extra named arguments for formatting
*
* Can be overridden to add extra named arguments to the sink's format.
* <h3> Example Usage </h3>
*
* The following code would add a {zero} formatting argument which could be used in setFormat method of this
* sink.
*
* @code
* fmt::dynamic_format_arg_store<fmt::format_context>
* ExampleSink::getExtraFormattingArgs(const Message& messageInfo) {
* fmt::dynamic_format_arg_store<fmt::format_context> args;
* args.push_back(fmt::arg("zero", 0);
* return args;
* }
* @endcode
*
* @return fmt::dynamic_format_arg_store<fmt::format_context>
*/
virtual fmt::dynamic_format_arg_store<fmt::format_context> getExtraFormattingArgs(const Message& messageInfo);
private:
Level lowestLevel = Level::DEBUG;
std::string logFormat;
std::vector<std::shared_ptr<BaseSink>> sinks {};
};
} // namespace lemlib