C++ 中比较不错的日志工具有 log4cxx
,log4qt
等,但是它们都不能和 qDebug()
, qInfo()
等有机的结合在一起,所以在 Qt 中使用总觉得不够舒服,感谢 Qt 提供了 qInstallMessageHandler()
这个函数,使用这个函数可以安装自定义的日志输出处理函数,把日志输出到文件,控制台等,具体的使用可以查看 Qt 的帮助文档。
本文主要是介绍使用 qInstallMessageHandler()
实现一个简单的日志工具,例如调用 qDebug() << "Hi"
,输出的内容会同时输出到日志文件和控制台,并且日志文件如果不是当天创建的,会使用它的创建日期备份起来,涉及到的文件有:
main.cpp: 使用示例
Singleton.h: 单例模版
LogHandler.h: 自定义日志相关类的头文件
LogHandler.cpp: 自定义日志相关类的实现文件
定义 QT_MESSAGELOGCONTEXT qDebug
其实是一个宏: #define qDebug QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).debug
,在 Debug 版本的时候会输出行号,文件名,函数名等,但是在 Release 版本的时候不会输出,为了输出它们,需要在 .pro 文件里加入下面的定义:
1 DEFINES += QT_MESSAGELOGCONTEXT
main.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include "LogHandler.h" #include <QApplication> #include <QDebug> #include <QTime> #include <QPushButton> int main (int argc, char *argv[]) { QApplication app (argc, argv) ; Singleton<LogHandler>::getInstance ().installMessageHandler (); qDebug () << "Hello" ; qDebug () << "当前时间是: " << QTime::currentTime ().toString ("hh:mm:ss" ); qInfo () << QString ("God bless you!" ); QPushButton *button = new QPushButton ("退出" ); button->show (); QObject::connect (button, &QPushButton::clicked, [&app] { qDebug () << "退出" ; app.quit (); }); Singleton<LogHandler>::getInstance ().uninstallMessageHandler (); qDebug () << "........" ; Singleton<LogHandler>::getInstance ().installMessageHandler (); int ret = app.exec (); Singleton<LogHandler>::getInstance ().uninstallMessageHandler (); return ret; }
控制台输出:
1 2 3 4 5 Hello 当前时间是: "16:29:42" "God bless you!" ........ 退出
日志文件: 位置: exe 所在目录的 log 目录下的 log.txt 格式: 时间 - [Level] (文件名:行数, 函数): 消息
1 2 3 4 16:29:42 - [Debug] (main.cpp:15, int main(int, char **)): Hello 16:29:42 - [Debug] (main.cpp:16, int main(int, char **)): 当前时间是: "16:29:42" 16:29:42 - [Info ] (main.cpp:17, int main(int, char **)): "God bless you!" 16:29:46 - [Debug] (main.cpp:22, auto main(int, char **)::(anonymous class)::operator()() const): 退出
LogHandler.h 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #ifndef LOGHANDLER_H #define LOGHANDLER_H #include "Singleton.h" #define LogHandlerInstance Singleton<LogHandler> ::getInstance() struct LogHandlerPrivate ;class LogHandler { SINGLETON (LogHandler) public : void uninstallMessageHandler () ; void installMessageHandler () ; private : LogHandlerPrivate *d; }; #endif
LogHandler.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 #include "LogHandler.h" #include <stdio.h> #include <stdlib.h> #include <QDebug> #include <QDateTime> #include <QMutexLocker> #include <QtGlobal> #include <QDir> #include <QFile> #include <QFileInfo> #include <QTimer> #include <QTextStream> #include <iostream> #include <QTextCodec> struct LogHandlerPrivate { LogHandlerPrivate (); ~LogHandlerPrivate (); void openAndBackupLogFile () ; static void messageHandler (QtMsgType type, const QMessageLogContext &context, const QString &msg) ; void makeSureLogDirectory () const ; QDir logDir; QTimer renameLogFileTimer; QTimer flushLogFileTimer; QDate logFileCreatedDate; static QFile *logFile; static QTextStream *logOut; static QMutex logMutex; }; QMutex LogHandlerPrivate::logMutex; QFile* LogHandlerPrivate::logFile = nullptr ; QTextStream* LogHandlerPrivate::logOut = nullptr ; LogHandlerPrivate::LogHandlerPrivate () { logDir.setPath ("log" ); QString logPath = logDir.absoluteFilePath ("log.txt" ); logFileCreatedDate = QFileInfo (logPath).lastModified ().date (); openAndBackupLogFile (); renameLogFileTimer.setInterval (1000 * 60 * 10 ); renameLogFileTimer.start (); QObject::connect (&renameLogFileTimer, &QTimer::timeout, [this ] { QMutexLocker locker (&LogHandlerPrivate::logMutex); openAndBackupLogFile (); }); flushLogFileTimer.setInterval (1000 ); flushLogFileTimer.start (); QObject::connect (&flushLogFileTimer, &QTimer::timeout, [] { QMutexLocker locker (&LogHandlerPrivate::logMutex); if (nullptr != logOut) { logOut->flush (); } }); } LogHandlerPrivate::~LogHandlerPrivate () { if (nullptr != logFile) { logFile->flush (); logFile->close (); delete logOut; delete logFile; logOut = nullptr ; logFile = nullptr ; } } void LogHandlerPrivate::openAndBackupLogFile () { makeSureLogDirectory (); QString logPath = logDir.absoluteFilePath ("log.txt" ); if (nullptr == logFile) { logFile = new QFile (logPath); logOut = (logFile->open (QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) ? new QTextStream (logFile) : nullptr ; if (nullptr != logOut) { logOut->setCodec ("UTF-8" ); } if (logFileCreatedDate.isNull ()) { logFileCreatedDate = QDate::currentDate (); } } if (logFileCreatedDate != QDate::currentDate ()) { logFile->flush (); logFile->close (); delete logOut; delete logFile; QString newLogPath = logDir.absoluteFilePath (logFileCreatedDate.toString ("yyyy-MM-dd.log" ));; QFile::copy (logPath, newLogPath); QFile::remove (logPath); logFile = new QFile (logPath); logOut = (logFile->open (QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) ? new QTextStream (logFile) : nullptr ; logFileCreatedDate = QDate::currentDate (); if (nullptr != logOut) { logOut->setCodec ("UTF-8" ); } } } void LogHandlerPrivate::makeSureLogDirectory () const { if (!logDir.exists ()) { logDir.mkpath ("." ); } } void LogHandlerPrivate::messageHandler (QtMsgType type, const QMessageLogContext &context, const QString &msg) { QMutexLocker locker (&LogHandlerPrivate::logMutex) ; QString level; switch (type) { case QtDebugMsg: level = "DEBUG" ; break ; case QtInfoMsg: level = "INFO " ; break ; case QtWarningMsg: level = "WARN " ; break ; case QtCriticalMsg: level = "ERROR" ; break ; case QtFatalMsg: level = "FATAL" ; break ; default : break ; } #if defined(Q_OS_WIN) QByteArray localMsg = QTextCodec::codecForName ("GB2312" )->fromUnicode (msg); #else QByteArray localMsg = msg.toLocal8Bit (); #endif std::cout << std::string (localMsg) << std::endl; if (nullptr == LogHandlerPrivate::logOut) { return ; } QString fileName = context.file; int index = fileName.lastIndexOf (QDir::separator ()); fileName = fileName.mid (index + 1 ); (*LogHandlerPrivate::logOut) << QString ("%1 - [%2] (%3:%4, %5): %6\n" ) .arg (QDateTime::currentDateTime ().toString ("yyyy-MM-dd hh:mm:ss" )).arg (level) .arg (fileName).arg (context.line).arg (context.function).arg (msg); } LogHandler::LogHandler () : d (nullptr ) { } LogHandler::~LogHandler () { } void LogHandler::installMessageHandler () { QMutexLocker locker (&LogHandlerPrivate::logMutex) ; if (nullptr == d) { d = new LogHandlerPrivate (); qInstallMessageHandler (LogHandlerPrivate::messageHandler); } } void LogHandler::uninstallMessageHandler () { QMutexLocker locker (&LogHandlerPrivate::logMutex) ; qInstallMessageHandler (nullptr ); delete d; d = nullptr ; }
Singleton.h 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 #ifndef SINGLETON_H #define SINGLETON_H #include <QMutex> #include <QScopedPointer> template <typename T>class Singleton {public : static T& getInstance () ; Singleton (const Singleton &other); Singleton<T>& operator =(const Singleton &other); private : static QMutex mutex; static QScopedPointer<T> instance; }; template <typename T> QMutex Singleton<T>::mutex;template <typename T> QScopedPointer<T> Singleton<T>::instance;template <typename T>T& Singleton<T>::getInstance () { if (instance.isNull ()) { mutex.lock (); if (instance.isNull ()) { instance.reset (new T ()); } mutex.unlock (); } return *instance.data (); } #define SINGLETON(Class) \ private: \ Class(); \ ~Class(); \ Class(const Class &other); \ Class& operator=(const Class &other); \ friend class Singleton<Class> ; \ friend struct QScopedPointerDeleter<Class> ; #endif
思考
main() 函数里的 qDebug() 输出都是在 UI 线程,LogHandler 是否多线程安全?怎么测试?
日志的相关配置数据例如输出目录等都是写死在程序里的,如果写到配置文件里是不是更灵活?
日志的格式也是写死在程序里的,如果能做到通过配置修改日志格式那就更强大了,就像 log4cxx
一样
测试如何快速的看到不同日期生成的日志文件不同?
删除超过 30 天的日志
单个日志文件例如大于 100M 后重新创建一个新的日志文件