RabbitCommon v2.2.6
Loading...
Searching...
No Matches
Log.cpp
1// Copyright Copyright (c) Kang Lin studio, All Rights Reserved
2// Author Kang Lin <kl222@126.com>
3
4#include "Log.h"
5#include "RabbitCommonDir.h"
6#include <string>
7#include <QDebug>
8#include <QSettings>
9#include <QDir>
10#include <QFileInfo>
11#include <QUrl>
12#ifdef HAVE_RABBITCOMMON_GUI
13 #include <QDesktopServices>
14#endif
15#include <QRegularExpression>
16#include <QDateTime>
17#include <QtGlobal>
18#include <QMutex>
19#include <QCoreApplication>
20#ifdef HAVE_RABBITCOMMON_GUI
21#include "DockDebugLog.h"
22extern CDockDebugLog* g_pDcokDebugLog;
23#endif
24
25#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
26 QtMessageHandler g_originalMessageHandler = Q_NULLPTR;
27#else
28 QtMsgHandler g_originalMessageHandler = Q_NULLPTR;
29#endif
30
31
32namespace RabbitCommon {
33
34static Q_LOGGING_CATEGORY(Logger, "RabbitCommon.log")
35QRegularExpression g_reInclude;
36QRegularExpression g_reExclude;
37
38CLog::CLog() : QObject(),
39 m_szFileFormat("yyyy-MM-dd"),
40 m_nLength(0),
41 m_nCount(0)
42{
43 QSettings set(RabbitCommon::CDir::Instance()->GetFileUserConfigure(),
44 QSettings::IniFormat);
45
46 SetFilter(set.value("Log/Filter/include").toString(),
47 set.value("Log/Filter/Exclude").toString());
48
49 QString szPattern = "[%{time hh:mm:ss.zzz} %{pid}|%{threadid} %{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}E%{endif}%{if-fatal}F%{endif}] %{category} - %{message}";
50#ifdef QT_MESSAGELOGCONTEXT
51 // Use qt message log context(__FILE__, __LIEN__, __FUNCTION__) in release
52 szPattern += " [%{file}:%{line}, %{function}]";
53#elif defined(DEBUG) || defined(_DEBUG)
54 szPattern += " [%{file}:%{line}, %{function}]";
55#endif
56 QString szFilterRules;
57 quint64 nInterval = 60; // Unit: second
58#if !(defined(DEBUG) || defined(_DEBUG) || ANDROID)
59 szFilterRules = "*.debug = false";
60#endif
61
62 m_szPath = CDir::Instance()->GetDirLog();
63 QString szConfFile = RabbitCommon::CDir::Instance()->GetDirConfig()
64 + QDir::separator() + qApp->applicationName() + "_logqt.ini";
65 szConfFile = set.value("Log/ConfigFile", szConfFile).toString();
66 if(!QFile::exists(szConfFile))
67 szConfFile = RabbitCommon::CDir::Instance()->GetDirConfig()
68 + QDir::separator() + "logqt.in";
69 if (QFile::exists(szConfFile))
70 {
71 m_szConfigureFile = szConfFile;
72 qInfo(Logger) << "Load log configure file:" << m_szConfigureFile;
73 QSettings setConfig(m_szConfigureFile, QSettings::IniFormat);
74 setConfig.beginGroup("Log");
75 m_szPath = setConfig.value("Path", m_szPath).toString();
76 m_szFileFormat = setConfig.value("Name", m_szFileFormat).toString();
77 szPattern = setConfig.value("Pattern", szPattern).toString();
78 nInterval = setConfig.value("Interval", nInterval).toUInt();
79 m_nCount = setConfig.value("Count", 0).toULongLong();
80 QString szLength = setConfig.value("Length", 0).toString();
81 if(!szLength.isEmpty()) {
82 QRegularExpression e("(\\d+)([mkg]?)",
83 QRegularExpression::CaseInsensitiveOption);
84 QRegularExpressionMatch m;
85 if(szLength.contains(e, &m) && m.hasMatch())
86 {
87 quint64 len = 0;
88 if(m.capturedLength(1) > 0)
89 len = m.captured(1).toULong();
90 if(m.capturedLength(2) > 0)
91 {
92 QString u = m.captured(2).toUpper();
93 if("K" == u)
94 len <<= 10;
95 else if("M" == u)
96 len <<= 20;
97 else if("G" == u)
98 len <<= 30;
99 }
100 m_nLength = len;
101 }
102 }
103 setConfig.endGroup();
104
105 setConfig.beginGroup("Rules");
106 auto keys = setConfig.childKeys();
107 //qDebug(Logger) << "keys" << keys;
108 szFilterRules.clear();
109 foreach(auto k, keys) {
110 QString v = setConfig.value(k).toString();
111 szFilterRules += k + "=" + v + "\n";
112 }
113 setConfig.endGroup();
114 } else {
115 qWarning(Logger) << "Log configure file is not exist:" << szConfFile
116 << ". Use default settings.";
117 }
118
119 qDebug(Logger) << "Log configure:"
120 << "\n Path:" << m_szPath
121 << "\n FileNameFormat:" << m_szFileFormat
122 << "\n szPattern:" << szPattern
123 << "\n Interval:" << nInterval
124 << "\n Count:" << m_nCount
125 << "\n Length:" << m_nLength
126 << "\n Rules:" << szFilterRules;
127
128 if(!szFilterRules.isEmpty())
129 QLoggingCategory::setFilterRules(szFilterRules);
130
131 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
132 qSetMessagePattern(szPattern);
133 g_originalMessageHandler = qInstallMessageHandler(myMessageOutput);
134 #else
135 g_originalMessageHandler = qInstallMsgHandler(myMessageOutput);
136 #endif
137
138 QDir d;
139 if(!d.exists(m_szPath))
140 {
141 if(!d.mkpath(m_szPath))
142 fprintf(stderr, "Create log directory fail. %s\n",
143 m_szPath.toStdString().c_str());
144 }
145
146 slotTimeout();
147
148 bool check = connect(&m_Timer, SIGNAL(timeout()),
149 this, SLOT(slotTimeout()));
150 Q_ASSERT(check);
151 m_Timer.start(nInterval * 1000);
152}
153
154CLog::~CLog()
155{
156 if(m_File.isOpen())
157 m_File.close();
158 if(m_Timer.isActive())
159 m_Timer.stop();
160}
161
162CLog* CLog::Instance()
163{
164 static CLog* p = NULL;
165 if(!p)
166 {
167 p = new CLog();
168 }
169 return p;
170}
171
172QString CLog::GetLogFile()
173{
174 return m_File.fileName();
175}
176
177QString CLog::OpenLogConfigureFile()
178{
179 return m_szConfigureFile;
180}
181
182QString CLog::GetLogDir()
183{
184 QString f = GetLogFile();
185 if(f.isEmpty()) return f;
186
187 QFileInfo fi(f);
188 return fi.absolutePath();
189}
190
191int CLog::SetFilter(const QString &szInclude, const QString &szExclude)
192{
193 g_reInclude = QRegularExpression(szInclude);
194 g_reExclude = QRegularExpression(szExclude);
195
196 QSettings set(RabbitCommon::CDir::Instance()->GetFileUserConfigure(),
197 QSettings::IniFormat);
198 set.setValue("Log/Filter/include", szInclude);
199 set.setValue("Log/Filter/Exclude", szExclude);
200 return 0;
201}
202
203int CLog::GetFilter(QString &szInclude, QString &szExclude)
204{
205 szInclude = g_reInclude.pattern();
206 szExclude = g_reExclude.pattern();
207 return 0;
208}
209
210#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
211void CLog::myMessageOutput(QtMsgType type,
212 const QMessageLogContext &context,
213 const QString &msg)
214{
215 QString szMsg;
216 #if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
217 szMsg = qFormatLogMessage(type, context, msg);
218 #else
219 szMsg = msg;
220 #endif
221
222 if(g_reInclude.isValid() && !g_reInclude.pattern().isEmpty()) {
223 QRegularExpressionMatch m = g_reInclude.match(szMsg);
224 if(!m.hasMatch()) {
225 return;
226 }
227 }
228 if(g_reExclude.isValid() && !g_reExclude.pattern().isEmpty()) {
229 QRegularExpressionMatch m = g_reExclude.match(szMsg);
230 if(m.hasMatch()) {
231 return;
232 }
233 }
234
235#ifdef HAVE_RABBITCOMMON_GUI
236 if(g_pDcokDebugLog)
237 emit g_pDcokDebugLog->sigAddLog(szMsg);
238#endif
239
240 /*
241 QFile f(CLog::Instance()->GetLogFile());
242 if(!f.open(QFile::WriteOnly | QFile::Append))
243 {
244 fprintf(stderr, "Open log file fail. %s\n",
245 f.fileName().toStdString().c_str());
246 return;
247 }//*/
248
249 if(CLog::Instance()->m_File.isOpen())
250 {
251 QTextStream s(&CLog::Instance()->m_File);
252 CLog::Instance()->m_Mutex.lock();
253 s << szMsg << "\r\n";
254 //s.flush();
255 CLog::Instance()->m_Mutex.unlock();
256 }
257
258 //f.close();
259
260 if(g_originalMessageHandler)
261 g_originalMessageHandler(type, context, msg);
262}
263#else
264void CLog::myMessageOutput(QtMsgType type, const char* msg)
265{
266 QString szMsg(msg);
267
268 if(g_reInclude.isValid() && !g_reInclude.pattern().isEmpty()) {
269 QRegularExpressionMatch m = g_reInclude.match(szMsg);
270 if(!m.hasMatch()) {
271 return;
272 }
273 }
274 if(g_reExclude.isValid() && !g_reExclude.pattern().isEmpty()) {
275 QRegularExpressionMatch m = g_reExclude.match(szMsg);
276 if(m.hasMatch()) {
277 return;
278 }
279 }
280
281 /*
282 QFile f(CLog::Instance()->GetLogFile());
283 if(!f.open(QFile::WriteOnly | QFile::Append))
284 {
285 fprintf(stderr, "Open log file fail. %s\n",
286 f.fileName().toStdString().c_str());
287 return;
288 } //*/
289
290 if(CLog::Instance()->m_File.isOpen())
291 {
292 QTextStream s(&CLog::Instance()->m_File);
293 CLog::Instance()->m_Mutex.lock();
294 s << szMsg << "\r\n";
295 CLog::Instance()->m_Mutex.unlock();
296 }
297
298 //f.close();
299
300 if(g_originalMessageHandler)
301 g_originalMessageHandler(type, msg);
302}
303#endif
304
305void CLog::checkFileCount()
306{
307 if(m_nCount < 2) return;
308
309 QDir d(m_szPath);
310 auto lstFiles = d.entryInfoList(QDir::Files, QDir::Time);
311 if(lstFiles.size() < m_nCount) return;
312
313 if(lstFiles.first().lastModified() < lstFiles.back().lastModified())
314 {
315 if(d.remove(lstFiles.first().absoluteFilePath()))
316 qInfo(Logger) << "Remove file:" << lstFiles.first().absoluteFilePath();
317 else
318 qCritical(Logger) << "Remove file fail:"
319 << lstFiles.first().absoluteFilePath();
320 } else {
321 if(d.remove(lstFiles.back().absoluteFilePath()))
322 qInfo(Logger) << "Remove file:" << lstFiles.back().absoluteFilePath();
323 else
324 qCritical(Logger) << "Remove file fail:"
325 << lstFiles.back().absoluteFilePath();
326 }
327}
328
329QString CLog::getFileName()
330{
331 QChar fill('0');
332 QString szNo = QString("%1").arg(1, 4, 10, fill);
333 QString szSep("_");
334 QString szName;
335 QString szFile;
336 QDir d(m_szPath);
337
338 szFile = m_File.fileName();
339 if(!szFile.isEmpty()) return szFile;
340
341 szName = QDate::currentDate().toString(m_szFileFormat);
342 d.setNameFilters(QStringList() << szName + "*");
343 auto lstFiles = d.entryInfoList(QDir::Files, QDir::Name);
344 //qDebug(Logger) << "Log files:" << lstFiles;
345 if(lstFiles.isEmpty())
346 szFile = m_szPath + QDir::separator() + szName + szSep + szNo + ".log";
347 else
348 szFile = lstFiles.back().absoluteFilePath();
349
350 return szFile;
351}
352
353QString CLog::getNextFileName(const QString szFile)
354{
355 if(szFile.isEmpty())
356 return QString();
357
358 QFileInfo fi(szFile);
359 QChar fill('0');
360 QString szNo = QString("%1").arg(1, 4, 10, fill);
361 QString szSep("_");
362 QString szName = fi.baseName();
363
364 auto s = szName.split(szSep);
365 if(s.size() > 0
366 && QDate::currentDate().toString(m_szFileFormat) == s[0])
367 {
368 if(s.size() == 2) {
369 szNo = s[1];
370 szNo = QString("%1").arg(szNo.toInt() + 1, 4, 10, fill);
371 }
372
373 szName = s[0];
374 } else {
375 szName = QDate::currentDate().toString(m_szFileFormat);
376 }
377
378 szName += szSep + szNo;
379 return m_szPath + QDir::separator() + szName + ".log";
380}
381
382bool CLog::checkFileLength()
383{
384 if(m_nLength == 0) return false;
385
386 if(m_File.fileName().isEmpty()) return false;
387
388 QFileInfo fi(m_File.fileName());
389 if(fi.exists()) {
390 if(fi.size() < m_nLength) return false;
391 } else {
392 return false;
393 }
394
395 return true;
396}
397
398void CLog::slotTimeout()
399{
400 QString szFile;
401 if(m_File.fileName().isEmpty())
402 {
403 szFile = getFileName();
404 m_File.setFileName(szFile);
405 }
406
407 do {
408 if(checkFileLength())
409 szFile = getNextFileName(m_File.fileName());
410 else
411 if(m_File.isOpen()) break;
412#ifdef Q_OS_ANDROID
413 if(szFile.isEmpty()) return;
414#endif
415 Q_ASSERT(!szFile.isEmpty());
416
417 m_Mutex.lock();
418 if(m_File.isOpen())
419 m_File.close();
420
421 m_File.setFileName(szFile);
422 if(!m_File.open(QFile::WriteOnly | QFile::Append))
423 {
424 //NOTE: Do not use qDebug or qCritical. Causes a deadlock.
425 fprintf(stderr, "Open log file fail. %s\n",
426 m_File.fileName().toStdString().c_str());
427 }
428 m_Mutex.unlock();
429 } while(0);
430
431 checkFileCount();
432}
433
434#ifdef HAVE_RABBITCOMMON_GUI
435
436void OpenLogConfigureFile()
437{
438 QString f = RabbitCommon::CLog::Instance()->OpenLogConfigureFile();
439 if(f.isEmpty())
440 {
441 qCritical(Logger) << "Configure file is empty";
442 return;
443 }
444 QDesktopServices::openUrl(QUrl::fromLocalFile(f));
445}
446
447void OpenLogFile()
448{
449 QString d = RabbitCommon::CLog::Instance()->GetLogFile();
450 if(d.isEmpty())
451 {
452 qCritical(Logger) << "Log file is empty";
453 return;
454 }
455 QDesktopServices::openUrl(d);
456}
457
458void OpenLogFolder()
459{
460 QString f = RabbitCommon::CLog::Instance()->GetLogDir();
461 if(f.isEmpty())
462 {
463 qCritical(Logger) << "Log folder is empty";
464 return;
465 }
466 QDesktopServices::openUrl(QUrl::fromLocalFile(f));
467}
468
469#endif
470
471} // namespace RabbitCommon
The CDockDebugLog class.