QMessageLogger魔法是如何工作的?

How does QMessageLogger magic work?

本文关键字:工作 何工作 QMessageLogger      更新时间:2023-10-16

我正在为QT应用程序开发一个日志记录器框架。出于理解和学习的目的,我没有直接使用QMessageLogger。有一件事关于一个QMessageLogger功能,我真的很想在我的记录器,但我不知道它是如何工作的。以qDebug宏为例:

#define qDebug QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).debug

可以用两种方式调用这个函数:方法1:

qDebug("abc = %u", abc);

方式2:

qDebug() << "abc = " << abc;

我正在看库代码,但我不能完全理解它是如何实现的,一个可以使用va_args以及一些流对象与QMessageLogger工作。我怎样才能达到这样的效果呢?我真的很感激所有的帮助,如果有一个例子我会很感激。

这是我的print方法体。我需要用"流"的方式实现类似的功能:

/*!
 * brief Adds the log line to the print queue.
 * param lvl: Log level of the line.
 * param text: Formatted input for va_list.
 */
void CBcLogger::print(MLL::ELogLevel lvl, const char* text, ...)
{
    // check if logger initialized
    if (!m_loggerStarted)
        return;
    // check if log level sufficient
    if (lvl > m_setLogLvl)
        return;
    logLine_t logline;
    logline.loglvl = lvl;
    logline.datetime = QDateTime::currentDateTime();
    va_list argptr;
    va_start(argptr, text);
    char* output = NULL;
    if (vasprintf(&output, text, argptr))
    {
        logline.logstr = output;
        delete output;
    }
    va_end(argptr);
    emit addNewLogLine(logline);
}

首先,您需要理解以下内容

QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).debug

上面的行构造了一个QMessageLogger实例并立即访问它的debug成员。因为它是一个宏,你在它后面写的代码也很重要。

如果您查看QMessageLogger::debug是什么,您将看到四个重载,其中前两个与您的问题相关:

void debug(const char *msg, ...) const Q_ATTRIBUTE_FORMAT_PRINTF(2, 3);
QDebug debug() const;
QDebug debug(const QLoggingCategory &cat) const;
QDebug debug(CategoryFunction catFunc) const;

现在事情应该简单了。如果调用qDebug("abc = %u", abc),就调用了第一个重载,扩展后的宏如下:

QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).debug("abc = %u", abc)

大致等于

QMessageLogger temp(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC);
temp.debug("abc = %u", abc);

在第二种情况下,你调用一个重载,返回一个QDebug对象。QDebug已重载operator<<。展开后的宏如下所示:

QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).debug() << "abc = " << abc;

大致等于

QMessageLogger temp(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC);
QDebug anotherTemp = temp.debug();
anotherTemp << "abc = " << abc;

下面是这样一个记录器的简单实现:

void addNewLogLine(char const* ptr){
    cout << "addNewLogLine: " << ptr << endl;
}
struct LoggerHelper
{
    std::stringstream s;
    explicit LoggerHelper()=default;
    LoggerHelper(LoggerHelper&&) = default;
    ~LoggerHelper(){
        auto str = s.str();
        addNewLogLine(str.c_str());
    }
    template<typename T>
    LoggerHelper& operator<<(T const& val){
        s << val;
        return *this;
    }
};
struct Logger
{
    void operator()(char const* fmt, ...) const {
        char* buf;
        va_list args;
        va_start(args, fmt);
        vasprintf(&buf, fmt, args);
        va_end(args);
        addNewLogLine(buf);
        free(buf);
    }
    LoggerHelper operator()() const {
        return LoggerHelper{};
    }
};

演示

几个笔记:

  • 我坚持你的界面,但就个人而言,我会使用可变模板而不是va_args
  • 你应该freevasprintf返回的缓冲区。freedeletedelete[]不可互换
  • 我使用std::stringstream,但将其更改为QTextStream或任何其他应该足够简单
  • 如果您允许log << "foo" << "bar"语法而不是log() << "foo" << "bar",则不需要将helper实现为单独的类