Qt5通过qInstallMessageHandler将日志重定向到文件

发布时间 2023-06-08 16:00:31作者: 非法关键字

先看看日志重定向到文件的内容

[2023-06-08 15:36.907 main.cpp:17 INFO] --------------------------
[2023-06-08 15:36.908 main.cpp:18 INFO] Application Initilizing...
[2023-06-08 15:36.909 main.cpp:19 INFO] --------------------------

[2023-06-08 15:36.892 ShowHikGL.cpp:611 INFO] --------------------------
[2023-06-08 15:36.892 ShowHikGL.cpp:612 INFO] Begin cv::ocl::PlatformInfo
[2023-06-08 15:36.892 ShowHikGL.cpp:622 INFO] Name: NVIDIA CUDA
[2023-06-08 15:36.893 ShowHikGL.cpp:623 INFO] Vendor: NVIDIA Corporation
[2023-06-08 15:36.893 ShowHikGL.cpp:624 INFO] Version: OpenCL 3.0 CUDA 12.1.107
[2023-06-08 15:36.893 ShowHikGL.cpp:625 INFO] Number of devices: 1
[2023-06-08 15:36.893 ShowHikGL.cpp:632 INFO] Device  1
[2023-06-08 15:36.893 ShowHikGL.cpp:633 INFO] Vendor Id: 3
[2023-06-08 15:36.893 ShowHikGL.cpp:634 INFO] Vendor name: NVIDIA Corporation
[2023-06-08 15:36.893 ShowHikGL.cpp:635 INFO] Name: NVIDIA GeForce RTX 3060 Laptop GPU
[2023-06-08 15:36.893 ShowHikGL.cpp:636 INFO] Driver version: 3
[2023-06-08 15:36.893 ShowHikGL.cpp:645 INFO] Is NVidia device
[2023-06-08 15:36.893 ShowHikGL.cpp:647 INFO] Global Memory size: 6441926656
[2023-06-08 15:36.893 ShowHikGL.cpp:648 INFO] Memory cache size: 860160
[2023-06-08 15:36.893 ShowHikGL.cpp:649 INFO] Memory cache type: 2
[2023-06-08 15:36.893 ShowHikGL.cpp:650 INFO] Local Memory size: 49152
[2023-06-08 15:36.893 ShowHikGL.cpp:651 INFO] Local Memory type: 1
[2023-06-08 15:36.893 ShowHikGL.cpp:652 INFO] Max Clock frequency: 1425
[2023-06-08 15:36.893 ShowHikGL.cpp:655 INFO] End cv::ocl::PlatformInfo
[2023-06-08 15:36.893 ShowHikGL.cpp:656 INFO] --------------------------

日志类的代码如下

#ifndef TY_LOGGER_HEADER
#define TY_LOGGER_HEADER
#include <QApplication>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <QTextStream>
#include <QDate>
#include <QDateTime>
#include <QMutex>
#include <QQueue>
#include <QtConcurrent/QtConcurrent>

class TyLogger
{
private:
	TyLogger()
	{
		writeRunning = 0;
		createLogFile();
	}
	TyLogger(const TyLogger&) = delete;
	TyLogger& operator=(const TyLogger&) = delete;

public:
	~TyLogger() {
		file.close();
	}

	void write()
	{
		if (!writeRunning.testAndSetAcquire(0, 1))
		{
			//qInfo() << "write log has running queue";
			return;
		}
		while (true)
		{
			QMutexLocker locker(&mutex);
			if (queue.isEmpty()) break;
			const auto msg = queue.dequeue();
			// 当日期跨度后相应的更新日志文件名
			if (!isCurrentLogFile()) {
				file.close();
				createLogFile();
			}

			if (file.isOpen()) {
				QTextStream stream(&file);
				// 设置编码为UTF-8
				stream.setCodec("UTF-8");
				stream << msg << endl;
			}
		}
		// 重置标志允许write在QtConcurrent::run中运行
		writeRunning.store(0);
	}

	static void customMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg)
	{
		QString level;
		switch (type)
		{
		case QtDebugMsg:
			level = "DEBUG";
			break;
		case QtWarningMsg:
			level = "WARN";
			break;
		case QtCriticalMsg:
			level = "ERROR";
			break;
		case QtFatalMsg:
			level = "FATAL";
			break;
		case QtInfoMsg:
			level = "INFO";
			break;
		default:
			level = "UNKN ";
			break;
		}
#if _DEBUG
		auto fileName = QFileInfo(context.file).fileName();
		QString formattedMessage = QString("[%1 %3:%4 %2] %5")
			.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm.zzz"))
			.arg(level)
			.arg(fileName)
			.arg(context.line)
			.arg(msg);
#else
		QString formattedMessage = QString("[%1 %2] %5")
			.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm.zzz"))
			.arg(level)
			.arg(msg);
#endif
		// locker mutex scope
		{
			QMutexLocker locker(&instance().mutex);
			instance().queue.enqueue(formattedMessage);
		}
		// 使用QtConcurrent::run在后台线程中异步写入日志文件
		QtConcurrent::run(&instance(), &TyLogger::write);
	}

	static TyLogger& instance()
	{
		// C++11标准保证静态局部变量的初始化是线程安全的
		static TyLogger logger;
		return logger;
	}

private:
	void createLogFile()
	{
		QString logDirPath = "Logs";
		QDir logDir(logDirPath);
		if (!logDir.exists()) {
			logDir.mkpath(".");
		}

		QString logFilePath = logDirPath + "/" + QDateTime::currentDateTime().toString("yyyy-MM-dd") + ".log";
		file.setFileName(logFilePath);
		file.open(QIODevice::WriteOnly | QIODevice::Append);
		currentLogFileDate = QDate::currentDate();
	}

	bool isCurrentLogFile() const
	{
		return currentLogFileDate == QDate::currentDate();
	}

private:
	QMutex mutex;
	QQueue<QString> queue;
	QAtomicInt writeRunning;

	QFile file;
	QDate currentLogFileDate;
};
#endif // TY_LOGGER_HEADER

这里主要是传递customMessageHandler到qInstallMessageHandler来重定向日志到文件,在线程中写日志并保证线程安全,通过Queue与QAtomicInt来保证同一时间只有一个QtCurrent::run的任务来确保日志的输出是有序的,这里的单例单纯就是为了qInstallMessageHandler服务的,如果想使用Qt5款型的单例建议使用Q_GLOBAL_STATIC(Logger, logger)