core/command: add log --follow

This commit is contained in:
outfoxxed 2024-09-10 03:31:49 -07:00
parent c78381f6d0
commit a82fbf40c2
Signed by: outfoxxed
GPG key ID: 4C88A185FB89301E
8 changed files with 262 additions and 63 deletions

View file

@ -1,10 +1,14 @@
#include "logging.hpp" #include "logging.hpp"
#include <array> #include <array>
#include <cerrno>
#include <cstdio> #include <cstdio>
#include <fcntl.h>
#include <qbytearrayview.h> #include <qbytearrayview.h>
#include <qcoreapplication.h>
#include <qdatetime.h> #include <qdatetime.h>
#include <qendian.h> #include <qendian.h>
#include <qfilesystemwatcher.h>
#include <qhash.h> #include <qhash.h>
#include <qhashfunctions.h> #include <qhashfunctions.h>
#include <qlist.h> #include <qlist.h>
@ -356,6 +360,18 @@ void ThreadLogging::initFs() {
delete detailedFile; delete detailedFile;
detailedFile = nullptr; detailedFile = nullptr;
} else { } else {
auto lock = flock {
.l_type = F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 0,
};
if (fcntl(detailedFile->handle(), F_SETLK, &lock) != 0) { // NOLINT
qCWarning(logLogging) << "Unable to set lock marker on detailed log file. --follow from "
"other instances will not work.";
}
qCInfo(logLogging) << "Saving detailed logs to" << path; qCInfo(logLogging) << "Saving detailed logs to" << path;
} }
@ -737,22 +753,13 @@ bool EncodedLogReader::registerCategory() {
return true; return true;
} }
bool readEncodedLogs(QIODevice* device, bool timestamps, int tail, const QString& rulespec) { bool LogReader::initialize() {
QList<QLoggingRule> rules; this->reader.setDevice(this->file);
{
QLoggingSettingsParser parser;
parser.setContent(rulespec);
rules = parser.rules();
}
auto reader = EncodedLogReader();
reader.setDevice(device);
bool readable = false; bool readable = false;
quint8 logVersion = 0; quint8 logVersion = 0;
quint8 readerVersion = 0; quint8 readerVersion = 0;
if (!reader.readHeader(&readable, &logVersion, &readerVersion)) { if (!this->reader.readHeader(&readable, &logVersion, &readerVersion)) {
qCritical() << "Failed to read log header."; qCritical() << "Failed to read log header.";
return false; return false;
} }
@ -765,29 +772,33 @@ bool readEncodedLogs(QIODevice* device, bool timestamps, int tail, const QString
return false; return false;
} }
return true;
}
bool LogReader::continueReading() {
auto color = LogManager::instance()->colorLogs; auto color = LogManager::instance()->colorLogs;
auto tailRing = RingBuffer<LogMessage>(this->remainingTail);
auto filters = QHash<quint16, CategoryFilter>();
auto tailRing = RingBuffer<LogMessage>(tail);
LogMessage message; LogMessage message;
auto stream = QTextStream(stdout); auto stream = QTextStream(stdout);
while (reader.read(&message)) { auto readCursor = this->file->pos();
while (this->reader.read(&message)) {
readCursor = this->file->pos();
CategoryFilter filter; CategoryFilter filter;
if (filters.contains(message.readCategoryId)) { if (this->filters.contains(message.readCategoryId)) {
filter = filters.value(message.readCategoryId); filter = this->filters.value(message.readCategoryId);
} else { } else {
for (const auto& rule: rules) { for (const auto& rule: this->rules) {
filter.applyRule(message.category, rule); filter.applyRule(message.category, rule);
} }
filters.insert(message.readCategoryId, filter); this->filters.insert(message.readCategoryId, filter);
} }
if (filter.shouldDisplay(message.type)) { if (filter.shouldDisplay(message.type)) {
if (tail == 0) { if (this->remainingTail == 0) {
LogMessage::formatMessage(stream, message, color, timestamps); LogMessage::formatMessage(stream, message, color, this->timestamps);
stream << '\n'; stream << '\n';
} else { } else {
tailRing.emplace(message); tailRing.emplace(message);
@ -795,19 +806,97 @@ bool readEncodedLogs(QIODevice* device, bool timestamps, int tail, const QString
} }
} }
if (tail != 0) { if (this->remainingTail != 0) {
for (auto i = tailRing.size() - 1; i != -1; i--) { for (auto i = tailRing.size() - 1; i != -1; i--) {
auto& message = tailRing.at(i); auto& message = tailRing.at(i);
LogMessage::formatMessage(stream, message, color, timestamps); LogMessage::formatMessage(stream, message, color, this->timestamps);
stream << '\n'; stream << '\n';
} }
} }
stream << Qt::flush; stream << Qt::flush;
if (!device->atEnd()) { if (this->file->pos() != readCursor) {
qCritical() << "An error occurred parsing the end of this log file."; qCritical() << "An error occurred parsing the end of this log file.";
qCritical() << "Remaining data:" << device->readAll(); qCritical() << "Remaining data:" << this->file->readAll();
return false;
}
return true;
}
void LogFollower::FcntlWaitThread::run() {
auto lock = flock {
.l_type = F_RDLCK, // won't block other read locks when we take it
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 0,
};
auto r = fcntl(this->follower->reader->file->handle(), F_SETLKW, &lock); // NOLINT
if (r != 0) {
qCWarning(logLogging).nospace()
<< "Failed to wait for write locks to be removed from log file with error code " << errno
<< ": " << qt_error_string();
}
}
bool LogFollower::follow() {
QObject::connect(&this->waitThread, &QThread::finished, this, &LogFollower::onFileLocked);
QObject::connect(
&this->fileWatcher,
&QFileSystemWatcher::fileChanged,
this,
&LogFollower::onFileChanged
);
this->fileWatcher.addPath(this->path);
this->waitThread.start();
auto r = QCoreApplication::exec();
return r == 0;
}
void LogFollower::onFileChanged() {
if (!this->reader->continueReading()) {
QCoreApplication::exit(1);
}
}
void LogFollower::onFileLocked() {
if (!this->reader->continueReading()) {
QCoreApplication::exit(1);
} else {
QCoreApplication::exit(0);
}
}
bool readEncodedLogs(
QFile* file,
const QString& path,
bool timestamps,
int tail,
bool follow,
const QString& rulespec
) {
QList<QLoggingRule> rules;
{
QLoggingSettingsParser parser;
parser.setContent(rulespec);
rules = parser.rules();
}
auto reader = LogReader(file, timestamps, tail, rules);
if (!reader.initialize()) return false;
if (!reader.continueReading()) return false;
if (follow) {
auto follower = LogFollower(&reader, path);
return follower.follow();
} }
return true; return true;

View file

@ -4,6 +4,7 @@
#include <qcontainerfwd.h> #include <qcontainerfwd.h>
#include <qdatetime.h> #include <qdatetime.h>
#include <qfile.h>
#include <qhash.h> #include <qhash.h>
#include <qlatin1stringview.h> #include <qlatin1stringview.h>
#include <qlogging.h> #include <qlogging.h>
@ -130,7 +131,14 @@ private:
LoggingThreadProxy threadProxy; LoggingThreadProxy threadProxy;
}; };
bool readEncodedLogs(QIODevice* device, bool timestamps, int tail, const QString& rulespec); bool readEncodedLogs(
QFile* file,
const QString& path,
bool timestamps,
int tail,
bool follow,
const QString& rulespec
);
} // namespace qs::log } // namespace qs::log

View file

@ -1,12 +1,18 @@
#pragma once #pragma once
#include <utility>
#include <qbytearrayview.h> #include <qbytearrayview.h>
#include <qcontainerfwd.h> #include <qcontainerfwd.h>
#include <qfile.h> #include <qfile.h>
#include <qfilesystemwatcher.h>
#include <qlogging.h> #include <qlogging.h>
#include <qobject.h>
#include <qthread.h>
#include <qtmetamacros.h> #include <qtmetamacros.h>
#include <qtypes.h> #include <qtypes.h>
#include "logging.hpp" #include "logging.hpp"
#include "logging_qtprivate.hpp"
#include "ringbuf.hpp" #include "ringbuf.hpp"
namespace qs::log { namespace qs::log {
@ -120,4 +126,64 @@ private:
EncodedLogWriter detailedWriter; EncodedLogWriter detailedWriter;
}; };
class LogFollower;
class LogReader {
public:
explicit LogReader(
QFile* file,
bool timestamps,
int tail,
QList<qt_logging_registry::QLoggingRule> rules
)
: file(file)
, timestamps(timestamps)
, remainingTail(tail)
, rules(std::move(rules)) {}
bool initialize();
bool continueReading();
private:
QFile* file;
EncodedLogReader reader;
bool timestamps;
int remainingTail;
QHash<quint16, CategoryFilter> filters;
QList<qt_logging_registry::QLoggingRule> rules;
friend class LogFollower;
};
class LogFollower: public QObject {
Q_OBJECT;
public:
explicit LogFollower(LogReader* reader, QString path): reader(reader), path(std::move(path)) {}
bool follow();
private slots:
void onFileChanged();
void onFileLocked();
private:
LogReader* reader;
QString path;
QFileSystemWatcher fileWatcher;
class FcntlWaitThread: public QThread {
public:
explicit FcntlWaitThread(LogFollower* follower): follower(follower) {}
protected:
void run() override;
private:
LogFollower* follower;
};
FcntlWaitThread waitThread {this};
};
} // namespace qs::log } // namespace qs::log

