diff --git a/nymea-app/applogcontroller.cpp b/nymea-app/applogcontroller.cpp new file mode 100644 index 00000000..34acbd13 --- /dev/null +++ b/nymea-app/applogcontroller.cpp @@ -0,0 +1,110 @@ +#include "applogcontroller.h" + +#include +#include +#include + +QtMessageHandler AppLogController::s_oldLogMessageHandler = nullptr; + + +QObject *AppLogController::appLogControllerProvider(QQmlEngine *engine, QJSEngine *scriptEngine) +{ + Q_UNUSED(engine) + Q_UNUSED(scriptEngine) + return instance(); +} + +AppLogController *AppLogController::instance() +{ + static AppLogController* thiz = nullptr; + if (!thiz) { + thiz = new AppLogController(); + } + return thiz; +} + +AppLogController::AppLogController(QObject *parent) : QObject(parent) +{ + + QString fileName = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/nymea-app.log"; + m_logFile.setFileName(fileName); + qDebug() << "App log file:" << fileName; + + if (!m_logFile.open(QFile::ReadWrite)) { + qDebug() << "Cannot open logfile for writing"; + return; + } + + qDebug() << "Logging is" << (enabled() ? "enabled" : "disabled"); + if (enabled()) { + activate(); + } +} + +bool AppLogController::canWriteLogs() const +{ + return m_logFile.isOpen(); +} + +bool AppLogController::enabled() const +{ + QSettings settings; + return settings.value("AppLoggingEnabled", false).toBool(); +} + +void AppLogController::setEnabled(bool enabled) +{ + if (enabled == this->enabled()) { + return; + } + + if (enabled) { + if (!canWriteLogs()) { + qWarning() << "Cannot write log file. Not enabling logging."; + return; + } + activate(); + } else { + deactivate(); + } + QSettings settings; + settings.setValue("AppLoggingEnabled", enabled); + + emit enabledChanged(); + +} + +QByteArray AppLogController::content() +{ + return m_buffer; +} + +void AppLogController::logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message) +{ + s_oldLogMessageHandler(type, context, message); + + QString finalMessage = message + "\n"; + instance()->m_logFile.write(finalMessage.toUtf8()); + instance()->m_logFile.flush(); + instance()->m_buffer.append(finalMessage); + + int maxBuffer = 1024 * 1024; + if (instance()->m_buffer.size() > maxBuffer) { + instance()->m_buffer.remove(0, instance()->m_buffer.size() - maxBuffer); + } + emit instance()->contentChanged(); + emit instance()->contentAdded(finalMessage.toUtf8()); +} + +void AppLogController::activate() +{ + qDebug() << "Activating log file writing"; + s_oldLogMessageHandler = qInstallMessageHandler(&logMessageHandler); +} + +void AppLogController::deactivate() +{ + qInstallMessageHandler(s_oldLogMessageHandler); + s_oldLogMessageHandler = nullptr; + +} diff --git a/nymea-app/applogcontroller.h b/nymea-app/applogcontroller.h new file mode 100644 index 00000000..e998a697 --- /dev/null +++ b/nymea-app/applogcontroller.h @@ -0,0 +1,43 @@ +#ifndef APPLOGCONTROLLER_H +#define APPLOGCONTROLLER_H + +#include +#include +#include + +class AppLogController : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool canWriteLogs READ canWriteLogs CONSTANT) + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(QByteArray content READ content NOTIFY contentChanged) + +public: + static QObject* appLogControllerProvider(QQmlEngine *engine, QJSEngine *scriptEngine); + static AppLogController* instance(); + + bool canWriteLogs() const; + + bool enabled() const; + void setEnabled(bool enabled); + + QByteArray content(); + +signals: + void enabledChanged(); + void contentChanged(); + void contentAdded(const QByteArray &newContent); + +private: + explicit AppLogController(QObject *parent = nullptr); + static QtMessageHandler s_oldLogMessageHandler; + static void logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message); + + void activate(); + void deactivate(); + + QFile m_logFile; + QByteArray m_buffer; +}; + +#endif // APPLOGCONTROLLER_H diff --git a/nymea-app/main.cpp b/nymea-app/main.cpp index a3814403..54ff0894 100644 --- a/nymea-app/main.cpp +++ b/nymea-app/main.cpp @@ -38,6 +38,7 @@ #include "stylecontroller.h" #include "pushnotifications.h" +#include "applogcontroller.h" QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine) @@ -68,6 +69,9 @@ int main(int argc, char *argv[]) application.setApplicationName("nymea-app"); application.setOrganizationName("nymea"); + // Initialize app log controller as early as possible, but after setting app name etc + AppLogController::instance(); + foreach (const QFileInfo &fi, QDir(":/ui/fonts/").entryInfoList()) { QFontDatabase::addApplicationFont(fi.absoluteFilePath()); } @@ -106,6 +110,8 @@ int main(int argc, char *argv[]) PushNotifications::instance()->connectClient(); qmlRegisterSingletonType("Nymea", 1, 0, "PushNotifications", PushNotifications::pushNotificationsProvider); + qmlRegisterSingletonType("Nymea", 1, 0, "AppLogController", AppLogController::appLogControllerProvider); + #ifdef BRANDING engine->rootContext()->setContextProperty("appBranding", BRANDING); #else diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index 3b750ec6..617f9a48 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -21,12 +21,14 @@ HEADERS += \ pushnotifications.h \ platformhelper.h \ platformintegration/generic/platformhelpergeneric.h \ + applogcontroller.h SOURCES += main.cpp \ stylecontroller.cpp \ pushnotifications.cpp \ platformhelper.cpp \ platformintegration/generic/platformhelpergeneric.cpp \ + applogcontroller.cpp RESOURCES += resources.qrc \ ruletemplates.qrc \ diff --git a/nymea-app/ui/appsettings/DeveloperOptionsPage.qml b/nymea-app/ui/appsettings/DeveloperOptionsPage.qml index c167d2b4..0c5eb735 100644 --- a/nymea-app/ui/appsettings/DeveloperOptionsPage.qml +++ b/nymea-app/ui/appsettings/DeveloperOptionsPage.qml @@ -31,5 +31,52 @@ Page { } } } + + CheckDelegate { + text: qsTr("Enable app logging") + enabled: AppLogController.canWriteLogs + checked: AppLogController.enabled + onCheckedChanged: AppLogController.enabled = checked; + Layout.fillWidth: true + } + + MeaListItemDelegate { + Layout.fillWidth: true + text: qsTr("View log") + onClicked: pageStack.push(appLogComponent) + enabled: AppLogController.enabled + } + } + + Component { + id: appLogComponent + Page { + header: GuhHeader { + text: qsTr("App log") + backButtonVisible: true + onBackPressed: pageStack.pop() + } + + ScrollView { + anchors.fill: parent + + TextArea { + id: logArea + wrapMode: Text.WordWrap + readOnly: true + font.pixelSize: app.smallFont + + Component.onCompleted: { + text = AppLogController.content + } + Connections { + target: AppLogController + onContentAdded: { + logArea.append(newContent) + } + } + } + } + } } }