diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -21,6 +21,7 @@ kscreen_add_test(testconfigmonitor) kscreen_add_test(testinprocess) kscreen_add_test(testbackendloader) +kscreen_add_test(testlog) set(KSCREEN_WAYLAND_LIBS KF5::WaylandServer KF5::WaylandClient diff --git a/autotests/testbackendloader.cpp b/autotests/testbackendloader.cpp --- a/autotests/testbackendloader.cpp +++ b/autotests/testbackendloader.cpp @@ -47,6 +47,7 @@ TestBackendLoader::TestBackendLoader(QObject *parent) : QObject(parent) { + qputenv("KSCREEN_LOGGING", "false"); qputenv("KSCREEN_BACKEND_INPROCESS", QByteArray()); qputenv("KSCREEN_BACKEND", QByteArray()); } diff --git a/autotests/testconfigmonitor.cpp b/autotests/testconfigmonitor.cpp --- a/autotests/testconfigmonitor.cpp +++ b/autotests/testconfigmonitor.cpp @@ -55,6 +55,7 @@ private Q_SLOTS: void initTestCase() { + qputenv("KSCREEN_LOGGING", "false"); qputenv("KSCREEN_BACKEND", "Fake"); // This particular test is only useful for out of process operation, so enforce that qputenv("KSCREEN_BACKEND_INPROCESS", "0"); diff --git a/autotests/testinprocess.cpp b/autotests/testinprocess.cpp --- a/autotests/testinprocess.cpp +++ b/autotests/testinprocess.cpp @@ -68,6 +68,7 @@ void TestInProcess::init() { + qputenv("KSCREEN_LOGGING", "false"); // Make sure we do everything in-process qputenv("KSCREEN_BACKEND_INPROCESS", "1"); // Use Fake backend with one of the json configs diff --git a/autotests/testkwaylandbackend.cpp b/autotests/testkwaylandbackend.cpp --- a/autotests/testkwaylandbackend.cpp +++ b/autotests/testkwaylandbackend.cpp @@ -74,6 +74,7 @@ : QObject(parent) , m_config(nullptr) { + qputenv("KSCREEN_LOGGING", "false"); m_server = new WaylandTestServer(this); m_server->setConfig(TEST_DATA + QStringLiteral("multipleoutput.json")); } diff --git a/autotests/testkwaylandconfig.cpp b/autotests/testkwaylandconfig.cpp --- a/autotests/testkwaylandconfig.cpp +++ b/autotests/testkwaylandconfig.cpp @@ -63,6 +63,7 @@ : QObject(parent) , m_server(nullptr) { + qputenv("KSCREEN_LOGGING", "false"); } void TestKWaylandConfig::initTestCase() diff --git a/autotests/testlog.cpp b/autotests/testlog.cpp --- a/autotests/testlog.cpp +++ b/autotests/testlog.cpp @@ -38,6 +38,7 @@ void init(); void initTestCase(); void cleanupTestCase(); + void testContext(); void testEnabled(); void testLogFile(); void testLog(); @@ -63,7 +64,17 @@ { qunsetenv(KSCREEN_LOGGING); qunsetenv(KSCREEN_LOGFILE); +} + +void TestLog::testContext() +{ + auto log = new KScreen::Log; + QString ctx("context text"); + QVERIFY(log != nullptr); + log->setContext(ctx); + QCOMPARE(log->context(), ctx); + delete log; } void TestLog::testEnabled() @@ -114,6 +125,10 @@ QVERIFY(lf.exists()); + // TODO read log and match logmsg + + QVERIFY(lf.remove()); + } diff --git a/autotests/testqscreenbackend.cpp b/autotests/testqscreenbackend.cpp --- a/autotests/testqscreenbackend.cpp +++ b/autotests/testqscreenbackend.cpp @@ -54,6 +54,7 @@ void testQScreenBackend::initTestCase() { + qputenv("KSCREEN_LOGGING", "false"); qputenv("KSCREEN_BACKEND", "qscreen"); qputenv("KSCREEN_BACKEND_INPROCESS", "1"); KScreen::BackendManager::instance()->shutdownBackend(); diff --git a/autotests/testscreenconfig.cpp b/autotests/testscreenconfig.cpp --- a/autotests/testscreenconfig.cpp +++ b/autotests/testscreenconfig.cpp @@ -64,6 +64,7 @@ void testScreenConfig::initTestCase() { + qputenv("KSCREEN_LOGGING", "false"); qputenv("KSCREEN_BACKEND", "Fake"); } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,6 +16,7 @@ edid.cpp mode.cpp debug_p.cpp + log.cpp ) qt5_add_dbus_interface(libkscreen_SRCS ${CMAKE_SOURCE_DIR}/interfaces/org.kde.KScreen.Backend.xml backendinterface) @@ -43,6 +44,7 @@ ecm_generate_headers(KScreen_HEADERS HEADER_NAMES + Log Mode Output EDID diff --git a/src/backendlauncher/main.cpp b/src/backendlauncher/main.cpp --- a/src/backendlauncher/main.cpp +++ b/src/backendlauncher/main.cpp @@ -22,9 +22,11 @@ #include "debug_p.h" #include "backendloader.h" +#include "log.h" int main(int argc, char **argv) { + KScreen::Log::instance(); QGuiApplication::setDesktopSettingsAware(false); QGuiApplication app(argc, argv); diff --git a/src/backendmanager.cpp b/src/backendmanager.cpp --- a/src/backendmanager.cpp +++ b/src/backendmanager.cpp @@ -28,6 +28,7 @@ #include "debug_p.h" #include "getconfigoperation.h" #include "configserializer_p.h" +#include "log.h" #include #include @@ -68,6 +69,7 @@ , mLoader(0) , mMethod(OutOfProcess) { + Log::instance(); // Decide wether to run in, or out-of-process // if KSCREEN_BACKEND_INPROCESS is set explicitely, we respect that diff --git a/src/getconfigoperation.cpp b/src/getconfigoperation.cpp --- a/src/getconfigoperation.cpp +++ b/src/getconfigoperation.cpp @@ -21,6 +21,7 @@ #include "configoperation_p.h" #include "config.h" #include "output.h" +#include "log.h" #include "backendmanager_p.h" #include "configserializer_p.h" #include "backendinterface.h" diff --git a/src/log.h b/src/log.h new file mode 100644 --- /dev/null +++ b/src/log.h @@ -0,0 +1,117 @@ +/************************************************************************************* + * Copyright 2016 by Sebastian Kügler * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * + *************************************************************************************/ + +#ifndef KSCREEN_LOG_H +#define KSCREEN_LOG_H + +#include "kscreen_export.h" +#include "types.h" + +#include +#include + +namespace KScreen { + +void log(const QString& msg); + +/** KScreen-internal file logging. + * + * The purpose of this class is to allow better debugging of kscreen. QDebug falls short here, since + * we need to debug the concert of kscreen components from different processes. + * + * KScreen::Log manages access to kscreen's log file. + * + * The following environment variables are considered: + * - disable logging by setting + * KSCREEN_LOGGING=false + * - set the log file to a custom path, the default is in ~/.local/share/kscreen/kscreen.log + * + * Please do not translate messages written to the logs, it's developer information and should be + * english, independent from the user's locale preferences. + * + * @code + * + * Log::instance()->setContext("resume"); + * Log::log("Applying detected output configuration."); + * + * @endcode + * + * @since 5.8 + */ +class KSCREEN_EXPORT Log +{ + public: + explicit Log(); + virtual ~Log(); + + static Log* instance(); + + /** Log a message to a file + * + * Call this static method to add a new line to the log. + * + * @arg msg The log message to write. + */ + static void log(const QString &msg, const QString &category = QString()); + + /** Context for the logs. + * + * The context can be used to indicate what is going on overall, it is used to be able + * to group log entries into subsequential operations. For example the context can be + * "handling resume", which is then added to the log messages. + * + * @arg msg The log message to write to the file. + * + * @see ontext() + */ + QString context() const; + + /** Set the context for the logs. + * + * @see context() + */ + void setContext(const QString &context); + + /** Logging to file is enabled by environmental var, is it? + * + * @arg msg The log message to write to the file. + * @return Whether logging is enabled. + */ + bool enabled() const; + + /** Path to the log file + * + * This is usually ~/.local/share/kscreen/kscreen.log, but can be changed by setting + * KSCREEN_LOGFILE in the environment. + * + * @return The path to the log file. + */ + QString logFile() const; + + private: + class Private; + Private * const d; + + static Log* sInstance; + Log(Private *dd); +}; + +} //KSCreen namespace + + +#endif //KSCREEN_LOG_H diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,145 @@ +/************************************************************************************* + * Copyright 2016 by Sebastian Kügler * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * + *************************************************************************************/ + +#include "log.h" + +#include +#include +#include +#include +#include + +namespace KScreen { + +Log* Log::sInstance = nullptr; +QtMessageHandler sDefaultMessageHandler = nullptr; + +void kscreenLogOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + QByteArray localMsg = msg.toLocal8Bit(); + if (QString::fromLocal8Bit(context.category).startsWith(QLatin1String("kscreen"))) { + Log::log(localMsg.constData(), context.category); + } + sDefaultMessageHandler(type, context, msg); +} + +void log(const QString& msg) +{ + Log::log(msg); +} + +Log* Log::instance() +{ + if (!sInstance) { + sInstance = new Log(); + } + + return sInstance; +} + +using namespace KScreen; +class Log::Private +{ + public: + QString context; + bool enabled = true; + QString logFile; + +}; + +Log::Log() : + d(new Private) +{ + const char* logging_env = "KSCREEN_LOGGING"; + const char* logfile_env = "KSCREEN_LOGFILE"; + + if (qEnvironmentVariableIsSet(logging_env)) { + const QString logging_env_value = qgetenv(logging_env).constData(); + if (logging_env_value == QStringLiteral("0") || logging_env_value.toLower() == QStringLiteral("false")) { + d->enabled = false; + } + } + if (qEnvironmentVariableIsSet(logfile_env)) { + const auto logfile_env_value = qgetenv(logfile_env).constData(); + // todo : checks path exists, writable + d->logFile = QString::fromLocal8Bit(logfile_env_value); + } else { + d->logFile = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/kscreen/kscreen.log"; + } + if (!d->enabled) { + return; + } + QLoggingCategory::setFilterRules("kscreen.*=true"); + // todo : create path if necessary + QFileInfo fi(d->logFile); + if (!QDir().mkpath(fi.absolutePath())) { + qWarning() << "Failed to create logging dir" << fi.absolutePath(); + } + + if (!sDefaultMessageHandler) { + sDefaultMessageHandler = qInstallMessageHandler(kscreenLogOutput); + qDebug() << "installed message handler, logging to " << d->logFile; + } +} + +Log::Log(Log::Private *dd) : + d(dd) +{ +} + +Log::~Log() +{ + delete d; + delete sInstance; + sInstance = nullptr; +} + +QString Log::context() const +{ + return d->context; +} + +void Log::setContext(const QString& context) +{ + d->context = context; +} + +bool Log::enabled() const +{ + return d->enabled; +} + +QString Log::logFile() const +{ + return d->logFile; +} + +void Log::log(const QString &msg, const QString &category) +{ + auto _cat = category; + _cat.remove("kscreen."); + const QString timestamp = QString::number(QDateTime::currentDateTime().toMSecsSinceEpoch()); + QString logMessage = QString("\n%1 ; %2 ; %3 : %4").arg(timestamp, _cat, instance()->context(), msg); + QFile file(instance()->logFile()); + if (!file.open(QIODevice::Append | QIODevice::Text)) { + return; + } + file.write(logMessage.toUtf8()); +} + +} // ns