View file

@ -16,34 +16,13 @@
#include <qstringview.h> #include <qstringview.h>
#include <qtypes.h> #include <qtypes.h>
#include "logging_qtprivate.hpp"
namespace qs::log { namespace qs::log {
Q_DECLARE_LOGGING_CATEGORY(logLogging); Q_DECLARE_LOGGING_CATEGORY(logLogging);
namespace qt_logging_registry { namespace qt_logging_registry {
class QLoggingRule {
public:
QLoggingRule();
QLoggingRule(QStringView pattern, bool enabled);
[[nodiscard]] int pass(QLatin1StringView categoryName, QtMsgType type) const;
enum PatternFlag {
FullText = 0x1,
LeftFilter = 0x2,
RightFilter = 0x4,
MidFilter = LeftFilter | RightFilter
};
Q_DECLARE_FLAGS(PatternFlags, PatternFlag)
QString category;
int messageType;
PatternFlags flags;
bool enabled;
private:
void parse(QStringView pattern);
};
class QLoggingSettingsParser { class QLoggingSettingsParser {
public: public:
void setContent(QStringView content); void setContent(QStringView content);

View file

@ -0,0 +1,44 @@
#pragma once
// The logging rule parser from qloggingregistry_p.h and qloggingregistry.cpp.
// Was unable to properly link the functions when directly using the headers (which we depend
// on anyway), so below is a slightly stripped down copy. Making the originals link would
// be preferable.
#include <qflags.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qstringview.h>
namespace qs::log {
Q_DECLARE_LOGGING_CATEGORY(logLogging);
namespace qt_logging_registry {
class QLoggingRule {
public:
QLoggingRule();
QLoggingRule(QStringView pattern, bool enabled);
[[nodiscard]] int pass(QLatin1StringView categoryName, QtMsgType type) const;
enum PatternFlag {
FullText = 0x1,
LeftFilter = 0x2,
RightFilter = 0x4,
MidFilter = LeftFilter | RightFilter
};
Q_DECLARE_FLAGS(PatternFlags, PatternFlag)
QString category;
int messageType;
PatternFlags flags;
bool enabled;
private:
void parse(QStringView pattern);
};
} // namespace qt_logging_registry
} // namespace qs::log

View file

@ -96,6 +96,7 @@ struct CommandState {
bool sparse = false; bool sparse = false;
size_t verbosity = 0; size_t verbosity = 0;
int tail = 0; int tail = 0;
bool follow = false;
QStringOption rules; QStringOption rules;
QStringOption readoutRules; QStringOption readoutRules;
QStringOption file; QStringOption file;
@ -241,13 +242,10 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
} }
{ {
auto* sub = cli.add_subcommand( auto* sub = cli.add_subcommand("log", "Read quickshell logs.\n")
"log", ->description("If --file is specified, the given file will be read.\n"
"Read quickshell logs.\n"
"If --file is specified, the given file will be read.\n"
"If not, the log of the first launched instance matching" "If not, the log of the first launched instance matching"
"the instance selection flags will be read." "the instance selection flags will be read.");
);
auto* file = sub->add_option("--file", state.log.file, "Log file to read."); auto* file = sub->add_option("--file", state.log.file, "Log file to read.");
@ -255,6 +253,9 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
->description("Maximum number of lines to print, starting from the bottom.") ->description("Maximum number of lines to print, starting from the bottom.")
->check(CLI::Range(1, std::numeric_limits<int>::max(), "INT > 0")); ->check(CLI::Range(1, std::numeric_limits<int>::max(), "INT > 0"));
sub->add_flag("--follow", state.log.follow)
->description("Keep reading the log until the logging process terminates.");
sub->add_option("-r,--rules", state.log.readoutRules, "Log file to read.") sub->add_option("-r,--rules", state.log.readoutRules, "Log file to read.")
->description("Rules to apply to the log being read, in the format of QT_LOGGING_RULES."); ->description("Rules to apply to the log being read, in the format of QT_LOGGING_RULES.");
@ -267,6 +268,7 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
{ {
auto* sub = cli.add_subcommand("list", "List running quickshell instances."); auto* sub = cli.add_subcommand("list", "List running quickshell instances.");
auto* all = sub->add_flag("-a,--all", state.instance.all) auto* all = sub->add_flag("-a,--all", state.instance.all)
->description("List all instances.\n" ->description("List all instances.\n"
"If unspecified, only instances of" "If unspecified, only instances of"
@ -472,7 +474,14 @@ int readLogFile(CommandState& cmd) {
return -1; return -1;
} }
return qs::log::readEncodedLogs(&file, cmd.log.timestamp, cmd.log.tail, *cmd.log.readoutRules) return qs::log::readEncodedLogs(
&file,
path,
cmd.log.timestamp,
cmd.log.tail,
cmd.log.follow,
*cmd.log.readoutRules
)
? 0 ? 0
: -1; : -1;
} }

View file

@ -36,11 +36,14 @@ QDir QsPaths::crashDir(const QString& id) {
return dir; return dir;
} }
QString QsPaths::basePath(const QString& id) {
auto path = QsPaths::instance()->baseRunDir()->filePath("by-id");
path = QDir(path).filePath(id);
return path;
}
QString QsPaths::ipcPath(const QString& id) { QString QsPaths::ipcPath(const QString& id) {
auto ipcPath = QsPaths::instance()->baseRunDir()->filePath("by-id"); return QDir(QsPaths::basePath(id)).filePath("ipc.sock");
ipcPath = QDir(ipcPath).filePath(id);
ipcPath = QDir(ipcPath).filePath("ipc.sock");
return ipcPath;
} }
QDir* QsPaths::cacheDir() { QDir* QsPaths::cacheDir() {

View file

@ -17,6 +17,7 @@ public:
static QsPaths* instance(); static QsPaths* instance();
static void init(QString shellId, QString pathId); static void init(QString shellId, QString pathId);
static QDir crashDir(const QString& id); static QDir crashDir(const QString& id);
static QString basePath(const QString& id);
static QString ipcPath(const QString& id); static QString ipcPath(const QString& id);
static bool checkLock(const QString& path, InstanceLockInfo* info = nullptr); static bool checkLock(const QString& path, InstanceLockInfo* info = nullptr);
static QVector<InstanceLockInfo> collectInstances(const QString& path); static QVector<InstanceLockInfo> collectInstances(const QString& path);