3#include <QtConcurrent/QtConcurrentRun>
4#include <QtCore/QCoreApplication>
5#include <QtCore/QElapsedTimer>
6#include <QtCore/QMutex>
7#include <QtCore/QMutexLocker>
8#include <QtCore/QPointer>
9#include <QtCore/QSaveFile>
10#include <QtCore/QThread>
11#include <QtQml/QJSEngine>
37static QElapsedTimer
s_elapsedTimer = []() { QElapsedTimer t; t.start();
return t; }();
46void LogManager::msgHandler(QtMsgType type,
const QMessageLogContext& context,
const QString& msg)
48 auto* inst =
s_instance.load(std::memory_order_acquire);
54 const QByteArray line = qFormatLogMessage(type, context, msg).toLocal8Bit();
55 std::fprintf(stderr,
"%s\n", line.constData());
64 if (context.category && std::strncmp(context.category,
"qt.quick", 8) == 0) {
69 inst->log(type, context, msg);
78 return s_instance.load(std::memory_order_acquire);
86 QJSEngine::setObjectOwnership(inst, QJSEngine::CppOwnership);
90LogManager::LogManager(QObject* parent) : QObject(parent)
92 s_instance.store(
this, std::memory_order_release);
98 if (size >= _maxLogFileSize) {
103 _flushTimer.setInterval(kFlushIntervalMSecs);
104 _flushTimer.setSingleShot(
false);
105 (void)connect(&_flushTimer, &QTimer::timeout,
this, &LogManager::_flushToDisk);
112 if (
s_instance.load(std::memory_order_relaxed) ==
this) {
113 s_instance.store(
nullptr, std::memory_order_release);
117 QCoreApplication::processEvents();
121 _fileWriter->
close();
123 if (_exportFuture.isValid()) {
124 _exportFuture.waitForFinished();
130 Q_ASSERT(!
s_instance.load(std::memory_order_relaxed));
136 QStringLiteral(
"%{time process}%{if-warning} Warning:%{endif}%{if-critical} Critical:%{endif} %{message} - "
137 "%{category} - (%{function}:%{line})"));
143 const QByteArray env = qgetenv(
"QGC_LOG_LEVEL");
148 const QString level = QString::fromUtf8(env).toLower().trimmed();
151 if (level == QStringLiteral(
"trace") || level == QStringLiteral(
"debug")) {
152 rules = QStringLiteral(
"*.debug=true\n");
153 }
else if (level == QStringLiteral(
"info")) {
154 rules = QStringLiteral(
"*.debug=false\n*.info=true\n");
155 }
else if (level == QStringLiteral(
"warning") || level == QStringLiteral(
"warn")) {
156 rules = QStringLiteral(
"*.debug=false\n*.info=false\n*.warning=true\n");
157 }
else if (level == QStringLiteral(
"critical") || level == QStringLiteral(
"error")) {
158 rules = QStringLiteral(
"*.debug=false\n*.info=false\n*.warning=false\n*.critical=true\n");
159 }
else if (level == QStringLiteral(
"off") || level == QStringLiteral(
"none")) {
160 rules = QStringLiteral(
"*.debug=false\n*.info=false\n*.warning=false\n*.critical=false\n");
162 qWarning(
"QGC_LOG_LEVEL: unknown level '%s' (use debug/info/warning/critical/off)", env.constData());
166 QLoggingCategory::setFilterRules(rules);
185 _setDiskLoggingEnabled(logSettings->diskLoggingEnabled()->rawValue().toBool());
186 (void)connect(logSettings->diskLoggingEnabled(), &
Fact::rawValueChanged,
this, [
this, logSettings]() {
187 _setDiskLoggingEnabled(logSettings->diskLoggingEnabled()->rawValue().toBool());
191 _maxLogFileSize = logSettings->diskLoggingMaxFileSizeMB()->rawValue().toInt() * 1024 * 1024;
192 (void)connect(logSettings->diskLoggingMaxFileSizeMB(), &
Fact::rawValueChanged,
this, [
this, logSettings]() {
193 _maxLogFileSize = logSettings->diskLoggingMaxFileSizeMB()->rawValue().toInt() * 1024 * 1024;
196 _maxBackupFiles = logSettings->diskLoggingMaxBackupFiles()->rawValue().toInt();
197 (void)connect(logSettings->diskLoggingMaxBackupFiles(), &
Fact::rawValueChanged,
this, [
this, logSettings]() {
198 _maxBackupFiles = logSettings->diskLoggingMaxBackupFiles()->rawValue().toInt();
202 (void)connect(appSettings->showAppLogTimestampAsElapsedTime(), &
Fact::rawValueChanged, _model, [
this]() {
203 const int rows = _model->
rowCount();
206 emit _model->dataChanged(_model->index(0, col), _model->index(rows - 1, col), {Qt::DisplayRole});
210 _replayEarlyEntries();
218void LogManager::_replayEarlyEntries()
221 for (
const auto& entry : earlyEntries) {
222 if (_diskLoggingEnabled) {
223 _pendingDiskWrites.append(entry);
226 if (_diskLoggingEnabled && !_pendingDiskWrites.isEmpty()) {
235void LogManager::log(QtMsgType type,
const QMessageLogContext& context,
const QString& message)
237 LogEntry entry = buildEntry(type, context, message);
239 QMetaObject::invokeMethod(
241 [
this, entry = std::move(entry)]()
mutable {
245 Qt::QueuedConnection);
248const QString& LogManager::_internCategory(
const QString& category)
250 auto it = _internedCategories.find(category);
251 if (it != _internedCategories.end()) {
254 return *_internedCategories.insert(category);
257void LogManager::_dispatchToSinks(
const LogEntry& entry)
260 if (_diskLoggingEnabled) {
261 _pendingDiskWrites.append(entry);
265void LogManager::_handleEntry(
const LogEntry& entry)
267 if (_rateLimitingEnabled && !_rateLimitCheck(entry)) {
271 _dispatchToSinks(entry);
274bool LogManager::_rateLimitCheck(
const LogEntry& entry)
280 const qint64 now = QDateTime::currentMSecsSinceEpoch();
281 auto& bucket = _rateBuckets[entry.
category];
283 if (bucket.lastRefillMs == 0) {
284 bucket.lastRefillMs = now;
285 bucket.tokens = kRateMaxTokens;
288 const qint64 elapsed = now - bucket.lastRefillMs;
290 const int refill =
static_cast<int>(elapsed * kRateTokensPerSecond / 1000);
292 bucket.tokens = qMin(bucket.tokens + refill, kRateMaxTokens);
293 bucket.lastRefillMs = now;
295 if (bucket.suppressed > 0 && bucket.tokens > 0) {
296 _emitSuppressedSummary(entry.
category, bucket.suppressed);
297 bucket.suppressed = 0;
302 if (bucket.tokens > 0) {
311void LogManager::_emitSuppressedSummary(
const QString& category,
int count)
314 summary.
timestamp = QDateTime::currentDateTime();
317 summary.
message = QStringLiteral(
"... %1 messages suppressed (rate limited)").arg(count);
320 _dispatchToSinks(summary);
339 Q_ASSERT(QThread::currentThread() == thread());
341 _fileWriter->
flush();
344void LogManager::_setDiskLoggingEnabled(
bool enabled)
346 if (_diskLoggingEnabled != enabled) {
349 _fileWriter->
close();
351 _diskLoggingEnabled = enabled;
359void LogManager::_setIoError(
const QString& message)
362 _lastError = message;
369 if (_logDirectory == path) {
372 _logDirectory = path;
374 if (path.isEmpty()) {
379 const QDir dir(path);
380 _fileWriter->
setFilePath(dir.absoluteFilePath(QStringLiteral(
"AppLog.log")));
383void LogManager::_rotateLogs()
385 _fileWriter->
flush();
386 _fileWriter->
close();
388 const QString path = _fileWriter->
filePath();
389 const QFileInfo fileInfo(path);
390 const QString dir = fileInfo.absolutePath();
391 const QString name = fileInfo.baseName();
392 const QString ext = fileInfo.completeSuffix();
394 for (
int i = _maxBackupFiles - 1; i >= 1; --i) {
395 const QString from = QStringLiteral(
"%1/%2.%3.%4").arg(dir, name).arg(i).arg(ext);
396 const QString to = QStringLiteral(
"%1/%2.%3.%4").arg(dir, name).arg(i + 1).arg(ext);
397 if (QFile::exists(to)) {
398 (void)QFile::remove(to);
400 if (QFile::exists(from)) {
401 (void)QFile::rename(from, to);
405 const QString firstBackup = QStringLiteral(
"%1/%2.1.%3").arg(dir, name, ext);
406 (void)QFile::rename(path, firstBackup);
411void LogManager::_flushToDisk()
413 if (_pendingDiskWrites.isEmpty() || _ioError || !_diskLoggingEnabled || _logDirectory.isEmpty()) {
417 auto entries = std::move(_pendingDiskWrites);
430void LogManager::_exportEntries(QList<LogEntry> entries,
const QString& destFile)
434 QPointer<LogManager> guard(
this);
435 _exportFuture = QtConcurrent::run([guard, destFile, entries = std::move(entries)]() {
436 bool success =
false;
437 QSaveFile file(destFile);
438 if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
439 const int fmt = destFile.endsWith(QStringLiteral(
".csv"), Qt::CaseInsensitive)
443 success = file.commit();
445 qCWarning(LogManagerLog) <<
"write failed:" << file.errorString();
448 QMetaObject::invokeMethod(
452 emit guard->writeFinished(success);
455 Qt::QueuedConnection);
479 if (category.isEmpty()) {
483 QList<LogEntry> filtered;
485 if (msg.category == category) {
486 filtered.append(msg);
496 if (msg.category == category && msg.level == level) {
517 if (msg.category.isEmpty() || msg.category == QStringLiteral(
"default")) {
530 LogEntry entry = buildEntry(type, context, msg);
536LogEntry LogManager::buildEntry(QtMsgType type,
const QMessageLogContext& context,
const QString& message)
540 entry.
timestamp = QDateTime::currentDateTime();
542 entry.
category = context.category ? QString::fromLatin1(context.category) : QString();
545 const QString fullPath = QString::fromLatin1(context.file);
546 const int lastSlash = fullPath.lastIndexOf(QLatin1Char(
'/'));
547 entry.
file = (lastSlash >= 0) ? fullPath.mid(lastSlash + 1) : fullPath;
549 entry.
function = context.function ? QString::fromLatin1(context.function) : QString();
550 entry.
line = context.line;
551 entry.
threadId = QThread::currentThreadId();
static std::atomic< bool > s_echoToStderr
static std::atomic< bool > s_captureEnabled
static QtMessageHandler s_defaultHandler
static QMutex s_captureMutex
static QList< LogEntry > s_capturedMessages
static std::atomic< LogManager * > s_instance
static QElapsedTimer s_elapsedTimer
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void rawValueChanged(const QVariant &value)
static void installHandler(bool logOutput)
static void applyEnvironmentLogLevel()
Q_INVOKABLE void clearError()
static bool hasCapturedMessage(const QString &category, LogEntry::Level level)
static void clearCapturedMessages()
static bool hasCapturedCritical(const QString &category)
static bool hasCapturedWarning(const QString &category)
Q_INVOKABLE void writeMessages(const QString &destFile)
static LogManager * create(QQmlEngine *qmlEngine, QJSEngine *jsEngine)
static bool hasCapturedUncategorizedMessage()
static QList< LogEntry > capturedMessages(const QString &category={})
void setLogDirectory(const QString &path)
static LogManager * instance()
static void captureIfEnabled(QtMsgType type, const QMessageLogContext &context, const QString &msg)
static void setCaptureEnabled(bool enabled)
void enqueue(LogEntry entry)
QList< LogEntry > allEntriesSnapshot() const
int rowCount(const QModelIndex &parent=QModelIndex()) const override
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 SettingsManager * instance()
LogManagerSettings * logManagerSettings() const
AppSettings * appSettings() const
static Level fromQtMsgType(QtMsgType type)