RabbitCommon v2.3.3
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 <string>
5#include <QDebug>
6#include <QSettings>
7#include <QDir>
8#include <QFileInfo>
9#include <QUrl>
10#include <QRegularExpression>
11#include <QDateTime>
12#include <QtGlobal>
13#include <QMutex>
14#include <QCoreApplication>
15#include <QProcessEnvironment>
16#include <QStandardPaths>
17#include <QLoggingCategory>
18
19#ifdef HAVE_RABBITCOMMON_GUI
20 #include <QDesktopServices>
21 #include <QMessageBox>
22
23 #include "DockDebugLog.h"
24 #include "FileBrowser.h"
25 #include "DlgEdit.h"
26 extern CDockDebugLog* g_pDcokDebugLog;
27#endif
28
29#include "Log.h"
30#include "RabbitCommonDir.h"
31#include "RabbitCommonTools.h"
32#include "CoreDump/StackTrace.h"
33
34namespace RabbitCommon {
35
36static QFile g_File;
37static QMutex g_Mutex;
38#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
39 static QtMessageHandler g_originalMessageHandler = Q_NULLPTR;
40#else
41 static QtMsgHandler g_originalMessageHandler = Q_NULLPTR;
42#endif
43QRegularExpression g_reInclude;
44QRegularExpression g_reExclude;
45
46static bool g_bPrintStackTrace = false;
47QList<QtMsgType> g_lstPrintStackTraceLevel{QtMsgType::QtCriticalMsg};
48static CCallTrace* g_pStack = nullptr;
49
50static Q_LOGGING_CATEGORY(log, "RabbitCommon.log")
51
52CLog::CLog() : QObject(),
53 m_szName(QCoreApplication::applicationName()),
54 m_szDateFormat("yyyy-MM-dd"),
55 m_nLength(0),
56 m_nCount(0)
57{
58 if(!g_pStack) g_pStack = new CCallTrace();
59
60#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
61 g_originalMessageHandler = qInstallMessageHandler(myMessageOutput);
62#else
63 g_originalMessageHandler = qInstallMsgHandler(myMessageOutput);
64#endif
65
66 QSettings set(RabbitCommon::CDir::Instance()->GetFileUserConfigure(),
67 QSettings::IniFormat);
68
69 SetFilter(set.value("Log/Filter/include").toString(),
70 set.value("Log/Filter/Exclude").toString());
71
72 bool bReadOnly = false;
73#if defined(Q_OS_ANDROID)
74 bReadOnly = true;
75#endif
76
77 QString szConfFile = QStandardPaths::locate(
78 QStandardPaths::ConfigLocation,
79 QCoreApplication::applicationName() + "_logqt.ini");
80 szConfFile = set.value("Log/ConfigFile", szConfFile).toString();
81 if(!QFile::exists(szConfFile))
82 szConfFile = QStandardPaths::locate(QStandardPaths::ConfigLocation,
83 "logqt.ini");
84 if(!QFile::exists(szConfFile))
85 szConfFile = RabbitCommon::CDir::Instance()->GetDirConfig(bReadOnly)
86 + QDir::separator() + QCoreApplication::applicationName()
87 + "_logqt.ini";
88 if(!QFile::exists(szConfFile))
89 szConfFile = RabbitCommon::CDir::Instance()->GetDirConfig(bReadOnly)
90 + QDir::separator() + "logqt.ini";
91 LoadConfigure(szConfFile);
92
93 qInfo(log) << "Application name:" << QCoreApplication::applicationName();
94 qInfo(log) << "Application folder:" << CDir::Instance()->GetDirApplication();
95 qInfo(log) << "Application install root folder:"
96 << CDir::Instance()->GetDirApplicationInstallRoot();
97 qInfo(log) << "Document folder:" << CDir::Instance()->GetDirUserDocument();
98
99 bool check = false;
100 check = connect(&m_Watcher, SIGNAL(fileChanged(QString)),
101 this, SLOT(slotFileChanged(QString)));
102 Q_ASSERT(check);
103}
104
105CLog::~CLog()
106{
107 qDebug(log) << "CLog::~CLog()";
108 g_Mutex.lock();
109 qInstallMessageHandler(g_originalMessageHandler);
110 if(g_File.isOpen()) g_File.close();
111 g_Mutex.unlock();
112 if(m_Timer.isActive()) m_Timer.stop();
113 if(g_pStack) delete g_pStack;
114}
115
116CLog* CLog::Instance()
117{
118 static CLog* p = NULL;
119 if(!p)
120 {
121 p = new CLog();
122 }
123 return p;
124}
125
126int CLog::LoadConfigure(const QString &szFile)
127{
128 int nRet = 0;
129
130 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}";
131#ifdef QT_MESSAGELOGCONTEXT
132 // Use qt message log context(__FILE__, __LIEN__, Q_FUNC_INFO) in release
133 szPattern += " [%{file}:%{line}, %{function}]";
134#elif defined(DEBUG) || defined(_DEBUG)
135 szPattern += " [%{file}:%{line}, %{function}]";
136#endif
137 QString szFilterRules;
138 quint64 nInterval = 60; // Unit: second
139#if !(defined(DEBUG) || defined(_DEBUG) || ANDROID)
140 szFilterRules = "*.debug = false";
141#endif
142
143 if (QFile::exists(szFile)) {
144 m_szConfigureFile = szFile;
145 QSettings setConfig(m_szConfigureFile, QSettings::IniFormat);
146 setConfig.beginGroup("Log");
147 szPattern = setConfig.value("Pattern", szPattern).toString();
148
149 // Log file
150 m_szPath = setConfig.value("Path", CDir::Instance()->GetDirLog()).toString();
151 m_szName = setConfig.value("Name", m_szName).toString();
152 m_szDateFormat = setConfig.value("DateFormat", "yyyy-MM-dd").toString();
153 nInterval = setConfig.value("Interval", nInterval).toUInt();
154 m_nCount = setConfig.value("Count", 0).toULongLong();
155 QString szLength = setConfig.value("Length", 0).toString();
156 if(!szLength.isEmpty()) {
157 QRegularExpression e("(\\d+)([mkg]?)",
158 QRegularExpression::CaseInsensitiveOption);
159 QRegularExpressionMatch m;
160 if(szLength.contains(e, &m) && m.hasMatch())
161 {
162 quint64 len = 0;
163 if(m.capturedLength(1) > 0)
164 len = m.captured(1).toULong();
165 if(m.capturedLength(2) > 0)
166 {
167 QString u = m.captured(2).toUpper();
168 if("K" == u)
169 len <<= 10;
170 else if("M" == u)
171 len <<= 20;
172 else if("G" == u)
173 len <<= 30;
174 }
175 m_nLength = len;
176 }
177 }
178 // Log file end
179
180 g_bPrintStackTrace = setConfig.value(
181 "PrintStackTrace",
182 g_bPrintStackTrace).toBool();
183 QStringList lstPrintStackTraceLevel;
184 foreach(auto l, g_lstPrintStackTraceLevel) {
185 lstPrintStackTraceLevel << QString::number(l);
186 }
187 lstPrintStackTraceLevel =
188 setConfig.value("PrintStackTraceLevel",
189 lstPrintStackTraceLevel).toStringList();
190 g_lstPrintStackTraceLevel.clear();
191 foreach (auto level, lstPrintStackTraceLevel) {
192 g_lstPrintStackTraceLevel << (QtMsgType)level.toInt();
193 }
194
195 setConfig.endGroup(); // Log end
196
197 setConfig.beginGroup("Rules");
198 auto keys = setConfig.childKeys();
199 //qDebug(log) << "keys" << keys;
200 szFilterRules.clear();
201 foreach(auto k, keys) {
202 QString v = setConfig.value(k).toString();
203 szFilterRules += k + "=" + v + "\n";
204 }
205 setConfig.endGroup(); // Rules end
206 }
207
208 if(!szFilterRules.isEmpty())
209 QLoggingCategory::setFilterRules(szFilterRules);
210
211#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
212 qSetMessagePattern(szPattern);
213#endif
214
215 QDir d;
216 if(!d.exists(m_szPath))
217 {
218 if(!d.mkpath(m_szPath)) {
219 qCritical(log) << "Create log directory fail." << m_szPath;
220 }
221 }
222
223 g_Mutex.lock();
224 if(g_File.isOpen())
225 g_File.close();
226 g_Mutex.unlock();
227 g_File.setFileName(QString());
228 slotTimeout();
229
230 if (QFile::exists(szFile))
231 qInfo(log) << "Load log configure file:" << m_szConfigureFile;
232 else
233 qWarning(log) << "Log configure file is not exist:" << szFile
234 << ". Use default settings.";
235
236 qInfo(log) << "Log configure:"
237 << "\n Path:" << m_szPath
238 << "\n Name:" << m_szName
239 << "\n DateFormat:" << m_szDateFormat
240 << "(Base file name: " + getBaseName() + ")"
241 << "\n Interval:" << nInterval
242 << "\n Count:" << m_nCount
243 << "\n Length:" << m_nLength
244 << "\n PrintStackTrace:" << g_bPrintStackTrace
245 << "\n PrintStackTraceLevel:" << g_lstPrintStackTraceLevel
246 << "\n szPattern:" << szPattern
247 << "\n Rules:" << szFilterRules;
248
249 m_Watcher.addPath(m_szConfigureFile);
250
251 if(m_Timer.isActive())
252 m_Timer.stop();
253 bool check = connect(&m_Timer, SIGNAL(timeout()),
254 this, SLOT(slotTimeout()));
255 Q_ASSERT(check);
256 m_Timer.start(nInterval * 1000);
257
258 return nRet;
259}
260
261void CLog::slotFileChanged(const QString &szPath)
262{
263 qDebug(log) << "File changed:" << szPath;
264 if(GetLogConfigureFile() != szPath)
265 return;
266 LoadConfigure(szPath);
267}
268
269QString CLog::GetLogFile()
270{
271 return g_File.fileName();
272}
273
274QString CLog::GetLogConfigureFile()
275{
276 return m_szConfigureFile;
277}
278
279QString CLog::GetLogDir()
280{
281 QString f = GetLogFile();
282 if(f.isEmpty()) return f;
283
284 QFileInfo fi(f);
285 return fi.absolutePath();
286}
287
288int CLog::SetFilter(const QString &szInclude, const QString &szExclude)
289{
290 g_reInclude = QRegularExpression(szInclude);
291 g_reExclude = QRegularExpression(szExclude);
292
293 QSettings set(RabbitCommon::CDir::Instance()->GetFileUserConfigure(),
294 QSettings::IniFormat);
295 set.setValue("Log/Filter/include", szInclude);
296 set.setValue("Log/Filter/Exclude", szExclude);
297 return 0;
298}
299
300int CLog::GetFilter(QString &szInclude, QString &szExclude)
301{
302 szInclude = g_reInclude.pattern();
303 szExclude = g_reExclude.pattern();
304 return 0;
305}
306
307#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
308void CLog::myMessageOutput(QtMsgType type,
309 const QMessageLogContext &context,
310 const QString &msg)
311{
312 QString szMsg;
313#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
314 szMsg = qFormatLogMessage(type, context, msg);
315#else
316 szMsg = msg;
317#endif
318
319 if(g_reInclude.isValid() && !g_reInclude.pattern().isEmpty()) {
320 QRegularExpressionMatch m = g_reInclude.match(szMsg);
321 if(!m.hasMatch()) {
322 return;
323 }
324 }
325 if(g_reExclude.isValid() && !g_reExclude.pattern().isEmpty()) {
326 QRegularExpressionMatch m = g_reExclude.match(szMsg);
327 if(m.hasMatch()) {
328 return;
329 }
330 }
331
332 if(g_bPrintStackTrace && g_lstPrintStackTraceLevel.contains(type))
333 {
334 szMsg += "\n";
335 if(g_pStack)
336 szMsg += g_pStack->GetStack();
337 }
338
339#ifdef HAVE_RABBITCOMMON_GUI
340 if(g_pDcokDebugLog)
341 emit g_pDcokDebugLog->sigAddLog(szMsg);
342#endif
343
344 g_Mutex.lock();
345 if(g_File.isOpen())
346 {
347 QTextStream s(&g_File);
348
349 s << szMsg << "\r\n";
350 //s.flush();
351 }
352
353 if(g_originalMessageHandler) {
354 QString szRawMsg = msg;
355 if(g_bPrintStackTrace && g_lstPrintStackTraceLevel.contains(type))
356 {
357 szRawMsg += "\n";
358 if(g_pStack)
359 szRawMsg += g_pStack->GetStack();
360 }
361 g_originalMessageHandler(type, context, szRawMsg);
362 }
363 g_Mutex.unlock();
364}
365#else
366void CLog::myMessageOutput(QtMsgType type, const char* msg)
367{
368 QString szMsg(msg);
369
370 if(g_reInclude.isValid() && !g_reInclude.pattern().isEmpty()) {
371 QRegularExpressionMatch m = g_reInclude.match(szMsg);
372 if(!m.hasMatch()) {
373 return;
374 }
375 }
376 if(g_reExclude.isValid() && !g_reExclude.pattern().isEmpty()) {
377 QRegularExpressionMatch m = g_reExclude.match(szMsg);
378 if(m.hasMatch()) {
379 return;
380 }
381 }
382
383 if(g_bPrintStackTrace && g_lstPrintStackTraceLevel.contains(type))
384 {
385 szMsg += "\n";
386 if(g_pStack)
387 szMsg += g_pStack->GetStack();
388 }
389
390#ifdef HAVE_RABBITCOMMON_GUI
391 if(g_pDcokDebugLog)
392 emit g_pDcokDebugLog->sigAddLog(szMsg);
393#endif
394
395 g_Mutex.lock();
396 if(g_File.isOpen())
397 {
398 QTextStream s(&g_File);
399 s << szMsg << "\r\n";
400 }
401 g_Mutex.unlock();
402
403 if(g_originalMessageHandler) {
404 QString szRawMsg = msg;
405 if(g_bPrintStackTrace && g_lstPrintStackTraceLevel.contains(type))
406 {
407 szRawMsg += "\n";
408 if(g_pStack)
409 szRawMsg += g_pStack->GetStack();
410 }
411 g_originalMessageHandler(type, szRawMsg);
412 }
413}
414#endif
415
416void CLog::checkFileCount()
417{
418 if(0 == m_nCount) return;
419 QString szFile;
420 QDir d(m_szPath);
421 d.setNameFilters(QStringList() << getBaseName() + "*");
422 auto lstFiles = d.entryInfoList(QDir::Files, QDir::Time);
423 if(lstFiles.size() <= m_nCount) return;
424
425 if(lstFiles.first().lastModified() < lstFiles.back().lastModified())
426 szFile = lstFiles.first().absoluteFilePath();
427 else
428 szFile = lstFiles.back().absoluteFilePath();
429
430 if(szFile.isEmpty()) return;
431
432 bool bRet = false;
433 bRet = d.remove(szFile);
434 if(bRet)
435 qDebug(log) << "Remove file:" << szFile;
436 else
437 qCritical(log) << "Remove file fail:" << szFile;
438}
439
440QString CLog::getBaseName()
441{
442 QString szSep("_");
443 return m_szName + szSep
444 + QDate::currentDate().toString(m_szDateFormat) + szSep
445 + QString::number(QCoreApplication::applicationPid())
446 ;
447}
448
449QString CLog::getFileName()
450{
451 QChar fill('0');
452 QString szNo = QString("%1").arg(1, 4, 10, fill);
453 QString szSep("_");
454 QString szName;
455 QString szFile;
456 QDir d(m_szPath);
457
458 szName = getBaseName();
459 d.setNameFilters(QStringList() << szName + "*");
460 auto lstFiles = d.entryInfoList(QDir::Files, QDir::Name);
461 if(lstFiles.isEmpty())
462 szFile = m_szPath + QDir::separator() + szName + szSep + szNo + ".log";
463 else
464 szFile = lstFiles.back().absoluteFilePath();
465
466 return szFile;
467}
468
469QString CLog::getNextFileName(const QString szFile)
470{
471 if(szFile.isEmpty())
472 return QString();
473
474 QFileInfo fi(szFile);
475 QChar fill('0');
476 QString szNo = QString("%1").arg(1, 4, 10, fill);
477 QString szSep("_");
478 QString szName = fi.baseName();
479
480 auto s = szName.split(szSep);
481 if(s.size() > 0)
482 {
483 szNo = s[s.size() - 1];
484 szNo = QString("%1").arg(szNo.toInt() + 1, 4, 10, fill);
485 }
486
487 return m_szPath + QDir::separator() + getBaseName() +szSep + szNo + ".log";
488}
489
490bool CLog::checkFileLength()
491{
492 if(m_nLength == 0) return false;
493
494 if(g_File.fileName().isEmpty()) return false;
495
496 QFileInfo fi(g_File.fileName());
497 if(fi.exists()) {
498 if(fi.size() < m_nLength) return false;
499 } else {
500 return false;
501 }
502
503 return true;
504}
505
506bool CLog::checkFileName()
507{
508 QString szFile;
509 szFile = g_File.fileName();
510 QFileInfo fi(szFile);
511 szFile = fi.baseName();
512
513 int nPos = szFile.lastIndexOf("_");
514 return szFile.left(nPos) == getBaseName();
515}
516
517void CLog::slotTimeout()
518{
519 QString szFile;
520 if(g_File.fileName().isEmpty())
521 {
522 szFile = getFileName();
523 g_File.setFileName(szFile);
524 }
525
526 do {
527 if(checkFileLength())
528 szFile = getNextFileName(g_File.fileName());
529 else if(!checkFileName())
530 szFile = getFileName();
531 else
532 if(g_File.isOpen()) break;
533
534 if(szFile.isEmpty()){
535 qCritical(log) << "The file is empty";
536 return;
537 }
538 Q_ASSERT(!szFile.isEmpty());
539
540 g_Mutex.lock();
541 if(g_File.isOpen())
542 g_File.close();
543
544 g_File.setFileName(szFile);
545 bool bRet = g_File.open(QFile::WriteOnly | QFile::Append);
546 g_Mutex.unlock();
547 if(bRet) {
548 qInfo(log) << "Log file:" << g_File.fileName();
549 } else {
550 qCritical(log) << "Open log file fail." << g_File.fileName();
551 }
552 } while(0);
553
554 checkFileCount();
555}
556
557#ifdef HAVE_RABBITCOMMON_GUI
558
559void OpenLogConfigureFile()
560{
561 QString f = RabbitCommon::CLog::Instance()->GetLogConfigureFile();
562 if(f.isEmpty())
563 {
564 qCritical(log) << "Configure file is empty";
565 return;
566 }
567
568 auto env = QProcessEnvironment::systemEnvironment();
569 bool bRet = false;
570 if(env.value("SNAP").isEmpty() && env.value("APPDIR").isEmpty()
571 && env.value("FLATPAK_ID").isEmpty()) {
572 bRet = QDesktopServices::openUrl(QUrl::fromLocalFile(f));
573 }
574 if(bRet)
575 return;
576
577 qDebug(log) << "Open log configure file:" << f;
578 CDlgEdit e(QObject::tr("Log configure file"), f,
579 QObject::tr("Log configure file:") + f, false);
580 if(RC_SHOW_WINDOW(&e) != QDialog::Accepted)
581 return;
582
583 QFile file(f);
584 if(file.open(QFile::WriteOnly)) {
585 QString szText = e.getContext();
586 file.write(szText.toStdString().c_str(), szText.length());
587 file.close();
588 return;
589 }
590 qWarning(log) << "Save log configure file fail:" << file.errorString() << f;
591 QFileInfo fi(f);
592 QString szFile
593 = QFileDialog::getSaveFileName(
594 nullptr, QObject::tr("Save as..."),
595 RabbitCommon::CDir::Instance()->GetDirUserConfig()
596 + QDir::separator() + fi.fileName()
597 );
598 if(!szFile.isEmpty()) {
599 QFile f(szFile);
600 if(f.open(QFile::WriteOnly)) {
601 QString szText = e.getContext();
602 f.write(szText.toStdString().c_str(), szText.length());
603 f.close();
604 QSettings set(RabbitCommon::CDir::Instance()->GetFileUserConfigure(),
605 QSettings::IniFormat);
606 set.setValue("Log/ConfigFile", szFile);
607 qInfo(log) << "Save log configure file:" << szFile;
608 }
609 }
610}
611
612void OpenLogFile()
613{
614 QString f = RabbitCommon::CLog::Instance()->GetLogFile();
615 if(f.isEmpty())
616 {
617 qCritical(log) << "Log file is empty";
618 return;
619 }
620 bool bRet = false;
621 auto env = QProcessEnvironment::systemEnvironment();
622 if(env.value("SNAP").isEmpty() && env.value("FLATPAK_ID").isEmpty())
623 bRet = QDesktopServices::openUrl(QUrl::fromLocalFile(f));
624 if(!bRet) {
625 //qCritical(log) << "Open log file fail:" << f;
626 CDlgEdit e(QObject::tr("Log file"), f, QObject::tr("Log file:") + f);
627 RC_SHOW_WINDOW(&e);
628 }
629}
630
631void OpenLogFolder()
632{
633 QString d = RabbitCommon::CLog::Instance()->GetLogDir();
634 if(d.isEmpty())
635 {
636 qCritical(log) << "Log folder is empty";
637 return;
638 }
639 bool bRet = false;
640 auto env = QProcessEnvironment::systemEnvironment();
641 if(env.value("SNAP").isEmpty() && env.value("FLATPAK_ID").isEmpty())
642 bRet = QDesktopServices::openUrl(QUrl::fromLocalFile(d));
643 if(!bRet) {
644 CFileBrowser* f = new CFileBrowser();
645 f->setWindowTitle(QObject::tr("Log folder"));
646 f->setRootPath(d);
647#if defined(Q_OS_ANDROID)
648 f->showMaximized();
649#else
650 f->show();
651#endif
652 }
653}
654
655#endif // #ifdef HAVE_RABBITCOMMON_GUI
656
657} // namespace RabbitCommon
The CDockDebugLog class.
File browser.
Definition FileBrowser.h:26
void setRootPath(const QString dir)
Set root path.