#pragma once #include #include #include #include #include #include #include #include #include #include #include "logging.hpp" #include "logging_qtprivate.hpp" #include "ringbuf.hpp" namespace qs::log { enum EncodedLogOpcode : quint8 { RegisterCategory = 0, RecentMessageShort, RecentMessageLong, BeginCategories, }; enum CompressedLogType : quint8 { Debug = 0, Info = 1, Warn = 2, Critical = 3, }; CompressedLogType compressedTypeOf(QtMsgType type); QtMsgType typeOfCompressed(CompressedLogType type); class WriteBuffer { public: void setDevice(QIODevice* device); [[nodiscard]] bool hasDevice() const; [[nodiscard]] bool flush(); void writeBytes(const char* data, qsizetype length); void writeU8(quint8 data); void writeU16(quint16 data); void writeU32(quint32 data); void writeU64(quint64 data); private: QIODevice* device = nullptr; QByteArray buffer; }; class DeviceReader { public: void setDevice(QIODevice* device); [[nodiscard]] bool hasDevice() const; [[nodiscard]] bool readBytes(char* data, qsizetype length); // peek UP TO length [[nodiscard]] qsizetype peekBytes(char* data, qsizetype length); [[nodiscard]] bool skip(qsizetype length); [[nodiscard]] bool readU8(quint8* data); [[nodiscard]] bool readU16(quint16* data); [[nodiscard]] bool readU32(quint32* data); [[nodiscard]] bool readU64(quint64* data); private: QIODevice* device = nullptr; }; class EncodedLogWriter { public: void setDevice(QIODevice* target); [[nodiscard]] bool writeHeader(); [[nodiscard]] bool write(const LogMessage& message); private: void writeOp(EncodedLogOpcode opcode); void writeVarInt(quint32 n); void writeString(QByteArrayView bytes); quint16 getOrCreateCategory(QLatin1StringView category); WriteBuffer buffer; QHash categories; quint16 nextCategory = EncodedLogOpcode::BeginCategories; QDateTime lastMessageTime = QDateTime::fromSecsSinceEpoch(0); HashBuffer recentMessages {256}; }; class EncodedLogReader { public: void setDevice(QIODevice* source); [[nodiscard]] bool readHeader(bool* success, quint8* logVersion, quint8* readerVersion); // WARNING: log messages written to the given slot are invalidated when the log reader is destroyed. [[nodiscard]] bool read(LogMessage* slot); [[nodiscard]] CategoryFilter categoryFilterById(quint16 id); private: [[nodiscard]] bool readVarInt(quint32* slot); [[nodiscard]] bool readString(QByteArray* slot); [[nodiscard]] bool registerCategory(); DeviceReader reader; QVector> categories; QDateTime lastMessageTime = QDateTime::fromSecsSinceEpoch(0); RingBuffer recentMessages {256}; }; class ThreadLogging: public QObject { Q_OBJECT; public: explicit ThreadLogging(QObject* parent): QObject(parent) {} void init(); void initFs(); void setupFileLogging(); private slots: void onMessage(const LogMessage& msg, bool showInSparse); private: QFile* file = nullptr; QTextStream fileStream; QFile* detailedFile = nullptr; EncodedLogWriter detailedWriter; }; class LogFollower; class LogReader { public: explicit LogReader( QFile* file, bool timestamps, int tail, QList 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 filters; QList 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