10#include <QRegularExpression>
14#include <QCoreApplication>
15#include <QProcessEnvironment>
16#include <QStandardPaths>
17#include <QLoggingCategory>
19#ifdef HAVE_RABBITCOMMON_GUI
20 #include <QDesktopServices>
21 #include <QMessageBox>
23 #include "DockDebugLog.h"
24 #include "FileBrowser.h"
30#include "RabbitCommonDir.h"
31#include "RabbitCommonTools.h"
32#include "CoreDump/StackTrace.h"
38#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
39 static QtMessageHandler g_originalMessageHandler = Q_NULLPTR;
41 static QtMsgHandler g_originalMessageHandler = Q_NULLPTR;
43QRegularExpression g_reInclude;
44QRegularExpression g_reExclude;
46static bool g_bPrintStackTrace =
false;
47QList<QtMsgType> g_lstPrintStackTraceLevel{QtMsgType::QtCriticalMsg};
48static CCallTrace* g_pStack =
nullptr;
50static Q_LOGGING_CATEGORY(log,
"RabbitCommon.log")
52CLog::CLog() : QObject(),
53 m_szName(QCoreApplication::applicationName()),
54 m_szDateFormat("yyyy-MM-dd"),
58 if(!g_pStack) g_pStack =
new CCallTrace();
60#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
61 g_originalMessageHandler = qInstallMessageHandler(myMessageOutput);
63 g_originalMessageHandler = qInstallMsgHandler(myMessageOutput);
66 QSettings set(RabbitCommon::CDir::Instance()->GetFileUserConfigure(),
67 QSettings::IniFormat);
69 SetFilter(set.value(
"Log/Filter/include").toString(),
70 set.value(
"Log/Filter/Exclude").toString());
72 bool bReadOnly =
false;
73#if defined(Q_OS_ANDROID)
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,
84 if(!QFile::exists(szConfFile))
85 szConfFile = RabbitCommon::CDir::Instance()->GetDirConfig(bReadOnly)
86 + QDir::separator() + QCoreApplication::applicationName()
88 if(!QFile::exists(szConfFile))
89 szConfFile = RabbitCommon::CDir::Instance()->GetDirConfig(bReadOnly)
90 + QDir::separator() +
"logqt.ini";
91 LoadConfigure(szConfFile);
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();
100 check = connect(&m_Watcher, SIGNAL(fileChanged(QString)),
101 this, SLOT(slotFileChanged(QString)));
107 qDebug(log) <<
"CLog::~CLog()";
109 qInstallMessageHandler(g_originalMessageHandler);
110 if(g_File.isOpen()) g_File.close();
112 if(m_Timer.isActive()) m_Timer.stop();
113 if(g_pStack)
delete g_pStack;
116CLog* CLog::Instance()
118 static CLog* p = NULL;
126int CLog::LoadConfigure(
const QString &szFile)
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
133 szPattern +=
" [%{file}:%{line}, %{function}]";
134#elif defined(DEBUG) || defined(_DEBUG)
135 szPattern +=
" [%{file}:%{line}, %{function}]";
137 QString szFilterRules;
138 quint64 nInterval = 60;
139#if !(defined(DEBUG) || defined(_DEBUG) || ANDROID)
140 szFilterRules =
"*.debug = false";
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();
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())
163 if(m.capturedLength(1) > 0)
164 len = m.captured(1).toULong();
165 if(m.capturedLength(2) > 0)
167 QString u = m.captured(2).toUpper();
180 g_bPrintStackTrace = setConfig.value(
182 g_bPrintStackTrace).toBool();
183 QStringList lstPrintStackTraceLevel;
184 foreach(
auto l, g_lstPrintStackTraceLevel) {
185 lstPrintStackTraceLevel << QString::number(l);
187 lstPrintStackTraceLevel =
188 setConfig.value(
"PrintStackTraceLevel",
189 lstPrintStackTraceLevel).toStringList();
190 g_lstPrintStackTraceLevel.clear();
191 foreach (
auto level, lstPrintStackTraceLevel) {
192 g_lstPrintStackTraceLevel << (QtMsgType)level.toInt();
195 setConfig.endGroup();
197 setConfig.beginGroup(
"Rules");
198 auto keys = setConfig.childKeys();
200 szFilterRules.clear();
201 foreach(
auto k, keys) {
202 QString v = setConfig.value(k).toString();
203 szFilterRules += k +
"=" + v +
"\n";
205 setConfig.endGroup();
208 if(!szFilterRules.isEmpty())
209 QLoggingCategory::setFilterRules(szFilterRules);
211#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
212 qSetMessagePattern(szPattern);
216 if(!d.exists(m_szPath))
218 if(!d.mkpath(m_szPath)) {
219 qCritical(log) <<
"Create log directory fail." << m_szPath;
227 g_File.setFileName(QString());
230 if (QFile::exists(szFile))
231 qInfo(log) <<
"Load log configure file:" << m_szConfigureFile;
233 qWarning(log) <<
"Log configure file is not exist:" << szFile
234 <<
". Use default settings.";
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;
249 m_Watcher.addPath(m_szConfigureFile);
251 if(m_Timer.isActive())
253 bool check = connect(&m_Timer, SIGNAL(timeout()),
254 this, SLOT(slotTimeout()));
256 m_Timer.start(nInterval * 1000);
261void CLog::slotFileChanged(
const QString &szPath)
263 qDebug(log) <<
"File changed:" << szPath;
264 if(GetLogConfigureFile() != szPath)
266 LoadConfigure(szPath);
269QString CLog::GetLogFile()
271 return g_File.fileName();
274QString CLog::GetLogConfigureFile()
276 return m_szConfigureFile;
279QString CLog::GetLogDir()
281 QString f = GetLogFile();
282 if(f.isEmpty())
return f;
285 return fi.absolutePath();
288int CLog::SetFilter(
const QString &szInclude,
const QString &szExclude)
290 g_reInclude = QRegularExpression(szInclude);
291 g_reExclude = QRegularExpression(szExclude);
293 QSettings set(RabbitCommon::CDir::Instance()->GetFileUserConfigure(),
294 QSettings::IniFormat);
295 set.setValue(
"Log/Filter/include", szInclude);
296 set.setValue(
"Log/Filter/Exclude", szExclude);
300int CLog::GetFilter(QString &szInclude, QString &szExclude)
302 szInclude = g_reInclude.pattern();
303 szExclude = g_reExclude.pattern();
307#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
308void CLog::myMessageOutput(QtMsgType type,
309 const QMessageLogContext &context,
313#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
314 szMsg = qFormatLogMessage(type, context, msg);
319 if(g_reInclude.isValid() && !g_reInclude.pattern().isEmpty()) {
320 QRegularExpressionMatch m = g_reInclude.match(szMsg);
325 if(g_reExclude.isValid() && !g_reExclude.pattern().isEmpty()) {
326 QRegularExpressionMatch m = g_reExclude.match(szMsg);
332 if(g_bPrintStackTrace && g_lstPrintStackTraceLevel.contains(type))
336 szMsg += g_pStack->GetStack();
339#ifdef HAVE_RABBITCOMMON_GUI
341 emit g_pDcokDebugLog->sigAddLog(szMsg);
347 QTextStream s(&g_File);
349 s << szMsg <<
"\r\n";
353 if(g_originalMessageHandler) {
354 QString szRawMsg = msg;
355 if(g_bPrintStackTrace && g_lstPrintStackTraceLevel.contains(type))
359 szRawMsg += g_pStack->GetStack();
361 g_originalMessageHandler(type, context, szRawMsg);
366void CLog::myMessageOutput(QtMsgType type,
const char* msg)
370 if(g_reInclude.isValid() && !g_reInclude.pattern().isEmpty()) {
371 QRegularExpressionMatch m = g_reInclude.match(szMsg);
376 if(g_reExclude.isValid() && !g_reExclude.pattern().isEmpty()) {
377 QRegularExpressionMatch m = g_reExclude.match(szMsg);
383 if(g_bPrintStackTrace && g_lstPrintStackTraceLevel.contains(type))
387 szMsg += g_pStack->GetStack();
390#ifdef HAVE_RABBITCOMMON_GUI
392 emit g_pDcokDebugLog->sigAddLog(szMsg);
398 QTextStream s(&g_File);
399 s << szMsg <<
"\r\n";
403 if(g_originalMessageHandler) {
404 QString szRawMsg = msg;
405 if(g_bPrintStackTrace && g_lstPrintStackTraceLevel.contains(type))
409 szRawMsg += g_pStack->GetStack();
411 g_originalMessageHandler(type, szRawMsg);
416void CLog::checkFileCount()
418 if(0 == m_nCount)
return;
421 d.setNameFilters(QStringList() << getBaseName() +
"*");
422 auto lstFiles = d.entryInfoList(QDir::Files, QDir::Time);
423 if(lstFiles.size() <= m_nCount)
return;
425 if(lstFiles.first().lastModified() < lstFiles.back().lastModified())
426 szFile = lstFiles.first().absoluteFilePath();
428 szFile = lstFiles.back().absoluteFilePath();
430 if(szFile.isEmpty())
return;
433 bRet = d.remove(szFile);
435 qDebug(log) <<
"Remove file:" << szFile;
437 qCritical(log) <<
"Remove file fail:" << szFile;
440QString CLog::getBaseName()
443 return m_szName + szSep
444 + QDate::currentDate().toString(m_szDateFormat) + szSep
445 + QString::number(QCoreApplication::applicationPid())
449QString CLog::getFileName()
452 QString szNo = QString(
"%1").arg(1, 4, 10, fill);
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";
464 szFile = lstFiles.back().absoluteFilePath();
469QString CLog::getNextFileName(
const QString szFile)
474 QFileInfo fi(szFile);
476 QString szNo = QString(
"%1").arg(1, 4, 10, fill);
478 QString szName = fi.baseName();
480 auto s = szName.split(szSep);
483 szNo = s[s.size() - 1];
484 szNo = QString(
"%1").arg(szNo.toInt() + 1, 4, 10, fill);
487 return m_szPath + QDir::separator() + getBaseName() +szSep + szNo +
".log";
490bool CLog::checkFileLength()
492 if(m_nLength == 0)
return false;
494 if(g_File.fileName().isEmpty())
return false;
496 QFileInfo fi(g_File.fileName());
498 if(fi.size() < m_nLength)
return false;
506bool CLog::checkFileName()
509 szFile = g_File.fileName();
510 QFileInfo fi(szFile);
511 szFile = fi.baseName();
513 int nPos = szFile.lastIndexOf(
"_");
514 return szFile.left(nPos) == getBaseName();
517void CLog::slotTimeout()
520 if(g_File.fileName().isEmpty())
522 szFile = getFileName();
523 g_File.setFileName(szFile);
527 if(checkFileLength())
528 szFile = getNextFileName(g_File.fileName());
529 else if(!checkFileName())
530 szFile = getFileName();
532 if(g_File.isOpen())
break;
534 if(szFile.isEmpty()){
535 qCritical(log) <<
"The file is empty";
538 Q_ASSERT(!szFile.isEmpty());
544 g_File.setFileName(szFile);
545 bool bRet = g_File.open(QFile::WriteOnly | QFile::Append);
548 qInfo(log) <<
"Log file:" << g_File.fileName();
550 qCritical(log) <<
"Open log file fail." << g_File.fileName();
557#ifdef HAVE_RABBITCOMMON_GUI
559void OpenLogConfigureFile()
561 QString f = RabbitCommon::CLog::Instance()->GetLogConfigureFile();
564 qCritical(log) <<
"Configure file is empty";
568 auto env = QProcessEnvironment::systemEnvironment();
570 if(env.value(
"SNAP").isEmpty() && env.value(
"APPDIR").isEmpty()
571 && env.value(
"FLATPAK_ID").isEmpty()) {
572 bRet = QDesktopServices::openUrl(QUrl::fromLocalFile(f));
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)
584 if(file.open(QFile::WriteOnly)) {
585 QString szText = e.getContext();
586 file.write(szText.toStdString().c_str(), szText.length());
590 qWarning(log) <<
"Save log configure file fail:" << file.errorString() << f;
593 = QFileDialog::getSaveFileName(
594 nullptr, QObject::tr(
"Save as..."),
595 RabbitCommon::CDir::Instance()->GetDirUserConfig()
596 + QDir::separator() + fi.fileName()
598 if(!szFile.isEmpty()) {
600 if(f.open(QFile::WriteOnly)) {
601 QString szText = e.getContext();
602 f.write(szText.toStdString().c_str(), szText.length());
604 QSettings set(RabbitCommon::CDir::Instance()->GetFileUserConfigure(),
605 QSettings::IniFormat);
606 set.setValue(
"Log/ConfigFile", szFile);
607 qInfo(log) <<
"Save log configure file:" << szFile;
614 QString f = RabbitCommon::CLog::Instance()->GetLogFile();
617 qCritical(log) <<
"Log file is empty";
621 auto env = QProcessEnvironment::systemEnvironment();
622 if(env.value(
"SNAP").isEmpty() && env.value(
"FLATPAK_ID").isEmpty())
623 bRet = QDesktopServices::openUrl(QUrl::fromLocalFile(f));
626 CDlgEdit e(QObject::tr(
"Log file"), f, QObject::tr(
"Log file:") + f);
633 QString d = RabbitCommon::CLog::Instance()->GetLogDir();
636 qCritical(log) <<
"Log folder is empty";
640 auto env = QProcessEnvironment::systemEnvironment();
641 if(env.value(
"SNAP").isEmpty() && env.value(
"FLATPAK_ID").isEmpty())
642 bRet = QDesktopServices::openUrl(QUrl::fromLocalFile(d));
645 f->setWindowTitle(QObject::tr(
"Log folder"));
647#if defined(Q_OS_ANDROID)
void setRootPath(const QString dir)
Set root path.