7#include <QtConcurrent/QtConcurrentRun>
8#include <QtCore/QGlobalStatic>
9#include <QtCore/QMutex>
10#include <QtCore/QMutexLocker>
11#include <QtCore/QStringListModel>
12#include <QtCore/QTextStream>
20static QtMessageHandler defaultHandler =
nullptr;
29static void msgHandler(QtMsgType type,
const QMessageLogContext &context,
const QString &msg)
35 defaultHandler(type, context, msg);
39 const QString message = qFormatLogMessage(type, context, msg);
56 : QStringListModel(parent)
58 qCDebug(QGCLoggingLog) <<
this;
60 _flushTimer.setInterval(kFlushIntervalMSecs);
61 _flushTimer.setSingleShot(
false);
62 (void) connect(&_flushTimer, &QTimer::timeout,
this, &QGCLogging::_flushToDisk);
66#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
67 const Qt::ConnectionType conntype = Qt::QueuedConnection;
69 const Qt::ConnectionType conntype = Qt::AutoConnection;
76 qCDebug(QGCLoggingLog) <<
this;
82 qSetMessagePattern(QStringLiteral(
"%{time process}%{if-warning} Warning:%{endif}%{if-critical} Critical:%{endif} %{message} - %{category} - (%{function}:%{line})"));
85 defaultHandler = qInstallMessageHandler(
msgHandler);
107 if (category.isEmpty()) {
111 QList<CapturedLogMessage> filtered;
113 if (msg.category == category) {
114 filtered.append(msg);
125 if (msg.category == category && msg.type == type) {
147 if (msg.category.isEmpty() || msg.category == QStringLiteral(
"default")) {
162 context.category ? QString::fromLatin1(context.category) : QString(),
174void QGCLogging::_threadsafeLog(
const QString &message)
177 const int line = rowCount();
178 (void) QStringListModel::insertRows(line, 1);
179 (void) setData(index(line, 0), message, Qt::DisplayRole);
182 static constexpr const int kMaxLogRows = kMaxLogFileSize / 100;
183 if (rowCount() > kMaxLogRows) {
184 const int removeCount = rowCount() - kMaxLogRows;
185 beginRemoveRows(QModelIndex(), 0, removeCount - 1);
186 (void) removeRows(0, removeCount);
191 _pendingDiskWrites.append(message);
194void QGCLogging::_rotateLogs()
200 const QString basePath = _logFile.fileName();
201 const QFileInfo fileInfo(basePath);
202 const QString dir = fileInfo.absolutePath();
203 const QString name = fileInfo.baseName();
204 const QString ext = fileInfo.completeSuffix();
207 for (
int i = kMaxBackupFiles - 1; i >= 1; --i) {
208 const QString from = QStringLiteral(
"%1/%2.%3.%4").arg(dir, name).arg(i).arg(ext);
209 const QString to = QStringLiteral(
"%1/%2.%3.%4").arg(dir, name).arg(i+1).arg(ext);
210 if (QFile::exists(to)) {
211 (void) QFile::remove(to);
213 if (QFile::exists(from)) {
214 (void) QFile::rename(from, to);
219 const QString firstBackup = QStringLiteral(
"%1/%2.1.%3").arg(dir, name, ext);
220 if (QFile::exists(firstBackup)) {
221 (void) QFile::remove(firstBackup);
223 (void) QFile::rename(basePath, firstBackup);
226 _logFile.setFileName(basePath);
227 if (!_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
229 qgcApp()->showAppMessage(tr(
"Unable to reopen log file %1: %2").arg(_logFile.fileName(), _logFile.errorString()));
233void QGCLogging::_flushToDisk()
235 if (_pendingDiskWrites.isEmpty() || _ioError) {
240 if (!_logFile.isOpen()) {
241 if (!
qgcApp()->logOutput()) {
242 _pendingDiskWrites.clear();
246 const QString saveDirPath = SettingsManager::instance()->appSettings()->
crashSavePath();
247 const QDir saveDir(saveDirPath);
248 const QString saveFilePath = saveDir.absoluteFilePath(
"QGCConsole.log");
250 _logFile.setFileName(saveFilePath);
251 if (!_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
253 qgcApp()->showAppMessage(tr(
"Open console log output file failed %1 : %2").arg(_logFile.fileName(), _logFile.errorString()));
259 if (_logFile.size() >= kMaxLogFileSize) {
264 QTextStream out(&_logFile);
265 for (
const QString &line : std::as_const(_pendingDiskWrites)) {
267 if (out.status() != QTextStream::Ok) {
269 qCWarning(QGCLoggingLog) <<
"Error writing to log file:" << _logFile.errorString();
273 (void) _logFile.flush();
274 _pendingDiskWrites.clear();
280 const QStringList logs = stringList();
283 (void) QtConcurrent::run([
this, destFile, logs]() {
285 bool success =
false;
286 QSaveFile file(destFile);
287 if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
288 QTextStream out(&file);
289 for (
const QString &line : logs) {
292 success = ((out.status() == QTextStream::Ok) && file.commit());
294 qCWarning(QGCLoggingLog) <<
"write failed:" << file.errorString();
Q_GLOBAL_STATIC(FirmwarePluginFactoryRegister, _firmwarePluginFactoryRegisterInstance)
#define QGC_LOGGING_CATEGORY(name, categoryStr)
static std::atomic< bool > s_captureEnabled
static void msgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
static QList< CapturedLogMessage > s_capturedMessages
static QMutex s_captureMutex
static void setCaptureEnabled(bool enabled)
static bool hasCapturedMessage(const QString &category, QtMsgType type)
void log(const QString &message)
Enqueue a log message (thread-safe)
void writeMessages(const QString &destFile)
Write current log messages to a file asynchronously.
static bool hasCapturedCritical(const QString &category)
Convenience: captured critical in category?
static QList< CapturedLogMessage > capturedMessages(const QString &category={})
QGCLogging(QObject *parent=nullptr)
static QGCLogging * instance()
Get the singleton instance.
static void installHandler()
Install Qt message handler to route logs through this class.
static void clearCapturedMessages()
Discard all previously captured messages.
void writeFinished(bool success)
Emitted when file write finishes (success flag)
static void captureIfEnabled(QtMsgType type, const QMessageLogContext &context, const QString &msg)
void emitLog(const QString &message)
Emitted when a log message is enqueued.
static bool hasCapturedWarning(const QString &category)
Convenience: captured warning in category?
void writeStarted()
Emitted when file write starts.
static bool hasCapturedUncategorizedMessage()
Return true if any uncategorized message was captured (e.g. raw qDebug/qWarning).