Closed developer3067 closed 1 month ago
hey,
Quill uses macros for logging to avoid performance issues associated with argument evaluation for unused log levels (e.g., debug). If logging were implemented as a function, even empty functions would require evaluating arguments for any log level, leading to potential performance degradation.
Additionally, Quill creates metadata as constexpr for performance reasons. This approach allows for efficient logging without sacrificing speed.
While you could wrap Quill under an interface if you only need to pass pre-formatted log messages (like "{}", string), doing so would mean sacrificing many of the benefits Quill provides, such as async formatting
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/Logger.h"
#include "quill/sinks/ConsoleSink.h"
#include <iostream>
#include <string>
#include <utility>
#define MY_LOG(log_service, log_level, message) \
if (log_level >= log_service->get_log_level()) \
{ \
log_service->log(log_level, message); \
}
enum LogLevel
{
DEBUG,
INFO
};
class ILogService
{
public:
virtual void log(LogLevel log_level, std::string const& message) = 0;
virtual void set_log_level(LogLevel log_level) = 0;
LogLevel get_log_level() const noexcept { return _log_level; }
protected:
LogLevel _log_level;
};
class LogService : public ILogService
{
public:
LogService()
{
quill::BackendOptions backend_options;
quill::Backend::start(backend_options);
quill::PatternFormatterOptions pfo;
pfo.format_pattern = "%(time) [%(thread_id)] LOG_%(log_level:<9) %(logger:<12) %(message)";
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
logger = quill::Frontend::create_or_get_logger("root", console_sink, pfo);
}
void set_log_level(LogLevel log_level)
{
_log_level = log_level;
if (log_level == LogLevel::INFO)
{
logger->set_log_level(quill::LogLevel::Info);
}
else if (log_level == LogLevel::DEBUG)
{
logger->set_log_level(quill::LogLevel::Debug);
}
}
void log(LogLevel log_level, std::string const& message)
{
quill::LogLevel level;
if (log_level == LogLevel::INFO)
{
level = quill::LogLevel::Info;
}
else
{
level = quill::LogLevel::Debug;
}
QUILL_LOG_DYNAMIC(logger, level, "{}", message);
}
private:
quill::Logger* logger;
};
int main()
{
LogService ls;
ls.set_log_level(LogLevel::DEBUG);
ILogService* ils = &ls;
MY_LOG(ils, LogLevel::DEBUG, "test");
MY_LOG(ils, LogLevel::INFO, fmtquill::format("The answer is {} and {}", 33, 13));
}
You could do that, but you'll lose formatting outside the hotpath, as well as file, line, and function information, which isn't ideal. Probably you can manually inject the file/line info in the formatted string message yourself in the log() function if you need them. For optimal performance, I recommend logging directly through the provided macros. If you need to support multiple logging libraries, consider wrapping their macros in your own, allowing you to switch the underlying logic via a compiler flag, for example.
Hi,
Is there anyway to make quill dependency injection friendly? For example if I want to create an interface e.g.
ILogService
and then use quill in an implementation e.g.LogService
and injectILogService* log_service
into other classes, since quill uses macros likeLOG_INFO
this doesn't seem to be possible.