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 <QApplication>
24 #include "DockDebugLog.h"
25 #include "FileBrowser.h"
31#include "RabbitCommonDir.h"
32#include "RabbitCommonTools.h"
33#include "CoreDump/StackTrace.h"
39#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
40 static QtMessageHandler g_originalMessageHandler = Q_NULLPTR;
42 static QtMsgHandler g_originalMessageHandler = Q_NULLPTR;
44QRegularExpression g_reInclude;
45QRegularExpression g_reExclude;
47static bool g_bPrintStackTrace =
false;
48QList<QtMsgType> g_lstPrintStackTraceLevel{QtMsgType::QtCriticalMsg};
49static CCallTrace* g_pStack =
nullptr;
51static Q_LOGGING_CATEGORY(log,
"RabbitCommon.log")
53CLog::CLog() : QObject(),
54 m_szName(QCoreApplication::applicationName()),
55 m_szDateFormat("yyyy-MM-dd"),
59 if(!g_pStack) g_pStack =
new CCallTrace();
61#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
62 g_originalMessageHandler = qInstallMessageHandler(myMessageOutput);
64 g_originalMessageHandler = qInstallMsgHandler(myMessageOutput);
67 QSettings set(RabbitCommon::CDir::Instance()->GetFileUserConfigure(),
68 QSettings::IniFormat);
70 SetFilter(set.value(
"Log/Filter/include").toString(),
71 set.value(
"Log/Filter/Exclude").toString());
73 bool bReadOnly =
false;
74#if defined(Q_OS_ANDROID)
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,
85 if(!QFile::exists(szConfFile))
86 szConfFile = RabbitCommon::CDir::Instance()->GetDirConfig(bReadOnly)
87 + QDir::separator() + QCoreApplication::applicationName()
89 if(!QFile::exists(szConfFile))
90 szConfFile = RabbitCommon::CDir::Instance()->GetDirConfig(bReadOnly)
91 + QDir::separator() +
"logqt.ini";
92 LoadConfigure(szConfFile);
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();
101 check = connect(&m_Watcher, SIGNAL(fileChanged(QString)),
102 this, SLOT(slotFileChanged(QString)));
108 qDebug(log) <<
"CLog::~CLog()";
110 qInstallMessageHandler(g_originalMessageHandler);
111 if(g_File.isOpen()) g_File.close();
113 if(m_Timer.isActive()) m_Timer.stop();
114 if(g_pStack)
delete g_pStack;
117CLog* CLog::Instance()
119 static CLog* p = NULL;
127int CLog::LoadConfigure(
const QString &szFile)
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
134 szPattern +=
" [%{file}:%{line}, %{function}]";
135#elif defined(DEBUG) || defined(_DEBUG)
136 szPattern +=
" [%{file}:%{line}, %{function}]";
138 QString szFilterRules;
139 quint64 nInterval = 60;
140#if !(defined(DEBUG) || defined(_DEBUG) || ANDROID)
141 szFilterRules =
"*.debug = false";
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();
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())
164 if(m.capturedLength(1) > 0)
165 len = m.captured(1).toULong();
166 if(m.capturedLength(2) > 0)
168 QString u = m.captured(2).toUpper();
181 g_bPrintStackTrace = setConfig.value(
183 g_bPrintStackTrace).toBool();
184 QStringList lstPrintStackTraceLevel;
185 foreach(
auto l, g_lstPrintStackTraceLevel) {
186 lstPrintStackTraceLevel << QString::number(l);
188 lstPrintStackTraceLevel =
189 setConfig.value(
"PrintStackTraceLevel",
190 lstPrintStackTraceLevel).toStringList();
191 g_lstPrintStackTraceLevel.clear();
192 foreach (
auto level, lstPrintStackTraceLevel) {
193 g_lstPrintStackTraceLevel << (QtMsgType)level.toInt();
196 setConfig.endGroup();
198 setConfig.beginGroup(
"Rules");
199 auto keys = setConfig.childKeys();
201 szFilterRules.clear();
202 foreach(
auto k, keys) {
203 QString v = setConfig.value(k).toString();
204 szFilterRules += k +
"=" + v +
"\n";
206 setConfig.endGroup();
209 if(!szFilterRules.isEmpty())
210 QLoggingCategory::setFilterRules(szFilterRules);
212#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
213 qSetMessagePattern(szPattern);
217 if(m_szPath.isEmpty())
218 m_szPath = CDir::Instance()->GetDirLog();
219 if(!d.exists(m_szPath))
221 if(!d.mkpath(m_szPath)) {
222 qCritical(log) <<
"Create log directory fail." << m_szPath;
230 g_File.setFileName(QString());
233 if (QFile::exists(szFile))
234 qInfo(log) <<
"Load log configure file:" << m_szConfigureFile;
236 qWarning(log) <<
"Log configure file is not exist:" << szFile
237 <<
". Use default settings.";
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;
252 m_Watcher.addPath(m_szConfigureFile);
254 if(m_Timer.isActive())
256 bool check = connect(&m_Timer, SIGNAL(timeout()),
257 this, SLOT(slotTimeout()));
259 m_Timer.start(nInterval * 1000);
264void CLog::slotFileChanged(
const QString &szPath)
266 qDebug(log) <<
"File changed:" << szPath;
267 if(GetLogConfigureFile() != szPath)
269 LoadConfigure(szPath);
272QString CLog::GetLogFile()
274 return g_File.fileName();
277QString CLog::GetLogConfigureFile()
279 return m_szConfigureFile;
282QString CLog::GetLogDir()
284 QString f = GetLogFile();
285 if(f.isEmpty())
return f;
288 return fi.absolutePath();
291int CLog::SetFilter(
const QString &szInclude,
const QString &szExclude)
293 g_reInclude = QRegularExpression(szInclude);
294 g_reExclude = QRegularExpression(szExclude);
296 QSettings set(RabbitCommon::CDir::Instance()->GetFileUserConfigure(),
297 QSettings::IniFormat);
298 set.setValue(
"Log/Filter/include", szInclude);
299 set.setValue(
"Log/Filter/Exclude", szExclude);
303int CLog::GetFilter(QString &szInclude, QString &szExclude)
305 szInclude = g_reInclude.pattern();
306 szExclude = g_reExclude.pattern();
310#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
311void CLog::myMessageOutput(QtMsgType type,
312 const QMessageLogContext &context,
316#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
317 szMsg = qFormatLogMessage(type, context, msg);
322 if(g_reInclude.isValid() && !g_reInclude.pattern().isEmpty()) {
323 QRegularExpressionMatch m = g_reInclude.match(szMsg);
328 if(g_reExclude.isValid() && !g_reExclude.pattern().isEmpty()) {
329 QRegularExpressionMatch m = g_reExclude.match(szMsg);
335 if(g_bPrintStackTrace && g_lstPrintStackTraceLevel.contains(type))
339 szMsg += g_pStack->GetStack();
342#ifdef HAVE_RABBITCOMMON_GUI
344 emit g_pDcokDebugLog->sigAddLog(szMsg);
350 QTextStream s(&g_File);
352 s << szMsg <<
"\r\n";
356 if(g_originalMessageHandler) {
357 QString szRawMsg = msg;
358 if(g_bPrintStackTrace && g_lstPrintStackTraceLevel.contains(type))
362 szRawMsg += g_pStack->GetStack();
364 g_originalMessageHandler(type, context, szRawMsg);
369void CLog::myMessageOutput(QtMsgType type,
const char* msg)
373 if(g_reInclude.isValid() && !g_reInclude.pattern().isEmpty()) {
374 QRegularExpressionMatch m = g_reInclude.match(szMsg);
379 if(g_reExclude.isValid() && !g_reExclude.pattern().isEmpty()) {
380 QRegularExpressionMatch m = g_reExclude.match(szMsg);
386 if(g_bPrintStackTrace && g_lstPrintStackTraceLevel.contains(type))
390 szMsg += g_pStack->GetStack();
393#ifdef HAVE_RABBITCOMMON_GUI
395 emit g_pDcokDebugLog->sigAddLog(szMsg);
401 QTextStream s(&g_File);
402 s << szMsg <<
"\r\n";
406 if(g_originalMessageHandler) {
407 QString szRawMsg = msg;
408 if(g_bPrintStackTrace && g_lstPrintStackTraceLevel.contains(type))
412 szRawMsg += g_pStack->GetStack();
414 g_originalMessageHandler(type, szRawMsg);
419void CLog::checkFileCount()
421 if(0 == m_nCount)
return;
424 d.setNameFilters(QStringList() << getBaseName() +
"*");
425 auto lstFiles = d.entryInfoList(QDir::Files, QDir::Time);
426 if(lstFiles.size() <= m_nCount)
return;
428 if(lstFiles.first().lastModified() < lstFiles.back().lastModified())
429 szFile = lstFiles.first().absoluteFilePath();
431 szFile = lstFiles.back().absoluteFilePath();
433 if(szFile.isEmpty())
return;
436 bRet = d.remove(szFile);
438 qDebug(log) <<
"Remove file:" << szFile;
440 qCritical(log) <<
"Remove file fail:" << szFile;
443QString CLog::getBaseName()
446 return m_szName + szSep
447 + QDate::currentDate().toString(m_szDateFormat) + szSep
448 + QString::number(QCoreApplication::applicationPid())
452QString CLog::getFileName()
455 QString szNo = QString(
"%1").arg(1, 4, 10, fill);
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";
467 szFile = lstFiles.back().absoluteFilePath();
472QString CLog::getNextFileName(
const QString szFile)
477 QFileInfo fi(szFile);
479 QString szNo = QString(
"%1").arg(1, 4, 10, fill);
481 QString szName = fi.baseName();
483 auto s = szName.split(szSep);
486 szNo = s[s.size() - 1];
487 szNo = QString(
"%1").arg(szNo.toInt() + 1, 4, 10, fill);
490 return m_szPath + QDir::separator() + getBaseName() +szSep + szNo +
".log";
493bool CLog::checkFileLength()
495 if(m_nLength == 0)
return false;
497 if(g_File.fileName().isEmpty())
return false;
499 QFileInfo fi(g_File.fileName());
501 if(fi.size() < m_nLength)
return false;
509bool CLog::checkFileName()
512 szFile = g_File.fileName();
513 QFileInfo fi(szFile);
514 szFile = fi.baseName();
516 int nPos = szFile.lastIndexOf(
"_");
517 return szFile.left(nPos) == getBaseName();
520void CLog::slotTimeout()
523 if(g_File.fileName().isEmpty())
525 szFile = getFileName();
526 g_File.setFileName(szFile);
530 if(checkFileLength())
531 szFile = getNextFileName(g_File.fileName());
532 else if(!checkFileName())
533 szFile = getFileName();
535 if(g_File.isOpen())
break;
537 if(szFile.isEmpty()){
538 qCritical(log) <<
"The file is empty";
541 Q_ASSERT(!szFile.isEmpty());
547 g_File.setFileName(szFile);
548 bool bRet = g_File.open(QFile::WriteOnly | QFile::Append);
551 qInfo(log) <<
"Log file:" << g_File.fileName();
553 qCritical(log) <<
"Open log file fail." << g_File.fileName();
560#ifdef HAVE_RABBITCOMMON_GUI
562void OpenLogConfigureFile()
564 QString f = RabbitCommon::CLog::Instance()->GetLogConfigureFile();
567 qCritical(log) <<
"Configure file is empty";
571 auto env = QProcessEnvironment::systemEnvironment();
573 if(env.value(
"SNAP").isEmpty() && env.value(
"APPDIR").isEmpty()
574 && env.value(
"FLATPAK_ID").isEmpty()) {
575 bRet = QDesktopServices::openUrl(QUrl::fromLocalFile(f));
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)
587 if(file.open(QFile::WriteOnly)) {
588 QString szText = e.getContext();
589 file.write(szText.toStdString().c_str(), szText.length());
593 qWarning(log) <<
"Save log configure file fail:" << file.errorString() << f;
596 = QFileDialog::getSaveFileName(
597 nullptr, QObject::tr(
"Save as..."),
598 RabbitCommon::CDir::Instance()->GetDirUserConfig()
599 + QDir::separator() + fi.fileName()
601 if(!szFile.isEmpty()) {
603 if(f.open(QFile::WriteOnly)) {
604 QString szText = e.getContext();
605 f.write(szText.toStdString().c_str(), szText.length());
607 QSettings set(RabbitCommon::CDir::Instance()->GetFileUserConfigure(),
608 QSettings::IniFormat);
609 set.setValue(
"Log/ConfigFile", szFile);
610 qInfo(log) <<
"Save log configure file:" << szFile;
617 QString f = RabbitCommon::CLog::Instance()->GetLogFile();
620 qCritical(log) <<
"Log file is empty";
624 auto env = QProcessEnvironment::systemEnvironment();
625 if(env.value(
"SNAP").isEmpty() && env.value(
"FLATPAK_ID").isEmpty())
626 bRet = QDesktopServices::openUrl(QUrl::fromLocalFile(f));
629 CDlgEdit e(QObject::tr(
"Log file"), f, QObject::tr(
"Log file:") + f);
637 d = RabbitCommon::CLog::Instance()->GetLogFile();
640 qCritical(log) <<
"Log folder is empty";
643#if defined(Q_OS_LINUX)
645 auto env = QProcessEnvironment::systemEnvironment();
646 if(env.value(
"SNAP").isEmpty() && env.value(
"FLATPAK_ID").isEmpty()) {
647 bRet = RabbitCommon::CTools::LocateFileWithExplorer(d);
650 d = RabbitCommon::CLog::Instance()->GetLogDir();
652 f->setWindowTitle(QObject::tr(
"Log folder"));
654 #if defined(Q_OS_ANDROID)
661 RabbitCommon::CTools::LocateFileWithExplorer(d);
665void CopyLogFileToClipboard()
667 QString f = RabbitCommon::CLog::Instance()->GetLogFile();
670 qCritical(log) <<
"Log file is empty";
673 auto cb = QApplication::clipboard();
679void CopyLogFolderToClipboard()
682 d = RabbitCommon::CLog::Instance()->GetLogDir();
685 qCritical(log) <<
"Log folder is empty";
688 auto cb = QApplication::clipboard();
void setRootPath(const QString dir)
Set root path.