2

我有一个正在使用的小应用程序,它需要生成一个非常详细的日志。我已经实现了一个简单的单例 Logger 类,它看起来像这样

#ifndef LOGGER_H
#define LOGGER_H

#include <QObject>
#include <QMutex>
#include <QFile>

class Logger : public QObject
{
    Q_OBJECT
public:
    static Logger* sharedInstance()
    {
        static QMutex mutex;

        if (!m_sharedInstance)
        {
            mutex.lock();

            if(!m_sharedInstance)
                m_sharedInstance = new Logger;

            mutex.unlock();
        }

        return m_sharedInstance;
    }

    static void dropInstance()
    {
        static QMutex mutex;
        mutex.lock();
        delete m_sharedInstance;
        m_sharedInstance = 0;
        mutex.unlock();
    }

    void setLogFilePathAndOpen(QString path);
    void logMessage(QString message);
    void logMessageWorker(QString message);
    void closeLog();

private:
    Logger() {}

    Logger(const Logger &);
    Logger& operator=(const Logger &);

    static Logger *m_sharedInstance;

    QFile m_logFile;

signals:

public slots:

};

#endif // LOGGER_H

#include <QDateTime>
#include <QtConcurrentRun>

#include "logger.h"

Logger* Logger::m_sharedInstance = 0;

void Logger::setLogFilePathAndOpen(QString path)
{
    m_logFile.setFileName(path);
    m_logFile.open(QIODevice::Append | QIODevice::Text);
}

void Logger::logMessage(QString message)
{
    //TODO calling QtConcurrent causes about a 22% drop in performance. Look at other ways to do this.
    QtConcurrent::run(this, &Logger::logMessageWorker, message);
}

void Logger::logMessageWorker(QString message)
{
    QTextStream out(&m_logFile);
    out << tr("%1: %2\n").arg(QDateTime::currentDateTime().toString()).arg(message);
}

void Logger::closeLog()
{
    m_logFile.close();
}

现在我对 Qt 和 C++ 有点陌生,也许这都是错误的,所以对我来说放轻松:)。现在我得到了大约 22% 的性能下降,与不记录相比,使用这种方法是我能够管理的最好的方法。我想性能影响来自创建 QtConcurrent 线程。

我想我的问题是,这将是最好的方法还是有更好的方法来实现它,这将使性能更接近根本不记录。我知道无论应用程序的日志记录会变慢,但我正在尝试尽可能减少这种情况。

4

3 回答 3

3

由于您愿意忍受异步日志记录,因此您通常希望日志记录在单个线程中完成,并有一个线程安全的队列向其提供数据。周围有很多线程安全队列,例如我在上一个答案中发布的一个。您(显然)想使用 Qt 原语重写它,但我相信这足够相似,重写将主要是更改您使用的名称。

从那里开始,日志记录变得相当简单。日志线程只是从队列中检索数据,将其写入日志,然后重复。由于日志线程是唯一直接接触日志的线程,它根本不需要做任何锁定——它本质上是单线程代码,所有的锁定都在队列中处理。

于 2012-04-16T18:29:02.033 回答
1

有两种方法可以解决这个问题:

  1. 重用现有的日志框架,或者至少窃取它的想法。
  2. 停止记录这么多(但你仍然应该做 1。)

最佳实践?

通常,您应该将日志排队到由工作线程出队的队列中。有许多可能的实现(无锁队列、池队列等),不过没关系,只要选择一个,你就会得到很好的性能。

当然,简单地重用现有框架会好得多。您是否查看过 Boost.Log 或 Pantheios ?


另一件需要注意的事情是你不应该记录太多。日志在应用程序中通常很少见。

  • 对于调试运行,您可以assert在出现问题时自由地获得完整的核心转储,更有帮助
  • 对于发布运行,您可以使用条件日志记录仅在存在异常时记录

条件日志记录技巧可能有点违反直觉。这个想法是使用对象的析构函数来实际记录,并且仅当std::uncaught_exception()返回时true。当然,这意味着此日志记录应该是无异常的,因此除非您对自己的编程技能非常有信心,否则我建议您预先格式化消息。

于 2012-04-16T18:18:35.723 回答
0

我正在使用 qDebug、qWarning 等进行日志记录。然后,以这种方式将事件消息重定向到日志文件:

...
qInstallMsgHandler(messageHandler);
...

#define LOG_FILE "path/to/file.log"

void messageHandler(QtMsgType type, const char *msg)
{
#define NOW QDateTime::currentDateTime().toString("MM:dd: hh:mm:ss")
  QString out;
  switch (type) {
  case QtDebugMsg:    out = QString("%1: %2\n").arg(NOW).arg(msg); break;
  case QtWarningMsg:  out = QString("%1: warning: %2\n").arg(NOW).arg(msg); break;
  case QtCriticalMsg: out = QString("%1: critical: %2\n").arg(NOW).arg(msg); break;
  case QtFatalMsg:    out = QString("%1: fatal: %2\n").arg(NOW).arg(msg); break;
  default: return;
  }

  QFile file(LOG_FILE);
  if (file.open(QIODevice::WriteOnly | QIODevice::Append))
    QTextStream(&file) << out;
#undef NOW
}

这种方法是线程安全的,尽管它不是最有效的方法,因为文件 IO 没有被缓存。

于 2012-12-05T18:53:47.340 回答