Skip to content

Commit a82fbf4

Browse files
committed
core/command: add log --follow
1 parent c78381f commit a82fbf4

File tree

8 files changed

+262
-63
lines changed

8 files changed

+262
-63
lines changed

src/core/logging.cpp

Lines changed: 116 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
#include "logging.hpp"
22
#include <array>
3+
#include <cerrno>
34
#include <cstdio>
45

6+
#include <fcntl.h>
57
#include <qbytearrayview.h>
8+
#include <qcoreapplication.h>
69
#include <qdatetime.h>
710
#include <qendian.h>
11+
#include <qfilesystemwatcher.h>
812
#include <qhash.h>
913
#include <qhashfunctions.h>
1014
#include <qlist.h>
@@ -356,6 +360,18 @@ void ThreadLogging::initFs() {
356360
delete detailedFile;
357361
detailedFile = nullptr;
358362
} else {
363+
auto lock = flock {
364+
.l_type = F_WRLCK,
365+
.l_whence = SEEK_SET,
366+
.l_start = 0,
367+
.l_len = 0,
368+
};
369+
370+
if (fcntl(detailedFile->handle(), F_SETLK, &lock) != 0) { // NOLINT
371+
qCWarning(logLogging) << "Unable to set lock marker on detailed log file. --follow from "
372+
"other instances will not work.";
373+
}
374+
359375
qCInfo(logLogging) << "Saving detailed logs to" << path;
360376
}
361377

@@ -737,22 +753,13 @@ bool EncodedLogReader::registerCategory() {
737753
return true;
738754
}
739755

