一、注册日志消息处理程序
- 调用qInstallMessageHandler(处理日志函数),打印调试信息(QtDebugMsg)、警告信息(QtWarningMsg)、严重错误(QtCriticalMsg)和致命的错误(QtFatalMsg)的消息
- Qt源码中会打印出很多warning信息和debug信息,可以通过QT_NO_WARNING_OUTPUT和/或QT_NO_DEBUG_OUTPUT 屏蔽
- 调用qInstallMessageHandler(0)可以恢复消息处理程序。
二、日志类型
QtDebugMsg(调试信息)
- 由qDebug()函数生成的消息。如果未注册日志处理程序,在windows上则会发送到控制台 否则,它将被发送到调试器,
- 如果在编译过程中定义了QT_NO_DEBUG_OUTPUT,则此函数不执行任何操作
QtWarningMsg(警告信息)
- 由qWarning() 函数生成,在Windows下,消息将发送到调试器
- 如果在编译过程中定义了QT_NO_WARNING_OUTPUT,则此函数不执行任何操作
- qt源码中会出现非常多的警告信息,无论是debug还是release都建议关
QtCriticalMsg、QtSystemMsg(关键消息、系统信息)
- 由于qCritical()函数生成的消息,在Windows下,消息将发送到调试器
- 常常包含一些系统错误信息、和关键错误信息
QtFatalMsg(错误信息)
- 由于qFatal(()函数生成的消息,在Windows下,消息将发送到调试器
- 常常包含一些致命的错误信息
三、自定义的日志架构
日志格式
- 日志打印格式: [2021-01-27 09:43:43][info] 信息
- 日志文件名称:yyyyMMddhhmmsszzz_log.txt
- 日志打印种类: QtCriticalMsg, QtFatalMsg, QtInfoMsg QtDebugMsg(debug模式下才存在)
- 信息打印完毕换行
日志动态查看工具
信息写入逻辑
- 创建QWriteElement在独立线程中管理日志文件(创建、写入、关闭)
- 创建QLogWorker 管理写入文件类型、日志路径、QWriteElement
- 创建QLog单例,绑定qInstallMessageHandler,管理QLogWorker
四、源代码
QWriteElement
#ifndef QWriteElement_h
#define QWriteElement_h
#include
#include
#include
#include
class QWriteElement : public QObject
{
Q_OBJECT
public:
explicit QWriteElement(QObject *parent = nullptr);
public:
// 设置文件名
void SetFileName(const QString &fileName);
// 写入信息
void WriteMesaage(const QString &message);
// 关闭写入
void Close();
// 获取日志大小
qint64 GetLogSize() const;
signals:
// 文件名改变
void FileNameChanged(const QString &name);
// 用来转线程
void MessageReady(const QString &message);
private slots:
// 收到日志消息
void OnMessageReady(const QString &mesage);
// 文件名称改变
void OnFileNameChanged(const QString &fileName);
private:
// 独立运行线程
QThread m_thread;
// 日志文件
QFile *m_pLogfile;
// 文件操作锁
QMutex m_lock;
};
#endif // QWriteElement_h
#include "QWriteElement.h"
#include
#include
#include
QWriteElement::QWriteElement(QObject *parent) :
QObject(parent),
m_pLogfile(NULL)
{
moveToThread(&m_thread);
m_thread.start();
connect(this, &QWriteElement::FileNameChanged, this, &QWriteElement::OnFileNameChanged);;
connect(this, &QWriteElement::MessageReady, this, &QWriteElement::OnMessageReady);
}
void QWriteElement::SetFileName(const QString &fileName)
{
emit FileNameChanged(fileName);
}
void QWriteElement::WriteMesaage(const QString &message)
{
emit MessageReady(message);
}
void QWriteElement::Close()
{
m_thread.quit();
m_thread.wait(2000);
if (m_pLogfile && m_pLogfile->isOpen())
{
m_lock.lock();
m_pLogfile->close();
delete m_pLogfile;
m_pLogfile = NULL;
m_lock.unlock();
}
}
qint64 QWriteElement::GetLogSize() const
{
if (m_pLogfile)
{
return m_pLogfile->size();
}
return 0;
}
void QWriteElement::OnMessageReady(const QString &mesage)
{
m_lock.lock();
if (m_pLogfile && m_pLogfile->isOpen())
{
m_pLogfile->write(mesage.toUtf8());
m_pLogfile->flush();
}
m_lock.unlock();
}
void QWriteElement::OnFileNameChanged(const QString &fileName)
{
if (m_pLogfile)
{
QFileInfo current(*m_pLogfile);
QFileInfo dest(fileName);
if (current.absoluteFilePath() == dest.absoluteFilePath())
{
return;
}
m_pLogfile->close();
m_pLogfile = NULL;
}
QFileInfo info(fileName);
QDir dir = info.absoluteDir();
if (!dir.exists())
{
QString path = dir.absolutePath();
dir.mkpath(path);
}
m_pLogfile = new QFile(fileName);
if (m_pLogfile->open(QIODevice::WriteOnly))
{
qDebug() << "创建日志文件成功" << fileName;
}
else
{
qDebug() << "创建日志文件失败" << fileName;
}
}
QLogWorker
#ifndef QLogWorker_h
#define QLogWorker_h
#include
#include
#include
#include
#include "HiLog.h"
class QWriteElement;
class QLogWorker : public QObject
{
Q_OBJECT
public:
QLogWorker(QObject *parent = NULL);
~QLogWorker();
public:
/** 设置日志标记 */
void SetLogFlags(HiLog::eLogFlags flags);
/** 设置日志路径 */
void SetLogPath(const QString &path);
/** 设置日志文件的最大大小(超过大小自动创建新的日志文件)(字节) */
void SetLogFileMaxSize(qint64 size);
/** 设置日志文件自动创建的间隔(超过此间隔自动创建新的日志文件)(秒) */
void SetLogFileCreatInterval(qint64 interval);
/** 设置日志文件自动保持的时间,超过此时长,会被自动清理(秒) */
void SetLogFileSaveTime(qint64 time);
/** 写入log */
void WriteLog(HiLog::eLogFlag flag, const QString &message);
/** 获取所有日志列表 */
QList GetLogList();
/** 获取日志路径 */
QString GetLogDir();
private slots:
/** 检查日志时间定时器 */
void OnLogTimeout();
private:
/** 判断日志文件名称是否需要改变 */
bool IsLogFileNameNeedChanged() const;
/** 清理旧日志 */
void ClearLogs();
/** 清理路径下的旧日志 */
void ClearLogsByPath(const QString &path);
private:
QString MakeFileName();
/** 添加一个Element */
void AddWriteElement(HiLog::eLogFlag flag);
/** 移除一个element */
void RemoveWriteElement(HiLog::eLogFlag flag);
private:
QString m_logPath; // 日志路径
HiLog::eLogFlags m_eFlags; // 日志标记-需要打印信息种类
QDateTime m_logTime; // 日志文件创建时间
QWriteElement* m_element; // 读写文件操作
QTimer* m_pLogTimer; // 检查日志已经创建的时间
qint64 m_maxSize; // 日志文件最大大小
qint64 m_creatInterval; // 创建文件时长
qint64 m_saveTime; // 保存时长
};
#endif // QLogWorker_h
#include "QLogWorker.h"
#include "QWriteElement.h"
#include
#include
#include
#include
#define MAXSIZE_LOG 100 * 1024 * 1024 // 100M
#define INTERVAL_CHANGEDLOG 12 * 60 * 60 // 12小时换一个日志文件
#define TIME_LOGSAVE 7 * 24 * 60 * 60 // 日志保存时长
#define FORMAT_TIME_FILENAME "yyyyMMddhhmmss"
QLogWorker::QLogWorker(QObject *parent) :
QObject(parent),
m_logPath(QString()),
m_eFlags(HiLog::eLogFlags()),
m_pLogTimer(new QTimer(this)),
m_maxSize(MAXSIZE_LOG),
m_creatInterval(INTERVAL_CHANGEDLOG),
m_saveTime(TIME_LOGSAVE),
m_element(new QWriteElement())
{
#if _DEBUG
SetLogFlags(HiLog::eLogFlags(HiLog::eLog_Info | HiLog::eLog_Critical | HiLog::eLog_Fatal | HiLog::eLog_Debug));
#else
SetLogFlags(HiLog::eLogFlags(HiLog::eLog_Info | HiLog::eLog_Critical | HiLog::eLog_Fatal));
#endif
// 选取一个可用的位置
SetLogPath(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + QDir::separator() + "Log");
m_pLogTimer->setInterval(5*1000);
connect(m_pLogTimer, &QTimer::timeout, this, &QLogWorker::OnLogTimeout);
}
QLogWorker::~QLogWorker()
{
m_element->Close();
delete m_element;
m_element = nullptr;
}
void QLogWorker::SetLogFlags(HiLog::eLogFlags flags)
{
m_eFlags = flags;
}
void QLogWorker::SetLogPath(const QString &path)
{
if (m_logPath != path || IsLogFileNameNeedChanged())
{
m_logTime = QDateTime::currentDateTime();
m_logPath = path;
if (!m_pLogTimer->isActive())
{
m_pLogTimer->start();
}
m_element->SetFileName(MakeFileName());
}
}
void QLogWorker::SetLogFileMaxSize(qint64 size)
{
m_maxSize = size;
}
void QLogWorker::SetLogFileCreatInterval(qint64 interval)
{
m_creatInterval = interval;
}
void QLogWorker::SetLogFileSaveTime(qint64 time)
{
m_saveTime = time;
}
void QLogWorker::WriteLog(HiLog::eLogFlag flag, const QString &message)
{
if (m_eFlags &flag)
{
m_element->WriteMesaage(message);
}
}
QList QLogWorker::GetLogList()
{
QList filePathList;
QString dirPath = GetLogDir();
QDir dir(dirPath);
QFileInfoList fileList = dir.entryInfoList();
for each (QFileInfo fileinfo in fileList)
{
if(fileinfo.isFile())
filePathList.append(fileinfo.fileName());
}
return filePathList;
}
QString QLogWorker::GetLogDir()
{
return m_logPath;
}
void QLogWorker::OnLogTimeout()
{
// 判断条件,重新创建日志文件
SetLogPath(m_logPath);
if (!QFile(MakeFileName()).exists())
{
// 删除之前的日志
ClearLogs();
}
}
bool QLogWorker::IsLogFileNameNeedChanged() const
{
qint64 fileIndex = m_logTime.toMSecsSinceEpoch() / (qreal)(m_creatInterval * 1000 * 2) + 0.5;
qint64 currentIndex = QDateTime::currentMSecsSinceEpoch() / (qreal)(m_creatInterval * 1000 * 2) + 0.5;
bool timeFlag = currentIndex > fileIndex;
qint64 maxSize = qMax(m_element->GetLogSize(), maxSize);;
bool sizeFlag = maxSize > m_maxSize;
return timeFlag || sizeFlag;
}
void QLogWorker::ClearLogs()
{
ClearLogsByPath(m_logPath);
}
void QLogWorker::ClearLogsByPath(const QString &path)
{
QDir dir(path);
dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks);
dir.setSorting(QDir::Size | QDir::Reversed);
QFileInfoList list = dir.entryInfoList();
for (int i = 0; i < list.size(); ++i)
{
QFileInfo fileInfo = list.at(i);
QString fileName = fileInfo.fileName();
QDateTime logTime = QDateTime::fromString(fileName.left(fileName.indexOf(".txt")), FORMAT_TIME_FILENAME);
if (!logTime.isValid())
{
dir.remove(fileName);
}
else
{
if (QDateTime::currentMSecsSinceEpoch() - logTime.toMSecsSinceEpoch() > m_saveTime * 1000)
{
dir.remove(fileName);
}
}
}
}
QString QLogWorker::MakeFileName()
{
return m_logPath + QDir::separator() + m_logTime.toString(FORMAT_TIME_FILENAME) + "_log.txt";
}
void QLogWorker::AddWriteElement(HiLog::eLogFlag flag)
{
m_eFlags = m_eFlags | flag;
}
void QLogWorker::RemoveWriteElement(HiLog::eLogFlag flag)
{
m_eFlags = m_eFlags & ~flag;
}
QLog
#ifndef QLOG_H
#define QLog_H
#include
#include
class QLogWorker;
class QTLOG_EXPORT QLog : public QObject
{
Q_OBJECT
public:
enum eLogFlag // 日志类型
{
eLog_Debug = 0x001,
eLog_Info = 0x002,
eLog_Warning = 0x004,
eLog_Critical = 0x008,
eLog_Fatal = 0x010,
};
Q_DECLARE_FLAGS(eLogFlags, eLogFlag)
public:
/** 获取日志单例指针 */
static QLog *Instance();
/** 销毁单例指针 */
static void Destroy();
/** 用作监听qDebug输出 */
static void LogMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg);
/** 设置默认handler */
static void SetDefaultHandle(QtMessageHandler handler);
public:
/** 设置log的标记类型 */
void SetLogFlags(eLogFlags flags);
/** 设置日志路径 */
void SetLogPath(const QString &path);
/** 获取所有日志列表 */
QList GetLogList();
/** 获取日志路径 */
QString GetLogDir();
protected:
explicit QLog(QObject *parent = NULL);
private:
/** 获取工作类指针 */
QLogWorker *Worker() const;
private:
/** 单例指针创建锁 */
static QMutex m_mutex;
/** 单例使用锁 */
static QMutex m_useMutex;
/** 静态单例指针 */
static QLog *m_pInstance;
// 默认的handle
static QtMessageHandler gDefaultHandler;
private:
/** 日志工作类 */
QLogWorker *m_pWorker;
};
#endif //
#include "QLog.h"
#include "QLogWorker.h"
#include
QMutex QLog::m_mutex;
QMutex QLog::m_useMutex;
QLog *QLog::m_pInstance = NULL;
QtMessageHandler QLog::gDefaultHandler = nullptr;
QLog::QLog(QObject *parent) :
QObject(parent),
m_pWorker(new QLogWorker(this))
{
}
QLogWorker *QLog::Worker() const
{
return m_pWorker;
}
QLog *QLog::Instance()
{
if (!m_pInstance)
{
m_mutex.lock();
if (!m_pInstance)
{
m_pInstance = new QLog();
}
m_mutex.unlock();
}
return m_pInstance;
}
void QLog::Destroy()
{
if (m_pInstance)
{
m_useMutex.lock();
m_mutex.lock();
delete m_pInstance;
m_pInstance = NULL;
m_mutex.unlock();
m_useMutex.unlock();
}
}
// 注意日志的格式建议对齐,不然看起来头疼
void QLog::LogMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
if (gDefaultHandler)
{
gDefaultHandler(type, context, msg);
}
QByteArray localMsg = msg.toLocal8Bit();
QString log;
eLogFlag flag;
switch (type)
{
case QtDebugMsg:
log.append(tr("[%1][调试] %2\r\n").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")).arg(msg));
flag = eLog_Debug;
break;
case QtInfoMsg:
log.append(tr("[%1][信息] %2\r\n").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")).arg(msg));
flag = eLog_Info;
break;
case QtWarningMsg:
log.append(tr("[%1][警告] %2\r\n").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")).arg(msg));
flag = eLog_Warning;
break;
case QtCriticalMsg:
log.append(tr("[%1][紧急] %2\r\n").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")).arg(msg));
flag = eLog_Critical;
break;
case QtFatalMsg:
log.append(tr("[%1][错误] %2\r\n").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")).arg(msg));
flag = eLog_Fatal;
break;
default:
break;
}
if (!log.isEmpty())
{
m_useMutex.lock();
QLog::Instance()->Worker()->WriteLog(flag, log);
m_useMutex.unlock();
}
}
void QLog::SetDefaultHandle(QtMessageHandler handler)
{
gDefaultHandler = handler;
}
void QLog::SetLogFlags(eLogFlags flags)
{
m_pWorker->SetLogFlags(flags);
}
void QLog::SetLogPath(const QString &path)
{
m_pWorker->SetLogPath(path);
}
QList QLog::GetLogList()
{
return m_pWorker->GetLogList();
}
QString QLog::GetLogDir()
{
return m_pWorker->GetLogDir();
}
五、拓展
打印调用函数
日志分类
- 可以在QLogWorker针对信息类型创建不同的QWriteElement
六、拓展
#ifndef HILOG_H
#define HILOG_H
#include
#include
class LogWorker;
class HiLog : public QObject
{
Q_OBJECT
public:
enum eLogFlag // 日志类型
{
eLog_Debug = 0x001,
eLog_Info = 0x002,
eLog_Warning = 0x004,
eLog_Critical = 0x008,
eLog_Fatal = 0x010,
};
Q_DECLARE_FLAGS(eLogFlags, eLogFlag)
public:
// 获取日志单例指针
static HiLog *Instance();
// 销毁单例指针
static void Destroy();
// 用作监听qDebug输出
static void LogMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg);
// 设置默认handler
static void SetDefaultHandle(QtMessageHandler handler);
public:
// 设置log的标记类型
void SetLogFlags(eLogFlags flags);
// 设置日志路径
void SetLogPath(const QString &path);
// 获取所有日志列表
QList GetLogList(HiLog::eLogFlag flag);
QJsonDocument getLogMessageList(HiLog::eLogFlag flag);
// 获取日志路径
QString GetLogDir(HiLog::eLogFlag flag);
bool deleteLogFile(QString fileName);
void clearAllLogs();
protected:
explicit HiLog(QObject *parent = NULL);
private:
// 获取工作类指针
LogWorker *Worker() const;
private:
// 单例指针创建锁
static QMutex m_mutex;
// 同步锁
static QMutex m_useMutex;
// 静态单例指针
static HiLog *m_pInstance;
// 默认的handle
static QtMessageHandler gDefaultHandler;
private:
/** 日志工作类 */
LogWorker *m_pWorker;
};
#endif // HILOG_H