Qt 日志文件系统

Qt 日志文件系统

  • 一、注册日志消息处理程序
  • 二、日志类型
    • QtDebugMsg(调试信息)
    • QtWarningMsg(警告信息)
    • QtCriticalMsg、QtSystemMsg(关键消息、系统信息)
    • QtFatalMsg(错误信息)
  • 三、自定义的日志架构
    • 日志格式
    • 信息写入逻辑
  • 四、源代码
    • QWriteElement
    • QLogWorker
    • QLog
  • 五、拓展
    • 打印调用函数
    • 日志分类
  • HiLog.h

一、注册日志消息处理程序

  • 调用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下,消息将发送到调试器
  • 常常包含一些致命的错误信息

三、自定义的日志架构

日志格式

  • 日志打印格式: [2022-06-28 10:25:43][info] 信息
  • 日志文件名称:yyyy_MM_dd.log
  • 日志打印种类: 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<QString> 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<QString> QLogWorker::GetLogList()
{
	QList<QString> 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()
{
	if (!QFile(MakeFileName()).exists())
	{
		// 判断条件,重新创建日志文件
		SetLogPath(m_logPath);
		// 删除之前的日志
		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<QString> 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<QString> QLog::GetLogList()
{
	return m_pWorker->GetLogList();
}
 
QString QLog::GetLogDir()
{
	return m_pWorker->GetLogDir();
}

五、拓展

打印调用函数

log.append(tr("[%1][警告] %2 %3\r\n").arg(QDateTime::currentDateTime().toString("yyyy-mm-dd hh:mm:ss")).arg(msg)).arg(context.function));

日志分类

  • 可以在QLogWorker针对信息类型创建不同的QWriteElement

HiLog.h

#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<QString> 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

你可能感兴趣的:(QT,qt,c++,开发语言)