740-
bool readEncodedLogs(QIODevice* device, bool timestamps, int tail, const QString& rulespec) {
741-
QList<QLoggingRule> rules;
742-
743-
{
744-
QLoggingSettingsParser parser;
745-
parser.setContent(rulespec);
746-
rules = parser.rules();
747-
}
748-
749-
auto reader = EncodedLogReader();
750-
reader.setDevice(device);
756+
bool LogReader::initialize() {
757+
this->reader.setDevice(this->file);
751758

752759
bool readable = false;
753760
quint8 logVersion = 0;
754761
quint8 readerVersion = 0;
755-
if (!reader.readHeader(&readable, &logVersion, &readerVersion)) {
762+
if (!this->reader.readHeader(&readable, &logVersion, &readerVersion)) {
756763
qCritical() << "Failed to read log header.";
757764
return false;
758765
}
@@ -765,49 +772,131 @@ bool readEncodedLogs(QIODevice* device, bool timestamps, int tail, const QString
765772
return false;
766773
}
767774

768-
auto color = LogManager::instance()->colorLogs;
769-
770-
auto filters = QHash<quint16, CategoryFilter>();
775+
return true;
776+
}
771777

772-
auto tailRing = RingBuffer<LogMessage>(tail);
778+
bool LogReader::continueReading() {
779+
auto color = LogManager::instance()->colorLogs;
780+
auto tailRing = RingBuffer<LogMessage>(this->remainingTail);
773781

774782
LogMessage message;
775783
auto stream = QTextStream(stdout);
776-
while (reader.read(&message)) {
784+
auto readCursor = this->file->pos();
785+
while (this->reader.read(&message)) {
786+
readCursor = this->file->pos();
787+
777788
CategoryFilter filter;
778-
if (filters.contains(message.readCategoryId)) {
779-
filter = filters.value(message.readCategoryId);
789+
if (this->filters.contains(message.readCategoryId)) {
790+
filter = this->filters.value(message.readCategoryId);
780791
} else {
781-
for (const auto& rule: rules) {
792+
for (const auto& rule: this->rules) {
782793
filter.applyRule(message.category, rule);
783794
}
784795

785-
filters.insert(message.readCategoryId, filter);
796+
this->filters.insert(message.readCategoryId, filter);
786797
}
787798

788799
if (filter.shouldDisplay(message.type)) {
789-
if (tail == 0) {
790-
LogMessage::formatMessage(stream, message, color, timestamps);
800+
if (this->remainingTail == 0) {
801+
LogMessage::formatMessage(stream, message, color, this->timestamps);
791802
stream << '\n';
792803
} else {
793804
tailRing.emplace(message);
794805
}
795806
}
796807
}
797808

798-
if (tail != 0) {
809+
if (this->remainingTail != 0) {
799810
for (auto i = tailRing.size() - 1; i != -1; i--) {
800811
auto& message = tailRing.at(i);
801-
LogMessage::formatMessage(stream, message, color, timestamps);
812+
LogMessage::formatMessage(stream, message, color, this->timestamps);
802813
stream << '\n';
803814
}
804815
}
805816

806817
stream << Qt::flush;
807818

808-
if (!device->atEnd()) {
819+
if (this->file->pos() != readCursor) {
809820
qCritical() << "An error occurred parsing the end of this log file.";
810-
qCritical() << "Remaining data:" << device->readAll();
821+
qCritical() << "Remaining data:" << this->file->readAll();
822+
return false;
823+
}
824+
825+
return true;
826+
}
827+
828+
void LogFollower::FcntlWaitThread::run() {
829+
auto lock = flock {
830+
.l_type = F_RDLCK, // won't block other read locks when we take it
831+
.l_whence = SEEK_SET,
832+
.l_start = 0,
833+
.l_len = 0,
834+
};
835+
836+
auto r = fcntl(this->follower->reader->file->handle(), F_SETLKW, &lock); // NOLINT
837+
838+
if (r != 0) {
839+
qCWarning(logLogging).nospace()
840+
<< "Failed to wait for write locks to be removed from log file with error code " << errno
841+
<< ": " << qt_error_string();
842+
}
843+
}
844+
845+
bool LogFollower::follow() {
846+
QObject::connect(&this->waitThread, &QThread::finished, this, &LogFollower::onFileLocked);
847+
848+
QObject::connect(
849+
&this->fileWatcher,
850+
&QFileSystemWatcher::fileChanged,
851+
this,
852+
&LogFollower::onFileChanged
853+
);
854+
855+
this->fileWatcher.addPath(this->path);
856+
this->waitThread.start();
857+
858+
auto r = QCoreApplication::exec();
859+
return r == 0;
860+
}
861+
862+
void LogFollower::onFileChanged() {
863+
if (!this->reader->continueReading()) {
864+
QCoreApplication::exit(1);
865+
}
866+
}
867+
868+
void LogFollower::onFileLocked() {
869+
if (!this->reader->continueReading()) {
870+
QCoreApplication::exit(1);
871+
} else {
872+
QCoreApplication::exit(0);
873+
}
874+
}
875+
876+
bool readEncodedLogs(
877+
QFile* file,
878+
const QString& path,
879+
bool timestamps,
880+
int tail,
881+
bool follow,
882+
const QString& rulespec
883+
) {
884+
QList<QLoggingRule> rules;
885+
886+
{
887+
QLoggingSettingsParser parser;
888+
parser.setContent(rulespec);
889+
rules = parser.rules();
890+
}
891+
892+
auto reader = LogReader(file, timestamps, tail, rules);
893+
894+
if (!reader.initialize()) return false;
895+
if (!reader.continueReading()) return false;
896+
897+
if (follow) {
898+
auto follower = LogFollower(&reader, path);
899+
return follower.follow();
811900
}
812901

813902
return true;

src/core/logging.hpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include <qcontainerfwd.h>
66
#include <qdatetime.h>
7+
#include <qfile.h>
78
#include <qhash.h>
89
#include <qlatin1stringview.h>
910
#include <qlogging.h>
@@ -130,7 +131,14 @@ class LogManager: public QObject {
130131
LoggingThreadProxy threadProxy;
131132
};
132133

133-
bool readEncodedLogs(QIODevice* device, bool timestamps, int tail, const QString& rulespec);
134+
bool readEncodedLogs(
135+
QFile* file,
136+
const QString& path,
137+
bool timestamps,
138+
int tail,
139+
bool follow,
140+
const QString& rulespec
141+
);
134142

135143
} // namespace qs::log
136144

src/core/logging_p.hpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
#pragma once
2+
#include <utility>
3+
24
#include <qbytearrayview.h>
35
#include <qcontainerfwd.h>
46
#include <qfile.h>
7+
#include <qfilesystemwatcher.h>
58
#include <qlogging.h>
9+
#include <qobject.h>
10+
#include <qthread.h>
611
#include <qtmetamacros.h>
712
#include <qtypes.h>
813

914
#include "logging.hpp"
15+
#include "logging_qtprivate.hpp"
1016
#include "ringbuf.hpp"
1117

1218
namespace qs::log {
@@ -120,4 +126,64 @@ private slots:
120126
EncodedLogWriter detailedWriter;
121127
};
122128

129+
class LogFollower;
130+
131+
class LogReader {
132+
public:
133+
explicit LogReader(
134+
QFile* file,
135+
bool timestamps,
136+
int tail,
137+
QList<qt_logging_registry::QLoggingRule> rules
138+
)
139+
: file(file)
140+
, timestamps(timestamps)
141+
, remainingTail(tail)
142+
, rules(std::move(rules)) {}
143+
144+
bool initialize();
145+
bool continueReading();
146+
147+
private:
148+
QFile* file;
149+
EncodedLogReader reader;
150+
bool timestamps;
151+
int remainingTail;
152+
QHash<quint16, CategoryFilter> filters;
153+
QList<qt_logging_registry::QLoggingRule> rules;
154+
155+
friend class LogFollower;
156+
};
157+
158+
class LogFollower: public QObject {
159+
Q_OBJECT;
160+
161+
public:
162+
explicit LogFollower(LogReader* reader, QString path): reader(reader), path(std::move(path)) {}
163+
164+
bool follow();
165+
166+
private slots:
167+
void onFileChanged();
168+
void onFileLocked();
169+
170+
private:
171+
LogReader* reader;
172+
QString path;
173+
QFileSystemWatcher fileWatcher;
174+
175+
class FcntlWaitThread: public QThread {
176+
public:
177+
explicit FcntlWaitThread(LogFollower* follower): follower(follower) {}
178+
179+
protected:
180+
void run() override;
181+
182+
private:
183+
LogFollower* follower;
184+
};
185+
186+
FcntlWaitThread waitThread {this};
187+
};
188+
123189
} // namespace qs::log

src/core/logging_qtprivate.cpp

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,13 @@
1616
#include <qstringview.h>
1717
#include <qtypes.h>
1818

19+
#include "logging_qtprivate.hpp"
20+
1921
namespace qs::log {
2022
Q_DECLARE_LOGGING_CATEGORY(logLogging);
2123

2224
namespace qt_logging_registry {
2325

24-
class QLoggingRule {
25-
public:
26-
QLoggingRule();
27-
QLoggingRule(QStringView pattern, bool enabled);
28-
[[nodiscard]] int pass(QLatin1StringView categoryName, QtMsgType type) const;
29-
30-
enum PatternFlag {
31-
FullText = 0x1,
32-
LeftFilter = 0x2,
33-
RightFilter = 0x4,
34-
MidFilter = LeftFilter | RightFilter
35-
};
36-
Q_DECLARE_FLAGS(PatternFlags, PatternFlag)
37-
38-
QString category;
39-
int messageType;
40-
PatternFlags flags;
41-
bool enabled;
42-
43-
private:
44-
void parse(QStringView pattern);
45-
};
46-
4726
class QLoggingSettingsParser {
4827
public:
4928
void setContent(QStringView content);

src/core/logging_qtprivate.hpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#pragma once
2+
3+
// The logging rule parser from qloggingregistry_p.h and qloggingregistry.cpp.
4+
5+
// Was unable to properly link the functions when directly using the headers (which we depend
6+
// on anyway), so below is a slightly stripped down copy. Making the originals link would
7+
// be preferable.
8+
9+
#include <qflags.h>
10+
#include <qlogging.h>
11+
#include <qloggingcategory.h>
12+
#include <qstringview.h>
13+
14+
namespace qs::log {
15+
Q_DECLARE_LOGGING_CATEGORY(logLogging);
16+
17+
namespace qt_logging_registry {
18+
19+
class QLoggingRule {
20+
public:
21+
QLoggingRule();
22+
QLoggingRule(QStringView pattern, bool enabled);
23+
[[nodiscard]] int pass(QLatin1StringView categoryName, QtMsgType type) const;
24+
25+
enum PatternFlag {
26+
FullText = 0x1,
27+
LeftFilter = 0x2,
28+
RightFilter = 0x4,
29+
MidFilter = LeftFilter | RightFilter
30+
};
31+
Q_DECLARE_FLAGS(PatternFlags, PatternFlag)
32+
33+
QString category;
34+
int messageType;
35+
PatternFlags flags;
36+
bool enabled;
37+
38+
private:
39+
void parse(QStringView pattern);
40+
};
41+
42+
} // namespace qt_logging_registry
43+
44+
} // namespace qs::log

0 commit comments

Comments
 (0)