diff --git a/src/framework/CMakeLists.txt b/src/framework/CMakeLists.txt index 7ab2d83..ef1d4ff 100644 --- a/src/framework/CMakeLists.txt +++ b/src/framework/CMakeLists.txt @@ -46,6 +46,7 @@ set(PRIVATE_HEADERS set(PROJECT_SOURCES ${PROJECT_SOURCE_DIR}/updater.cpp ${PROJECT_SOURCE_DIR}/sentryreporter.cpp + ${PROJECT_SOURCE_DIR}/logger.cpp ${PROJECT_SOURCE_DIR}/application.cpp ${PROJECT_SOURCE_DIR}/mainwindow.cpp ${PROJECT_SOURCE_DIR}/style.cpp @@ -53,7 +54,10 @@ set(PROJECT_SOURCES ${PROJECT_SOURCE_DIR}/navigationwidget.cpp ${PROJECT_SOURCE_DIR}/navigationpane.cpp ${PROJECT_SOURCE_DIR}/styledbar.cpp - ${PROJECT_SOURCE_DIR}/logger.cpp + ${PROJECT_SOURCE_DIR}/logger/baselogger.cpp + ${PROJECT_SOURCE_DIR}/logger/consolelogger.cpp + ${PROJECT_SOURCE_DIR}/logger/loggerdecorator.cpp + ${PROJECT_SOURCE_DIR}/logger/sentrylogger.cpp ) set(ACCESS_HHEADERS @@ -65,6 +69,10 @@ set(ACCESS_HHEADERS set(PRIVATE_HEADERS ${PRIVATE_HEADERS} ${PROJECT_SOURCE_DIR}/access/signserver.h ${PROJECT_SOURCE_DIR}/sentryreporter.h + ${PROJECT_SOURCE_DIR}/logger/baselogger.h + ${PROJECT_SOURCE_DIR}/logger/consolelogger.h + ${PROJECT_SOURCE_DIR}/logger/loggerdecorator.h + ${PROJECT_SOURCE_DIR}/logger/sentrylogger.h ${PROJECT_SOURCE_DIR}/logger.h ) diff --git a/src/framework/access/access.cpp b/src/framework/access/access.cpp index 2e37fde..a77775f 100644 --- a/src/framework/access/access.cpp +++ b/src/framework/access/access.cpp @@ -46,6 +46,7 @@ #include "request.h" #include "signserver.h" +#include "logger.h" #include "version.h" constexpr const char *apiEndpointSubpath = "/api/v1"; @@ -887,21 +888,27 @@ void NGAccess::updateSupportInfo() const void NGAccess::logMessage(const QString &value, LogLevel level) { - // Unknown levels will be info - SentryReporter::Level slevel = SentryReporter::Level::Info; - if(level == LogLevel::Warning) { - slevel = SentryReporter::Level::Warning; + auto &logger = getLogger(); + const auto payload = QStringLiteral("[NGAccess] %1").arg(value); + + switch (level) + { + case LogLevel::Debug: + logger.debug(payload); + break; + case LogLevel::Info: + logger.info(payload); + break; + case LogLevel::Warning: + logger.warning(payload); + break; + case LogLevel::Error: + logger.critical(payload); + break; + case LogLevel::Fatal: + logger.critical(QStringLiteral("[NGAccess] FATAL %1").arg(value)); + break; } - else if(level == LogLevel::Error) { - slevel = SentryReporter::Level::Error; - } - else if(level == LogLevel::Fatal) { - slevel = SentryReporter::Level::Fatal; - } - else if(level == LogLevel::Debug) { - slevel = SentryReporter::Level::Debug; - } - SentryReporter::instance().sendMessage(value, slevel); } SignInEvent::SignInEvent(QObject *parent) : QObject(parent) diff --git a/src/framework/access/signserver.cpp b/src/framework/access/signserver.cpp index a096909..d241f23 100644 --- a/src/framework/access/signserver.cpp +++ b/src/framework/access/signserver.cpp @@ -60,6 +60,34 @@ constexpr const char *contentStr = "" constexpr unsigned short listenPort = 65020; constexpr const char *redirectUriStr = "http://127.0.0.1:65020"; +namespace +{ +void logAuth(const BaseLogger::LogLevel level, const QString &clientId, const QString &message, const bool flush = false) +{ + auto &logger = getLogger(); + const auto payload = QStringLiteral("Authorization [%1] %2").arg(clientId, message); + + switch (level) + { + case BaseLogger::LogLevel::Debug: + logger.debug(payload); + break; + case BaseLogger::LogLevel::Info: + logger.info(payload); + break; + case BaseLogger::LogLevel::Warning: + logger.warning(payload); + break; + case BaseLogger::LogLevel::Critical: + logger.critical(payload); + break; + } + + if (flush) + logger.flush(); +} +} + static std::string toHex(unsigned char *value, int size) { @@ -104,7 +132,6 @@ NGSignServer::NGSignServer(const QString &clientId, const QString &scope, m_redirectUri(redirectUriStr), m_clientId(clientId), m_scope(scope), - m_logger(new Logger(m_clientId, "Authorization Logging", this)), m_timer(new QTimer(this)) { setLabelText(tr("Please sign in\nvia the opened browser...")); @@ -118,34 +145,37 @@ NGSignServer::NGSignServer(const QString &clientId, const QString &scope, // Start listen server m_listenServer = new QTcpServer(this); bool result = m_listenServer->listen(QHostAddress::LocalHost, listenPort); - m_logger->add(QString("LISTEN result = %1, error = %2").arg(result ? "GOOD" : "BAD", m_listenServer->errorString())); + const auto listenMsg = QString("Listen result = %1, error = %2") + .arg(result ? "Success" : "Failed", m_listenServer->errorString()); + logAuth(result ? BaseLogger::LogLevel::Info : BaseLogger::LogLevel::Warning, + m_clientId, listenMsg); if (result) { connect(m_timer, &QTimer::timeout, this, [this]() { - m_logger->add("TIMEOUT"); - m_logger->send(); + logAuth(BaseLogger::LogLevel::Warning, m_clientId, + QStringLiteral("Timeout while waiting for authorization reply"), true); }); m_timer->start(30 * 1000); // 30 sec } else { - m_logger->send(); + getLogger().flush(); } connect(m_listenServer, SIGNAL(newConnection()), this, SLOT(onIncomingConnection())); connect(m_listenServer, &QTcpServer::acceptError, this, [this](QAbstractSocket::SocketError err) { m_timer->stop(); - m_logger->add(QString("ACCEPT_ERROR: %1").arg(QString::number(static_cast(err)))); - m_logger->send(); + logAuth(BaseLogger::LogLevel::Critical, m_clientId, + QString("Accept error: %1").arg(QString::number(static_cast(err))), true); }); } NGSignServer::~NGSignServer() { - m_logger->send(); + getLogger().flush(); m_listenServer->close(); } @@ -166,7 +196,8 @@ QString NGSignServer::verifier() const void NGSignServer::onIncomingConnection() { - m_logger->add("ON_INCOMING_CONNECTION"); + logAuth(BaseLogger::LogLevel::Info, m_clientId, + QStringLiteral("Incoming connection received")); QTcpSocket *socket = m_listenServer->nextPendingConnection(); connect(socket, SIGNAL(readyRead()), this, SLOT(onGetReply()), Qt::UniqueConnection); @@ -176,18 +207,19 @@ void NGSignServer::onIncomingConnection() void NGSignServer::onGetReply() { m_timer->stop(); - m_logger->add("ON_GET_REPLY"); + logAuth(BaseLogger::LogLevel::Info, m_clientId, + QStringLiteral("Processing authorization reply")); if (!m_listenServer->isListening()) { - m_logger->add("ON_GET_REPLY status = ERROR: server is not listening"); - m_logger->send(); + logAuth(BaseLogger::LogLevel::Warning, m_clientId, + QStringLiteral("Authorization server is not listening"), true); return; } QTcpSocket *socket = qobject_cast(sender()); if (!socket) { - m_logger->add("ON_GET_REPLY status = ERROR: socket is null"); - m_logger->send(); + logAuth(BaseLogger::LogLevel::Warning, m_clientId, + QStringLiteral("Authorization reply socket is null"), true); return; } socket->setSocketOption(QAbstractSocket::LowDelayOption, 1); @@ -244,7 +276,10 @@ void NGSignServer::onGetReply() socket->disconnectFromHost(); socket->deleteLater(); - m_logger->add(QString("ON_GET_REPLY status = %1, code = %2, error = %3").arg(result ? "GOOD" : "BAD", m_code, errorMsg)); + logAuth(result ? BaseLogger::LogLevel::Info : BaseLogger::LogLevel::Warning, + m_clientId, + QString("Authorization reply status = %1, code = %2, error = %3") + .arg(result ? "Success" : "Failed", m_code, errorMsg)); // Close dialog done(result); @@ -277,7 +312,10 @@ int NGSignServer::exec() #endif bool result = QDesktopServices::openUrl(url); - m_logger->add(QString("EXEC status = %1, url = %2").arg(result ? "GOOD" : "BAD", url.toDisplayString())); + logAuth(result ? BaseLogger::LogLevel::Info : BaseLogger::LogLevel::Warning, + m_clientId, + QString("Open authorization URL status = %1, url = %2") + .arg(result ? "Success" : "Failed", url.toDisplayString())); return QProgressDialog::exec(); } diff --git a/src/framework/access/signserver.h b/src/framework/access/signserver.h index acbce99..5ca5641 100644 --- a/src/framework/access/signserver.h +++ b/src/framework/access/signserver.h @@ -23,7 +23,6 @@ #include #include -class Logger; class QTimer; class Q_DECL_HIDDEN NGSignServer : public QProgressDialog @@ -51,7 +50,6 @@ private slots: QString m_redirectUri; QString m_clientId, m_scope, m_verifier; QTcpServer *m_listenServer; - Logger* m_logger = {}; QTimer* m_timer = {}; // QDialog interface diff --git a/src/framework/logger.cpp b/src/framework/logger.cpp index 8f32d8d..533b03c 100644 --- a/src/framework/logger.cpp +++ b/src/framework/logger.cpp @@ -19,20 +19,43 @@ ******************************************************************************/ #include "logger.h" -#include "sentryreporter.h" -Logger::Logger(const QString& id, const QString& title, QObject* parent) - : QObject(parent) - , m_message(QString("%1 %2").arg(title, id)) +#include + +#include "logger/consolelogger.h" +#include "logger/sentrylogger.h" + +namespace { +std::shared_ptr g_logger; + +void applyEnvironmentLogLevel(BaseLogger &logger) +{ + const auto envValue = qgetenv("NGSTD_LOGGING_LEVEL"); + if (envValue.isEmpty()) + return; + + logger.setLevel(QString::fromLocal8Bit(envValue)); +} } -void Logger::add(const QString& message) +BaseLogger &getLogger() { - m_message += QString("\n* \"%1\"").arg(message); + if (!g_logger) + { + auto consoleLogger = std::make_shared(); + g_logger = std::make_shared(consoleLogger); + applyEnvironmentLogLevel(*g_logger); + } + + return *g_logger; } -void Logger::send() +void setLogger(const std::shared_ptr &logger) { - SentryReporter::instance().sendMessage(m_message); + g_logger = logger; + + if (g_logger) + applyEnvironmentLogLevel(*g_logger); } + diff --git a/src/framework/logger.h b/src/framework/logger.h index e369d97..83df392 100644 --- a/src/framework/logger.h +++ b/src/framework/logger.h @@ -1,41 +1,10 @@ -/****************************************************************************** -* Project: NextGIS GIS libraries -* Purpose: Framework library -* Author: NextGIS -******************************************************************************* -* Copyright (C) 2012-2025 NextGIS, info@nextgis.ru -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 2 of the License, or -* (at your option) any later version. -* This program 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 General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -******************************************************************************/ - #ifndef LOGGER_H #define LOGGER_H -#include -#include -#include "framework.h" - -class Logger final : public QObject -{ -public: - Logger(const QString& id, const QString& title, QObject* parent = nullptr); - ~Logger() = default; - - void add(const QString& message); - void send(); +#include "logger/baselogger.h" +#include -private: - QString m_message; -}; +NGFRAMEWORK_EXPORT BaseLogger &getLogger(); +NGFRAMEWORK_EXPORT void setLogger(const std::shared_ptr &logger); -#endif LOGGER_H \ No newline at end of file +#endif // LOGGER_H \ No newline at end of file diff --git a/src/framework/logger/baselogger.cpp b/src/framework/logger/baselogger.cpp new file mode 100644 index 0000000..79f64ba --- /dev/null +++ b/src/framework/logger/baselogger.cpp @@ -0,0 +1,146 @@ +/****************************************************************************** +* Project: NextGIS GIS libraries +* Purpose: Framework library +* Author: NextGIS +******************************************************************************* +* Copyright (C) 2012-2025 NextGIS, info@nextgis.ru +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 of the License, or +* (at your option) any later version. +* This program 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +******************************************************************************/ + +#include "logger/baselogger.h" + +#include + +namespace +{ +BaseLogger::LogLevel parseLevel(const QString &value, bool *ok) +{ + const auto normalized = value.trimmed().toLower(); + auto level = BaseLogger::LogLevel::Warning; + auto parsed = true; + + if (normalized == QLatin1String("debug")) + level = BaseLogger::LogLevel::Debug; + else if (normalized == QLatin1String("info")) + level = BaseLogger::LogLevel::Info; + else if (normalized == QLatin1String("warning")) + level = BaseLogger::LogLevel::Warning; + else if (normalized == QLatin1String("critical")) + level = BaseLogger::LogLevel::Critical; + else + parsed = false; + + if (ok) + *ok = parsed; + + return level; +} + +QString levelToString(const BaseLogger::LogLevel level) +{ + switch (level) + { + case BaseLogger::LogLevel::Debug: + return QStringLiteral("DEBUG"); + case BaseLogger::LogLevel::Info: + return QStringLiteral("INFO"); + case BaseLogger::LogLevel::Warning: + return QStringLiteral("WARNING"); + case BaseLogger::LogLevel::Critical: + return QStringLiteral("CRITICAL"); + } + + return QStringLiteral("INFO"); +} +} + +BaseLogger::BaseLogger(QObject *parent) + : QObject(parent) + , m_level(LogLevel::Warning) +{ +} + +void BaseLogger::debug(const QString &msg) +{ + write(LogLevel::Debug, msg); +} + +void BaseLogger::info(const QString &msg) +{ + write(LogLevel::Info, msg); +} + +void BaseLogger::warning(const QString &msg) +{ + write(LogLevel::Warning, msg); +} + +void BaseLogger::critical(const QString &msg) +{ + write(LogLevel::Critical, msg); +} + +void BaseLogger::setLevel(const BaseLogger::LogLevel level) +{ + m_level = level; +} + +void BaseLogger::setLevel(const QString &levelStr) +{ + auto ok = false; + const auto parsed = parseLevel(levelStr, &ok); + if (ok) + { + setLevel(parsed); + return; + } + + if (!levelStr.trimmed().isEmpty()) + { + write(LogLevel::Warning, + QStringLiteral("Unknown log level \"%1\". Keeping \"%2\".") + .arg(levelStr, levelToString(m_level)), + true); + } +} + +BaseLogger::LogLevel BaseLogger::level() const +{ + return m_level; +} + +void BaseLogger::flush() +{ + // no implementation +} + +QString BaseLogger::formatMessage(const BaseLogger::LogLevel level, const QString &msg) +{ + const auto timestamp = QDateTime::currentDateTime() + .toString(QStringLiteral("yyyy-MM-ddTHH:mm:ss.zzz")); + return QStringLiteral("%1 ngstd [%2] %3") + .arg(timestamp, levelToString(level), msg); +} + +void BaseLogger::write(const BaseLogger::LogLevel level, const QString &msg, const bool force) +{ + if (force || shouldLog(level)) + log(level, msg); +} + +bool BaseLogger::shouldLog(const BaseLogger::LogLevel level) const +{ + return level >= m_level; +} + diff --git a/src/framework/logger/baselogger.h b/src/framework/logger/baselogger.h new file mode 100644 index 0000000..9af2f83 --- /dev/null +++ b/src/framework/logger/baselogger.h @@ -0,0 +1,68 @@ +/****************************************************************************** +* Project: NextGIS GIS libraries +* Purpose: Framework library +* Author: NextGIS +******************************************************************************* +* Copyright (C) 2012-2025 NextGIS, info@nextgis.ru +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 of the License, or +* (at your option) any later version. +* This program 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +******************************************************************************/ + +#ifndef NGFRAMEWORK_BASELOGGER_H +#define NGFRAMEWORK_BASELOGGER_H + +#include "framework.h" + +#include +#include + +class NGFRAMEWORK_EXPORT BaseLogger : public QObject +{ + Q_OBJECT + +public: + enum class LogLevel + { + Debug = 0, + Info, + Warning, + Critical + }; + + explicit BaseLogger(QObject *parent = nullptr); + + void debug(const QString &msg); + void info(const QString &msg); + void warning(const QString &msg); + void critical(const QString &msg); + + void setLevel(LogLevel level); + void setLevel(const QString &levelStr); + LogLevel level() const; + + virtual void flush(); + + static QString formatMessage(LogLevel level, const QString &msg); + +protected: + virtual void log(LogLevel level, const QString &msg) = 0; + void write(LogLevel level, const QString &msg, bool force = false); + +private: + bool shouldLog(LogLevel level) const; + + LogLevel m_level; +}; + +#endif // NGFRAMEWORK_BASELOGGER_H + diff --git a/src/framework/logger/consolelogger.cpp b/src/framework/logger/consolelogger.cpp new file mode 100644 index 0000000..c6ceaff --- /dev/null +++ b/src/framework/logger/consolelogger.cpp @@ -0,0 +1,32 @@ +/****************************************************************************** +* Project: NextGIS GIS libraries +* Purpose: Framework library +* Author: NextGIS +******************************************************************************* +* Copyright (C) 2012-2025 NextGIS, info@nextgis.ru +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 of the License, or +* (at your option) any later version. +* This program 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +******************************************************************************/ + +#include "logger/consolelogger.h" + +#include + +void ConsoleLogger::log(const BaseLogger::LogLevel level, const QString &msg) +{ + QTextStream stream( + (level == LogLevel::Warning || level == LogLevel::Critical) ? stderr : stdout, + QIODevice::WriteOnly); + stream << formatMessage(level, msg) << Qt::endl; +} + diff --git a/src/framework/logger/consolelogger.h b/src/framework/logger/consolelogger.h new file mode 100644 index 0000000..580213c --- /dev/null +++ b/src/framework/logger/consolelogger.h @@ -0,0 +1,38 @@ +/****************************************************************************** +* Project: NextGIS GIS libraries +* Purpose: Framework library +* Author: NextGIS +******************************************************************************* +* Copyright (C) 2012-2025 NextGIS, info@nextgis.ru +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 of the License, or +* (at your option) any later version. +* This program 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +******************************************************************************/ + +#ifndef NGFRAMEWORK_CONSOLELOGGER_H +#define NGFRAMEWORK_CONSOLELOGGER_H + +#include "logger/baselogger.h" + +class NGFRAMEWORK_EXPORT ConsoleLogger : public BaseLogger +{ + Q_OBJECT + +public: + using BaseLogger::BaseLogger; + +protected: + void log(LogLevel level, const QString &msg) override; +}; + +#endif // NGFRAMEWORK_CONSOLELOGGER_H + diff --git a/src/framework/logger/loggerdecorator.cpp b/src/framework/logger/loggerdecorator.cpp new file mode 100644 index 0000000..41558f6 --- /dev/null +++ b/src/framework/logger/loggerdecorator.cpp @@ -0,0 +1,61 @@ +/****************************************************************************** +* Project: NextGIS GIS libraries +* Purpose: Framework library +* Author: NextGIS +******************************************************************************* +* Copyright (C) 2012-2025 NextGIS, info@nextgis.ru +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 of the License, or +* (at your option) any later version. +* This program 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +******************************************************************************/ + +#include "logger/loggerdecorator.h" + +LoggerDecorator::LoggerDecorator(std::shared_ptr wrapped, QObject *parent) + : BaseLogger(parent) + , m_wrapped(std::move(wrapped)) +{ +} + +void LoggerDecorator::flush() +{ + if (m_wrapped) + m_wrapped->flush(); +} + +void LoggerDecorator::log(const BaseLogger::LogLevel level, const QString &msg) +{ + if (!m_wrapped) + return; + + switch (level) + { + case LogLevel::Debug: + m_wrapped->debug(msg); + break; + case LogLevel::Info: + m_wrapped->info(msg); + break; + case LogLevel::Warning: + m_wrapped->warning(msg); + break; + case LogLevel::Critical: + m_wrapped->critical(msg); + break; + } +} + +std::shared_ptr LoggerDecorator::wrapped() const +{ + return m_wrapped; +} + diff --git a/src/framework/logger/loggerdecorator.h b/src/framework/logger/loggerdecorator.h new file mode 100644 index 0000000..be1accf --- /dev/null +++ b/src/framework/logger/loggerdecorator.h @@ -0,0 +1,47 @@ +/****************************************************************************** +* Project: NextGIS GIS libraries +* Purpose: Framework library +* Author: NextGIS +******************************************************************************* +* Copyright (C) 2012-2025 NextGIS, info@nextgis.ru +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 of the License, or +* (at your option) any later version. +* This program 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +******************************************************************************/ + +#ifndef NGFRAMEWORK_LOGGERDECORATOR_H +#define NGFRAMEWORK_LOGGERDECORATOR_H + +#include "logger/baselogger.h" + +#include + +class NGFRAMEWORK_EXPORT LoggerDecorator : public BaseLogger +{ + Q_OBJECT + +public: + explicit LoggerDecorator(std::shared_ptr wrapped, QObject *parent = nullptr); + + void flush() override; + +protected: + void log(LogLevel level, const QString &msg) override; + + std::shared_ptr wrapped() const; + +private: + std::shared_ptr m_wrapped; +}; + +#endif // NGFRAMEWORK_LOGGERDECORATOR_H + diff --git a/src/framework/logger/sentrylogger.cpp b/src/framework/logger/sentrylogger.cpp new file mode 100644 index 0000000..ddee86d --- /dev/null +++ b/src/framework/logger/sentrylogger.cpp @@ -0,0 +1,138 @@ +/****************************************************************************** +* Project: NextGIS GIS libraries +* Purpose: Framework library +* Author: NextGIS +******************************************************************************* +* Copyright (C) 2012-2025 NextGIS, info@nextgis.ru +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 of the License, or +* (at your option) any later version. +* This program 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +******************************************************************************/ + +#include "logger/sentrylogger.h" + +#include + +#include "logger/baselogger.h" +#include "logger/loggerdecorator.h" +#include "sentryreporter.h" + +namespace +{ +SentryReporter::Level toSentryLevel(const BaseLogger::LogLevel level) +{ + switch (level) + { + case BaseLogger::LogLevel::Critical: + return SentryReporter::Level::Fatal; + case BaseLogger::LogLevel::Warning: + return SentryReporter::Level::Warning; + case BaseLogger::LogLevel::Info: + return SentryReporter::Level::Info; + case BaseLogger::LogLevel::Debug: + default: + return SentryReporter::Level::Debug; + } +} +} + +SentryLogger::SentryLogger(std::shared_ptr wrapped, QObject *parent) + : LoggerDecorator(std::move(wrapped), parent) + , m_flushTimer(this) +{ + m_flushTimer.setInterval(kFlushIntervalMs); + m_flushTimer.setTimerType(Qt::CoarseTimer); + + QObject::connect(&m_flushTimer, &QTimer::timeout, this, [this]() { + if (m_lineCount == 0) + return; + + flush(); + }); + + m_flushTimer.start(); +} + +SentryLogger::~SentryLogger() +{ + m_flushTimer.stop(); + flush(); +} + +void SentryLogger::flush() +{ + QString payload; + auto payloadLevel = LogLevel::Debug; + + { + QMutexLocker locker(&m_mutex); + if (m_buffer.isEmpty()) + { + LoggerDecorator::flush(); + return; + } + + payload = m_buffer; + payloadLevel = m_highestBufferedLevel; + m_buffer.clear(); + m_lineCount = 0; + m_highestBufferedLevel = LogLevel::Debug; + } + + sendBuffered(payload, payloadLevel); + LoggerDecorator::flush(); +} + +void SentryLogger::log(const BaseLogger::LogLevel level, const QString &msg) +{ + LoggerDecorator::log(level, msg); + appendMessage(level, BaseLogger::formatMessage(level, msg)); +} + +void SentryLogger::appendMessage(const BaseLogger::LogLevel level, const QString &formattedMessage) +{ + QString payload; + auto payloadLevel = LogLevel::Debug; + + { + QMutexLocker locker(&m_mutex); + if (m_lineCount >= kMaxBufferedLines && !m_buffer.isEmpty()) + { + payload = m_buffer; + payloadLevel = m_highestBufferedLevel; + m_buffer.clear(); + m_lineCount = 0; + m_highestBufferedLevel = LogLevel::Debug; + } + + if (!m_buffer.isEmpty()) + m_buffer.append(QLatin1Char('\n')); + + m_buffer.append(formattedMessage); + ++m_lineCount; + + if (m_lineCount == 1 || level >= m_highestBufferedLevel) + m_highestBufferedLevel = level; + } + + if (!payload.isEmpty()) + sendBuffered(payload, payloadLevel); +} + +void SentryLogger::sendBuffered(const QString &payload, BaseLogger::LogLevel level) +{ + if (payload.isEmpty()) + return; + + SentryReporter::instance().sendMessage(payload, toSentryLevel(level)); +} + diff --git a/src/framework/logger/sentrylogger.h b/src/framework/logger/sentrylogger.h new file mode 100644 index 0000000..6114ec8 --- /dev/null +++ b/src/framework/logger/sentrylogger.h @@ -0,0 +1,57 @@ +/****************************************************************************** +* Project: NextGIS GIS libraries +* Purpose: Framework library +* Author: NextGIS +******************************************************************************* +* Copyright (C) 2012-2025 NextGIS, info@nextgis.ru +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 of the License, or +* (at your option) any later version. +* This program 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +******************************************************************************/ + +#ifndef NGFRAMEWORK_SENTRYLOGGER_H +#define NGFRAMEWORK_SENTRYLOGGER_H + +#include "logger/loggerdecorator.h" + +#include +#include + +class NGFRAMEWORK_EXPORT SentryLogger : public LoggerDecorator +{ + Q_OBJECT + +public: + explicit SentryLogger(std::shared_ptr wrapped, QObject *parent = nullptr); + ~SentryLogger() override; + + void flush() override; + +protected: + void log(LogLevel level, const QString &msg) override; + +private: + void appendMessage(LogLevel level, const QString &formattedMessage); + void sendBuffered(const QString &payload, LogLevel level); + + static constexpr int kMaxBufferedLines = 1000; + static constexpr int kFlushIntervalMs = 10 * 1000; + + QTimer m_flushTimer; + QMutex m_mutex; + QString m_buffer; + int m_lineCount = 0; + LogLevel m_highestBufferedLevel = LogLevel::Debug; +}; + +#endif // NGFRAMEWORK_SENTRYLOGGER_H +