3#include <QtConcurrent/QtConcurrentRun>
4#include <QtCore/QCoreApplication>
5#include <QtCore/QMutex>
6#include <QtCore/QMutexLocker>
7#include <QtCore/QPointer>
8#include <QtCore/QSaveFile>
9#include <QtCore/QThread>
10#include <QtQml/QJSEngine>
25static std::atomic<
LogManager*> s_instance{
nullptr};
41void LogManager::msgHandler(QtMsgType type,
const QMessageLogContext& context,
const QString& msg)
43 auto* inst = s_instance.load(std::memory_order_acquire);
54 if (context.category && std::strncmp(context.category,
"qt.quick", 8) == 0) {
58 LogManager::captureIfEnabled(type, context, msg);
59 inst->log(type, context, msg);
68 return s_instance.load(std::memory_order_acquire);
71LogManager* LogManager::create(QQmlEngine* qmlEngine, QJSEngine* jsEngine)
74 auto* inst = instance();
76 QJSEngine::setObjectOwnership(inst, QJSEngine::CppOwnership);
80LogManager::LogManager(QObject* parent) : QObject(parent)
82 s_instance.store(
this, std::memory_order_release);
91 if (size >= kMaxLogFileSize) {
96 _flushTimer.setInterval(kFlushIntervalMSecs);
97 _flushTimer.setSingleShot(
false);
98 (void)connect(&_flushTimer, &QTimer::timeout,
this, &LogManager::_flushToDisk);
102LogManager::~LogManager()
105 if (s_instance.load(std::memory_order_relaxed) ==
this) {
106 s_instance.store(
nullptr, std::memory_order_release);
110 QCoreApplication::processEvents();
114 _remoteSink->setEnabled(
false);
115 _fileWriter->
close();
118 if (_exportFuture.isValid()) {
119 _exportFuture.waitForFinished();
123void LogManager::installHandler()
125 Q_ASSERT(!s_instance.load(std::memory_order_relaxed));
129 QStringLiteral(
"%{time process}%{if-warning} Warning:%{endif}%{if-critical} Critical:%{endif} %{message} - "
130 "%{category} - (%{function}:%{line})"));
134void LogManager::applyEnvironmentLogLevel()
136 const QByteArray env = qgetenv(
"QGC_LOG_LEVEL");
141 const QString level = QString::fromUtf8(env).toLower().trimmed();
144 if (level == QStringLiteral(
"trace") || level == QStringLiteral(
"debug")) {
145 rules = QStringLiteral(
"*.debug=true\n");
146 }
else if (level == QStringLiteral(
"info")) {
147 rules = QStringLiteral(
"*.debug=false\n*.info=true\n");
148 }
else if (level == QStringLiteral(
"warning") || level == QStringLiteral(
"warn")) {
149 rules = QStringLiteral(
"*.debug=false\n*.info=false\n*.warning=true\n");
150 }
else if (level == QStringLiteral(
"critical") || level == QStringLiteral(
"error")) {
151 rules = QStringLiteral(
"*.debug=false\n*.info=false\n*.warning=false\n*.critical=true\n");
152 }
else if (level == QStringLiteral(
"off") || level == QStringLiteral(
"none")) {
153 rules = QStringLiteral(
"*.debug=false\n*.info=false\n*.warning=false\n*.critical=false\n");
155 qWarning(
"QGC_LOG_LEVEL: unknown level '%s' (use debug/info/warning/critical/off)", env.constData());
159 QLoggingCategory::setFilterRules(rules);
166void LogManager::log(QtMsgType type,
const QMessageLogContext& context,
const QString& message)
168 LogEntry entry = buildEntry(type, context, message);
170 QMetaObject::invokeMethod(
172 [
this, entry = std::move(entry)]()
mutable {
176 Qt::QueuedConnection);
179const QString& LogManager::_internCategory(
const QString& category)
181 auto it = _internedCategories.find(category);
182 if (it != _internedCategories.end()) {
185 return *_internedCategories.insert(category);
188void LogManager::_dispatchToSinks(
const LogEntry& entry)
190 _model->enqueue(entry);
191 if (_logStore->isOpen()) {
192 _logStore->append(entry);
194 if (_diskLoggingEnabled) {
195 _pendingDiskWrites.append(entry);
198 _remoteSink->send(entry);
202void LogManager::_handleEntry(
const LogEntry& entry)
204 if (!_rateLimitCheck(entry)) {
208 _dispatchToSinks(entry);
210 if (_flushOnLevel >= 0 &&
static_cast<int>(entry.
level) >= _flushOnLevel) {
215bool LogManager::_rateLimitCheck(
const LogEntry& entry)
221 const qint64 now = QDateTime::currentMSecsSinceEpoch();
222 auto& bucket = _rateBuckets[entry.
category];
224 if (bucket.lastRefillMs == 0) {
225 bucket.lastRefillMs = now;
226 bucket.tokens = kRateMaxTokens;
229 const qint64 elapsed = now - bucket.lastRefillMs;
231 const int refill =
static_cast<int>(elapsed * kRateTokensPerSecond / 1000);
233 bucket.tokens = qMin(bucket.tokens + refill, kRateMaxTokens);
234 bucket.lastRefillMs = now;
236 if (bucket.suppressed > 0 && bucket.tokens > 0) {
237 _emitSuppressedSummary(entry.
category, bucket.suppressed);
238 bucket.suppressed = 0;
243 if (bucket.tokens > 0) {
252void LogManager::_emitSuppressedSummary(
const QString& category,
int count)
255 summary.
timestamp = QDateTime::currentDateTime();
256 summary.
level = LogEntry::Warning;
258 summary.
message = QStringLiteral(
"... %1 messages suppressed (rate limited)").arg(count);
261 _dispatchToSinks(summary);
268void LogManager::clearError()
278void LogManager::flush()
280 Q_ASSERT(QThread::currentThread() == thread());
282 _fileWriter->
flush();
285void LogManager::setDiskLoggingEnabled(
bool enabled)
287 if (_diskLoggingEnabled != enabled) {
290 _fileWriter->
close();
292 _diskLoggingEnabled = enabled;
297void LogManager::setDiskCompressionEnabled(
bool enabled)
299 if (_diskCompressionEnabled != enabled) {
300 _diskCompressionEnabled = enabled;
305void LogManager::setFlushOnLevel(
int level)
307 if (_flushOnLevel != level) {
308 _flushOnLevel = level;
313QStringList LogManager::categoryLogLevelNames()
315 return {tr(
"Debug"), tr(
"Info"), tr(
"Warning"), tr(
"Critical")};
318QVariantList LogManager::categoryLogLevelValues()
320 return {QtDebugMsg, QtInfoMsg, QtWarningMsg, QtCriticalMsg};
327void LogManager::_setIoError(
const QString& message)
330 _lastError = message;
335void LogManager::setLogDirectory(
const QString& path)
337 if (_logDirectory == path) {
340 _logDirectory = path;
342 if (_logStore->isOpen()) {
346 if (path.isEmpty()) {
351 const QDir dir(path);
352 _fileWriter->
setFilePath(dir.absoluteFilePath(QStringLiteral(
"QGCConsole.log")));
353 _logStore->open(dir.absoluteFilePath(QStringLiteral(
"QGCConsole.db")));
356void LogManager::_rotateLogs()
358 _fileWriter->
flush();
359 _fileWriter->
close();
361 const QString path = _fileWriter->
filePath();
362 const QFileInfo fileInfo(path);
363 const QString dir = fileInfo.absolutePath();
364 const QString name = fileInfo.baseName();
365 const QString ext = fileInfo.completeSuffix();
367 for (
int i = kMaxBackupFiles - 1; i >= 1; --i) {
368 const QString from = QStringLiteral(
"%1/%2.%3.%4").arg(dir, name).arg(i).arg(ext);
369 const QString to = QStringLiteral(
"%1/%2.%3.%4").arg(dir, name).arg(i + 1).arg(ext);
370 if (QFile::exists(to)) {
371 (void)QFile::remove(to);
373 if (QFile::exists(from)) {
374 (void)QFile::rename(from, to);
378 const QString firstBackup = QStringLiteral(
"%1/%2.1.%3").arg(dir, name, ext);
379 (void)QFile::rename(path, firstBackup);
384void LogManager::_flushToDisk()
386 if (_pendingDiskWrites.isEmpty() || _ioError || !_diskLoggingEnabled || _logDirectory.isEmpty()) {
390 auto entries = std::move(_pendingDiskWrites);
398void LogManager::writeMessages(
const QString& destFile, ExportFormat format)
400 _exportEntries(_model->allEntriesSnapshot(), destFile, format);
403void LogManager::writeFilteredMessages(
const QString& destFile, ExportFormat format)
405 _exportEntries(_model->filteredEntries(), destFile, format);
408void LogManager::_exportEntries(QList<LogEntry> entries,
const QString& destFile, ExportFormat format)
412 QPointer<LogManager> guard(
this);
413 _exportFuture = QtConcurrent::run([guard, destFile, entries = std::move(entries), format]() {
414 bool success =
false;
415 QSaveFile file(destFile);
416 if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
419 success = file.commit();
421 qCWarning(LogManagerLog) <<
"write failed:" << file.errorString();
424 QMetaObject::invokeMethod(
428 emit guard->writeFinished(success);
431 Qt::QueuedConnection);
440void LogManager::setCaptureEnabled(
bool enabled)
445void LogManager::clearCapturedMessages()
451QList<LogEntry> LogManager::capturedMessages(
const QString& category)
455 if (category.isEmpty()) {
459 QList<LogEntry> filtered;
461 if (msg.category == category) {
462 filtered.append(msg);
468bool LogManager::hasCapturedMessage(
const QString& category, LogEntry::Level level)
472 if (msg.category == category && msg.level == level) {
479bool LogManager::hasCapturedWarning(
const QString& category)
481 return hasCapturedMessage(category, LogEntry::Warning);
484bool LogManager::hasCapturedCritical(
const QString& category)
486 return hasCapturedMessage(category, LogEntry::Critical);
489bool LogManager::hasCapturedUncategorizedMessage()
493 if (msg.category.isEmpty() || msg.category == QStringLiteral(
"default")) {
500void LogManager::captureIfEnabled(QtMsgType type,
const QMessageLogContext& context,
const QString& msg)
506 LogEntry entry = buildEntry(type, context, msg);
512LogEntry LogManager::buildEntry(QtMsgType type,
const QMessageLogContext& context,
const QString& message)
515 entry.
timestamp = QDateTime::currentDateTime();
517 entry.
category = context.category ? QString::fromLatin1(context.category) : QString();
520 const QString fullPath = QString::fromLatin1(context.file);
521 const int lastSlash = fullPath.lastIndexOf(QLatin1Char(
'/'));
522 entry.
file = (lastSlash >= 0) ? fullPath.mid(lastSlash + 1) : fullPath;
524 entry.
function = context.function ? QString::fromLatin1(context.function) : QString();
525 entry.
line = context.line;
526 entry.
threadId = QThread::currentThreadId();
static QtMessageHandler s_defaultHandler
static QMutex s_captureMutex
static QList< LogEntry > s_capturedMessages
static Q_LOGGING_CATEGORY(LogManagerLog, "Utilities.LogManager", QtWarningMsg) static std std::atomic< bool > s_captureEnabled
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void flushOnLevelChanged()
void diskLoggingEnabledChanged()
void diskCompressionEnabledChanged()
bool flush(int timeoutMs=5000)
void fileSizeChanged(qint64 size)
void write(const QByteArray &data)
void setFilePath(const QString &path)
void errorOccurred(const QString &message)
static Level fromQtMsgType(QtMsgType type)