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 <array>
#include <cerrno>
#include <cstdio>
#include <fcntl.h>
#include <qbytearrayview.h>
#include <qcoreapplication.h>
#include <qdatetime.h>
#include <qendian.h>
#include <qfilesystemwatcher.h>
#include <qhash.h>
#include <qhashfunctions.h>
#include <qlist.h>
@ -356,6 +360,18 @@ void ThreadLogging::initFs() {
delete detailedFile;
detailedFile = nullptr;
} 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;
}
@ -737,22 +753,13 @@ bool EncodedLogReader::registerCategory() {
return true;
}
bool readEncodedLogs(QIODevice* device, bool timestamps, int tail, const QString& rulespec) {
QList<QLoggingRule> rules;
{
QLoggingSettingsParser parser;
parser.setContent(rulespec);
rules = parser.rules();
}
auto reader = EncodedLogReader();
reader.setDevice(device);
bool LogReader::initialize() {
this->reader.setDevice(this->file);
bool readable = false;
quint8 logVersion = 0;
quint8 readerVersion = 0;
if (!reader.readHeader(&readable, &logVersion, &readerVersion)) {
if (!this->reader.readHeader(&readable, &logVersion, &readerVersion)) {
qCritical() << "Failed to read log header.";
return false;
}
@ -765,29 +772,33 @@ bool readEncodedLogs(QIODevice* device, bool timestamps, int tail, const QString
return false;
}
return true;
}
bool LogReader::continueReading() {
auto color = LogManager::instance()->colorLogs;
auto filters = QHash<quint16, CategoryFilter>();
auto tailRing = RingBuffer<LogMessage>(tail);
auto tailRing = RingBuffer<LogMessage>(this->remainingTail);
LogMessage message;
auto stream = QTextStream(stdout);
while (reader.read(&message)) {
auto readCursor = this->file->pos();
while (this->reader.read(&message)) {
readCursor = this->file->pos();
CategoryFilter filter;
if (filters.contains(message.readCategoryId)) {
filter = filters.value(message.readCategoryId);
if (this->filters.contains(message.readCategoryId)) {
filter = this->filters.value(message.readCategoryId);
} else {
for (const auto& rule: rules) {
for (const auto& rule: this->rules) {
filter.applyRule(message.category, rule);
}
filters.insert(message.readCategoryId, filter);
this->filters.insert(message.readCategoryId, filter);
}
if (filter.shouldDisplay(message.type)) {
if (tail == 0) {
LogMessage::formatMessage(stream, message, color, timestamps);
if (this->remainingTail == 0) {
LogMessage::formatMessage(stream, message, color, this->timestamps);
stream << '\n';
} else {
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--) {
auto& message = tailRing.at(i);
LogMessage::formatMessage(stream, message, color, timestamps);
LogMessage::formatMessage(stream, message, color, this->timestamps);
stream << '\n';
}
}
stream << Qt::flush;
if (!device->atEnd()) {
if (this->file->pos() != readCursor) {
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;

View file

@ -4,6 +4,7 @@
#include <qcontainerfwd.h>
#include <qdatetime.h>
#include <qfile.h>
#include <qhash.h>
#include <qlatin1stringview.h>
#include <qlogging.h>
@ -130,7 +131,14 @@ private:
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

View file

@ -1,12 +1,18 @@
#pragma once
#include <utility>
#include <qbytearrayview.h>
#include <qcontainerfwd.h>
#include <qfile.h>
#include <qfilesystemwatcher.h>
#include <qlogging.h>
#include <qobject.h>
#include <qthread.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "logging.hpp"
#include "logging_qtprivate.hpp"
#include "ringbuf.hpp"
namespace qs::log {
@ -120,4 +126,64 @@ private:
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

View file

@ -16,34 +16,13 @@
#include <qstringview.h>
#include <qtypes.h>
#include "logging_qtprivate.hpp"
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);
};
class QLoggingSettingsParser {
public:
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;
size_t verbosity = 0;
int tail = 0;
bool follow = false;
QStringOption rules;
QStringOption readoutRules;
QStringOption file;
@ -241,13 +242,10 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
}
{
auto* sub = cli.add_subcommand(
"log",
"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"
"the instance selection flags will be read."
);
auto* sub = cli.add_subcommand("log", "Read quickshell logs.\n")
->description("If --file is specified, the given file will be read.\n"
"If not, the log of the first launched instance matching"
"the instance selection flags will be 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.")
->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.")
->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* all = sub->add_flag("-a,--all", state.instance.all)
->description("List all instances.\n"
"If unspecified, only instances of"
@ -472,7 +474,14 @@ int readLogFile(CommandState& cmd) {
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
: -1;
}

View file

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

View file

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