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