在对已发布程序查找bug时,查看日志就变得非常重要,可以快速帮助我们定位问题,解决问题。所有在程序中添加日志模块就显得非常中重要了。
在写日志模块时首先我们需要了解我们常用的qDebug,qInfo等输出打印信息的目标类QMessageLogger。
QMessageLogger类生成日志消息。
QMessageLogger用于为Qt日志框架生成消息。通过qDebug()、qInfo()、qWarning()、qCritical或qFatal()函数来使用它,这些函数实际上都是宏:例如,qDebug()扩展为QMessageLogger(__FILE__, __LINE__, Q_FUNC_INFO).debug()用于调试构建,QMessageLogger(0,0,0).debug()用于发布构建。
了解后再就是需要了解QMessageLogContext类提供关于日志消息的附加信息。该类提供了关于生成qDebug()、qInfo()、qWarning()、qCritical()或qFatal()消息的源代码位置的信息。
至此就可以开始着手编写外层日志模块代码了
// 此处因为是写在main.cpp文件中的,所以用的是c风格来进行编写
void logerOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
QByteArray localMsg = msg.toLocal8Bit();
// 根据设置不同的的前置打印宏判断输出不同打印信息,如果有需要可以给每类信息设置不同的文字颜色来区分
switch (type)
{
case QtDebugMsg:
fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtInfoMsg:
fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtWarningMsg:
fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtCriticalMsg:
fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtFatalMsg:
fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
}
// 创建单例线程来实时获取记录程序输出的各种日志信息
LogerThread::instance()->log(msg);
}
安装前面定义的Qt消息处理程序。返回指向上一个消息处理程序的指针。
static void prepareOther()
{
QDateTime time = QDateTime::currentDateTime();
int curTime = time.toTime_t();
// 设置日志多少天删除
int deleteTime = 7 * 24 * 60 * 60;
// 是否输出日志
if(Common::instance()->getIsOutPutLog() == 1)
{
// 删除log文件夹里面超过7天的文件
QDir dir(QApplication::applicationDirPath()+"/log");
if (dir.exists())
{
dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
QFileInfoList fileList = dir.entryInfoList();
foreach (QFileInfo file, fileList)
{
if(file.isFile())
{
int modifyTime = file.lastModified().toTime_t();
if(curTime - modifyTime > deleteTime)
{
file.dir().remove(file.fileName());
}
}
}
}
qInstallMessageHandler(logerOutput);
}
}
消息处理程序是一个输出调试消息、警告、关键和致命错误消息的函数。Qt库(调试模式)包含数百条警告消息,当内部错误(通常是无效的函数参数)发生时,这些警告消息将被打印出来。在发布模式下构建的Qt也包含这样的警告,除非在编译期间设置了QT_NO_WARNING_OUTPUT和/或QT_NO_DEBUG_OUTPUT。如果实现自己的消息处理程序,则可以完全控制这些消息。
默认消息处理程序将消息打印到X11下的标准输出或Windows下的调试器。如果是致命消息,则应用程序立即中止。
只能定义一个消息处理程序,因为这通常是在应用程序范围内完成的,以控制调试输出。
要恢复消息处理程序,请调用qInstallMessageHandler(0)。
然后就是调用线程与主线程并发执行
QtConcurrent::run(prepareOther);
Qt Concurrent模块扩展了Qt Core模块中的基本线程支持,并简化了可以在所有可用CPU内核上并行执行的代码开发。
——————————————————分割线——————————————————————
接下来就是实现前面提到的单例日志写入线程
#ifndef LOGERTHREAD_H
#define LOGERTHREAD_H
#include <QThread>
#include <QFile>
#include <QStringList>
#include <QMutexLocker>
class LogerThread : public QThread
{
Q_OBJECT
private:
explicit LogerThread(QObject *parent = nullptr);
public:
~LogerThread();
void log(QString msg); // 日志信息设置
void run();
// 创建单例
static LogerThread* instance()
{
if(!_pInstance)
{
QMutexLocker mutexLocker(&_mutex);
if(!_pInstance)
{
LogerThread *pInstance = new LogerThread();
_pInstance = pInstance;
}
}
return _pInstance;
}
private:
static LogerThread *_pInstance;
static QMutex _mutex;
bool _isRun; // 线程是否运行标志
QFile *_pLogFile; // 日志文件
QString _logPath; // 日志文件存储路径
QStringList _msgList; // 日志缓冲的list
};
#endif // LOGERTHREAD_H
#include "logerthread.h"
#include <QDir>
#include <QFileInfoList>
#include <QDateTime>
#include <QApplication>
#include <QString>
LogerThread * LogerThread::_pInstance = 0;
QMutex LogerThread::_mutex;
LogerThread::LogerThread(QObject *parent) :
QThread(parent), _isRun(true), _pLogFile(nullptr)
{
_logPath = tr("%1/log").arg(qApp->applicationDirPath());
this->start();
}
LogerThread::~LogerThread()
{
_isRun = false;
this->terminate();
this->wait();
}
void LogerThread::run()
{
QDir logDir(_logPath);
fprintf(stdout, "#LogerThread# run() %s\n", _logPath.toStdString().data());
if (!logDir.exists())
{
logDir.mkpath(_logPath);
}
// 限制日志文件最大10M
qint64 maxLogSize = 1024*1024*10;
// 通过时间戳来对文件命名
QString logPath = tr("%1/%2.log").arg(_logPath).arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hhmmss_zzz"));
_pLogFile = new QFile(logPath);
{
while (_isRun)
{
// 超过最大限制则重新创建新文件
if (_pLogFile->isOpen() && _pLogFile->size() >= maxLogSize)
{
_pLogFile->close();
}
if (!_pLogFile->isOpen())
{
QString logPath = tr("%1/%2.log").arg(_logPath).arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hhmmss_zzz"));
_pLogFile->setFileName(logPath);
// 注意写入时需要以追加形式打开文件
if (!_pLogFile->open(QFile::WriteOnly | QFile::Append))
fprintf(stdout, "#LogerThread# run() open log file fail! %s %s\n", logPath.toLocal8Bit().data(), _pLogFile->errorString().toStdString().data());
this->msleep(1000);
continue;
}
QByteArray logText;
while (_msgList.length() > 0 && _isRun)
{
logText = _msgList.first().toLocal8Bit();
logText.append("\n");
if (_pLogFile->write(logText) == -1)
break;
_msgList.removeFirst();
}
this->msleep(500);
}
_pLogFile->close();
}
}
void LogerThread::log(QString msg)
{
QString msgStr = QString("TM:[%1],").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz"))+msg;
_msgList.append(msgStr);
}