core/command: add log --follow
This commit is contained in:
parent
c78381f6d0
commit
a82fbf40c2
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
44
src/core/logging_qtprivate.hpp
Normal file
44
src/core/logging_qtprivate.hpp
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue