99 lines
4.1 KiB
C++
99 lines
4.1 KiB
C++
#pragma once
|
|
|
|
#include "fgc/Logger.h"
|
|
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace fgc {
|
|
|
|
// A rotating, size-capped log file for the tower's own operation.
|
|
//
|
|
// A tower runs detached for days inside a tmux session, and in TUI mode every
|
|
// log line goes into a 500-line ring buffer on screen and nowhere else - so the
|
|
// record of what it did dies with the session. That is the one thing you want
|
|
// when somebody asks why an axis is not moving.
|
|
//
|
|
// This is deliberately a plain appender rather than anything clever:
|
|
//
|
|
// * It runs INSIDE Logger's lock. Logger holds a non-recursive mutex while it
|
|
// calls the sink, so every method here MUST be free of LOG_* - one logging
|
|
// call from this file deadlocks the whole process. Failures are recorded and
|
|
// reported through failed()/lastError(), which the caller polls from OUTSIDE
|
|
// the lock. That constraint is why paths::writeLogFile() is not reused: it
|
|
// LOG_WARNs on failure, and it truncates rather than appends.
|
|
// * It flushes every line but never fsyncs. A crashed process keeps everything
|
|
// (the bytes are already in the page cache); only a power cut loses the tail.
|
|
// fsync per line on a tower's eMMC would be far worse than that trade.
|
|
// * Rotation is by size, checked before each write. Files are pruned by count,
|
|
// oldest first.
|
|
struct FileLogPolicy {
|
|
// Empty => paths::defaultLogDir().
|
|
std::string dir;
|
|
// Rotate once a write would push the file past this. 0 disables rotation.
|
|
long long max_bytes = 20LL * 1024 * 1024;
|
|
// How many <prefix>_*.log to keep in `dir`, counting every run's files and
|
|
// every rotation of them. Oldest are deleted. 0 disables pruning.
|
|
int keep_files = 10;
|
|
// File name prefix. Deliberately NOT "test": Application::latestPriorTestReport()
|
|
// scans this same directory for test_*.log and would otherwise pick up ours.
|
|
std::string prefix = "fgc";
|
|
};
|
|
|
|
class FileLogSink {
|
|
public:
|
|
FileLogSink() = default;
|
|
~FileLogSink();
|
|
FileLogSink(const FileLogSink&) = delete;
|
|
FileLogSink& operator=(const FileLogSink&) = delete;
|
|
|
|
// Create the directory if needed and open a fresh file for this run. `header`
|
|
// is written first and re-written after every rotation, so each file on disk
|
|
// says for itself which build and tower produced it. Returns false and sets
|
|
// failed() if the file cannot be opened.
|
|
bool open(const FileLogPolicy& policy, const std::string& header);
|
|
|
|
// Append one already-formatted Logger line (no trailing newline, and it
|
|
// begins "HH:MM:SS.mmm"). A date is prepended here rather than in Logger,
|
|
// because the on-screen panes are width-constrained and a file that may span
|
|
// days is the only place the date is actually needed.
|
|
//
|
|
// `cat` distinguishes a wire-trace line from an ordinary one; the caller uses
|
|
// it to decide routing, and it is recorded here only for completeness.
|
|
// Never throws, never logs. A no-op once failed().
|
|
void write(LogLevel level, LogCat cat, const std::string& line);
|
|
|
|
void close();
|
|
|
|
bool isOpen() const { return fp_ != nullptr; }
|
|
std::string path() const { return path_; }
|
|
// Set when an open, write or rotate failed. The sink then stops writing
|
|
// rather than retrying every line. Poll this from outside Logger's lock.
|
|
bool failed() const { return failed_; }
|
|
std::string lastError() const { return last_error_; }
|
|
|
|
// Exposed for tests: the files this policy would prune, newest first.
|
|
static std::vector<std::string> existingFiles(const FileLogPolicy& policy);
|
|
|
|
private:
|
|
std::string segmentPath(int n) const;
|
|
bool openFile(const std::string& path);
|
|
void rotate();
|
|
void prune();
|
|
void fail(const std::string& what);
|
|
|
|
std::FILE* fp_ = nullptr;
|
|
FileLogPolicy policy_;
|
|
std::string header_;
|
|
std::string path_;
|
|
std::string base_; // path without the ".<NNN>.log" suffix
|
|
long long bytes_ = 0;
|
|
long long lines_ = 0; // lines in the CURRENT file; guards rotation loops
|
|
int rotation_ = 0;
|
|
bool failed_ = false;
|
|
std::string last_error_;
|
|
};
|
|
|
|
} // namespace fgc
|