diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b162187..93290a1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Minor: Added support for Brave & google-chrome-stable browsers. (#5452) - Minor: Added drop indicator line while dragging in tables. (#5256) - Minor: Add channel points indication for new bits power-up redemptions. (#5471) +- Minor: Added option to log streams by their ID, allowing for easier "per-stream" log analyzing. (#5507) - Minor: Added `/warn ` command for mods. This prevents the user from chatting until they acknowledge the warning. (#5474) - Minor: Improve appearance of reply button. (#5491) - Minor: Introduce HTTP API for plugins. (#5383, #5492, #5494) diff --git a/mocks/include/mocks/Logging.hpp b/mocks/include/mocks/Logging.hpp index 8d444142..0f4dba38 100644 --- a/mocks/include/mocks/Logging.hpp +++ b/mocks/include/mocks/Logging.hpp @@ -16,7 +16,7 @@ public: MOCK_METHOD(void, addMessage, (const QString &channelName, MessagePtr message, - const QString &platformName), + const QString &platformName, const QString &streamID), (override)); }; @@ -27,7 +27,8 @@ public: ~EmptyLogging() override = default; void addMessage(const QString &channelName, MessagePtr message, - const QString &platformName) override + const QString &platformName, + const QString &streamID) override { // } diff --git a/src/common/Channel.cpp b/src/common/Channel.cpp index afdc99c0..76dd8bb8 100644 --- a/src/common/Channel.cpp +++ b/src/common/Channel.cpp @@ -101,7 +101,8 @@ void Channel::addMessage(MessagePtr message, MessageContext context, { // Only log messages where the `DoNotLog` flag is not set getIApp()->getChatLogger()->addMessage(this->name_, message, - this->platform_); + this->platform_, + this->getCurrentStreamID()); } } @@ -356,6 +357,11 @@ void Channel::reconnect() { } +QString Channel::getCurrentStreamID() const +{ + return {}; +} + std::shared_ptr Channel::getEmpty() { static std::shared_ptr channel(new Channel("", Type::None)); diff --git a/src/common/Channel.hpp b/src/common/Channel.hpp index ad4af511..b85fb83c 100644 --- a/src/common/Channel.hpp +++ b/src/common/Channel.hpp @@ -119,6 +119,7 @@ public: virtual bool shouldIgnoreHighlights() const; virtual bool canReconnect() const; virtual void reconnect(); + virtual QString getCurrentStreamID() const; static std::shared_ptr getEmpty(); diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index f57081c8..cd293630 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -457,6 +457,7 @@ void TwitchChannel::updateStreamStatus( auto stream = *helixStream; { auto status = this->streamStatus_.access(); + status->streamId = stream.id; status->viewerCount = stream.viewerCount; status->gameId = stream.gameId; status->game = stream.gameName; @@ -770,6 +771,17 @@ void TwitchChannel::reconnect() getIApp()->getTwitchAbstract()->connect(); } +QString TwitchChannel::getCurrentStreamID() const +{ + auto streamStatus = this->accessStreamStatus(); + if (streamStatus->live) + { + return streamStatus->streamId; + } + + return {}; +} + QString TwitchChannel::roomId() const { return *this->roomID_.access(); diff --git a/src/providers/twitch/TwitchChannel.hpp b/src/providers/twitch/TwitchChannel.hpp index 611fea1f..e3d1e96d 100644 --- a/src/providers/twitch/TwitchChannel.hpp +++ b/src/providers/twitch/TwitchChannel.hpp @@ -87,6 +87,7 @@ public: QString uptime; int uptimeSeconds = 0; QString streamType; + QString streamId; }; struct RoomModes { @@ -133,6 +134,7 @@ public: bool hasHighRateLimit() const override; bool canReconnect() const override; void reconnect() override; + QString getCurrentStreamID() const override; void createClip(); // Data diff --git a/src/singletons/Logging.cpp b/src/singletons/Logging.cpp index d8bb5712..138efd38 100644 --- a/src/singletons/Logging.cpp +++ b/src/singletons/Logging.cpp @@ -33,7 +33,7 @@ Logging::Logging(Settings &settings) } void Logging::addMessage(const QString &channelName, MessagePtr message, - const QString &platformName) + const QString &platformName, const QString &streamID) { this->threadGuard.guard(); @@ -54,7 +54,7 @@ void Logging::addMessage(const QString &channelName, MessagePtr message, if (platIt == this->loggingChannels_.end()) { auto *channel = new LoggingChannel(channelName, platformName); - channel->addMessage(message); + channel->addMessage(message, streamID); auto map = std::map>(); this->loggingChannels_[platformName] = std::move(map); auto &ref = this->loggingChannels_.at(platformName); @@ -65,12 +65,12 @@ void Logging::addMessage(const QString &channelName, MessagePtr message, if (chanIt == platIt->second.end()) { auto *channel = new LoggingChannel(channelName, platformName); - channel->addMessage(message); + channel->addMessage(message, streamID); platIt->second.emplace(channelName, std::move(channel)); } else { - chanIt->second->addMessage(message); + chanIt->second->addMessage(message, streamID); } } diff --git a/src/singletons/Logging.hpp b/src/singletons/Logging.hpp index af86a702..984932ad 100644 --- a/src/singletons/Logging.hpp +++ b/src/singletons/Logging.hpp @@ -22,7 +22,8 @@ public: virtual ~ILogging() = default; virtual void addMessage(const QString &channelName, MessagePtr message, - const QString &platformName) = 0; + const QString &platformName, + const QString &streamID) = 0; }; class Logging : public ILogging @@ -31,7 +32,8 @@ public: Logging(Settings &settings); void addMessage(const QString &channelName, MessagePtr message, - const QString &platformName) override; + const QString &platformName, + const QString &streamID) override; private: using PlatformName = QString; diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index f0c19edf..98ec53ed 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -447,6 +447,10 @@ public: BoolSetting enableLogging = {"/logging/enabled", false}; BoolSetting onlyLogListedChannels = {"/logging/onlyLogListedChannels", false}; + BoolSetting separatelyStoreStreamLogs = { + "/logging/separatelyStoreStreamLogs", + false, + }; QStringSetting logPath = {"/logging/path", ""}; diff --git a/src/singletons/helper/LoggingChannel.cpp b/src/singletons/helper/LoggingChannel.cpp index f3a6fbb7..ad8cfb9a 100644 --- a/src/singletons/helper/LoggingChannel.cpp +++ b/src/singletons/helper/LoggingChannel.cpp @@ -1,4 +1,4 @@ -#include "LoggingChannel.hpp" +#include "singletons/helper/LoggingChannel.hpp" #include "Application.hpp" #include "common/QLogging.hpp" @@ -7,16 +7,58 @@ #include "singletons/Paths.hpp" #include "singletons/Settings.hpp" +#include #include +namespace { + +const QByteArray ENDLINE("\n"); + +void appendLine(QFile &fileHandle, const QString &line) +{ + assert(fileHandle.isOpen()); + assert(fileHandle.isWritable()); + + fileHandle.write(line.toUtf8()); + fileHandle.flush(); +} + +QString generateOpeningString( + const QDateTime &now = QDateTime::currentDateTime()) +{ + QString ret("# Start logging at "); + + ret.append(now.toString("yyyy-MM-dd HH:mm:ss ")); + ret.append(now.timeZoneAbbreviation()); + ret.append(ENDLINE); + + return ret; +} + +QString generateClosingString( + const QDateTime &now = QDateTime::currentDateTime()) +{ + QString ret("# Stop logging at "); + + ret.append(now.toString("yyyy-MM-dd HH:mm:ss")); + ret.append(now.timeZoneAbbreviation()); + ret.append(ENDLINE); + + return ret; +} + +QString generateDateString(const QDateTime &now) +{ + return now.toString("yyyy-MM-dd"); +} + +} // namespace + namespace chatterino { -QByteArray endline("\n"); - -LoggingChannel::LoggingChannel(const QString &_channelName, - const QString &_platform) - : channelName(_channelName) - , platform(_platform) +LoggingChannel::LoggingChannel(QString _channelName, QString _platform) + : channelName(std::move(_channelName)) + , platform(std::move(_platform)) { if (this->channelName.startsWith("/whispers")) { @@ -54,14 +96,15 @@ LoggingChannel::LoggingChannel(const QString &_channelName, LoggingChannel::~LoggingChannel() { - this->appendLine(this->generateClosingString()); + appendLine(this->fileHandle, generateClosingString()); this->fileHandle.close(); + this->currentStreamFileHandle.close(); } void LoggingChannel::openLogFile() { QDateTime now = QDateTime::currentDateTime(); - this->dateString = this->generateDateString(now); + this->dateString = generateDateString(now); if (this->fileHandle.isOpen()) { @@ -87,14 +130,45 @@ void LoggingChannel::openLogFile() this->fileHandle.open(QIODevice::Append); - this->appendLine(this->generateOpeningString(now)); + appendLine(this->fileHandle, generateOpeningString(now)); } -void LoggingChannel::addMessage(MessagePtr message) +void LoggingChannel::openStreamLogFile(const QString &streamID) +{ + QDateTime now = QDateTime::currentDateTime(); + this->currentStreamID = streamID; + + if (this->currentStreamFileHandle.isOpen()) + { + this->currentStreamFileHandle.flush(); + this->currentStreamFileHandle.close(); + } + + QString baseFileName = this->channelName + "-" + streamID + ".log"; + + QString directory = + this->baseDirectory + QDir::separator() + this->subDirectory; + + if (!QDir().mkpath(directory)) + { + qCDebug(chatterinoHelper) << "Unable to create logging path"; + return; + } + + QString fileName = directory + QDir::separator() + baseFileName; + qCDebug(chatterinoHelper) << "Logging stream to" << fileName; + this->currentStreamFileHandle.setFileName(fileName); + + this->currentStreamFileHandle.open(QIODevice::Append); + appendLine(this->currentStreamFileHandle, generateOpeningString(now)); +} + +void LoggingChannel::addMessage(const MessagePtr &message, + const QString &streamID) { QDateTime now = QDateTime::currentDateTime(); - QString messageDateString = this->generateDateString(now); + QString messageDateString = generateDateString(now); if (messageDateString != this->dateString) { this->dateString = messageDateString; @@ -154,42 +228,19 @@ void LoggingChannel::addMessage(MessagePtr message) } } str.append(messageText); - str.append(endline); + str.append(ENDLINE); - this->appendLine(str); -} + appendLine(this->fileHandle, str); -QString LoggingChannel::generateOpeningString(const QDateTime &now) const -{ - QString ret("# Start logging at "); + if (!streamID.isEmpty() && getSettings()->separatelyStoreStreamLogs) + { + if (this->currentStreamID != streamID) + { + this->openStreamLogFile(streamID); + } - ret.append(now.toString("yyyy-MM-dd HH:mm:ss ")); - ret.append(now.timeZoneAbbreviation()); - ret.append(endline); - - return ret; -} - -QString LoggingChannel::generateClosingString(const QDateTime &now) const -{ - QString ret("# Stop logging at "); - - ret.append(now.toString("yyyy-MM-dd HH:mm:ss")); - ret.append(now.timeZoneAbbreviation()); - ret.append(endline); - - return ret; -} - -void LoggingChannel::appendLine(const QString &line) -{ - this->fileHandle.write(line.toUtf8()); - this->fileHandle.flush(); -} - -QString LoggingChannel::generateDateString(const QDateTime &now) -{ - return now.toString("yyyy-MM-dd"); + appendLine(this->currentStreamFileHandle, str); + } } } // namespace chatterino diff --git a/src/singletons/helper/LoggingChannel.hpp b/src/singletons/helper/LoggingChannel.hpp index 753a63b9..3e57dd9b 100644 --- a/src/singletons/helper/LoggingChannel.hpp +++ b/src/singletons/helper/LoggingChannel.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -14,8 +13,7 @@ using MessagePtr = std::shared_ptr; class LoggingChannel { - explicit LoggingChannel(const QString &_channelName, - const QString &platform); + explicit LoggingChannel(QString _channelName, QString _platform); public: ~LoggingChannel(); @@ -26,19 +24,11 @@ public: LoggingChannel(LoggingChannel &&) = delete; LoggingChannel &operator=(LoggingChannel &&) = delete; - void addMessage(MessagePtr message); + void addMessage(const MessagePtr &message, const QString &streamID); private: void openLogFile(); - - QString generateOpeningString( - const QDateTime &now = QDateTime::currentDateTime()) const; - QString generateClosingString( - const QDateTime &now = QDateTime::currentDateTime()) const; - - void appendLine(const QString &line); - - QString generateDateString(const QDateTime &now); + void openStreamLogFile(const QString &streamID); const QString channelName; const QString platform; @@ -46,6 +36,8 @@ private: QString subDirectory; QFile fileHandle; + QFile currentStreamFileHandle; + QString currentStreamID; QString dateString; diff --git a/src/widgets/settingspages/ModerationPage.cpp b/src/widgets/settingspages/ModerationPage.cpp index 65ba577b..95669cce 100644 --- a/src/widgets/settingspages/ModerationPage.cpp +++ b/src/widgets/settingspages/ModerationPage.cpp @@ -157,11 +157,21 @@ ModerationPage::ModerationPage() onlyLogListedChannels->setEnabled(getSettings()->enableLogging); logs.append(onlyLogListedChannels); + auto *separatelyStoreStreamLogs = + this->createCheckBox("Store live stream logs as separate files", + getSettings()->separatelyStoreStreamLogs); + + separatelyStoreStreamLogs->setEnabled(getSettings()->enableLogging); + logs.append(separatelyStoreStreamLogs); + // Select event QObject::connect( enableLogging, &QCheckBox::stateChanged, this, - [enableLogging, onlyLogListedChannels]() mutable { + [enableLogging, onlyLogListedChannels, + separatelyStoreStreamLogs]() mutable { onlyLogListedChannels->setEnabled(enableLogging->isChecked()); + separatelyStoreStreamLogs->setEnabled( + getSettings()->enableLogging); }); EditableModelView *view =