chore(vendor): update vendored libenderlog to v0.0.3
Dated record timestamps, per-sink timestamp format, and archive names that keep the extension last (<stem>.<timestamp>.<ext>).
This commit is contained in:
+46
-24
@@ -54,6 +54,9 @@ export namespace ender::log {
|
||||
return "?";
|
||||
}
|
||||
|
||||
/** Default timestamp rendered on a record, chrono format syntax. */
|
||||
inline constexpr std::string_view default_time_format{"%Y-%m-%d %H:%M:%S"};
|
||||
|
||||
/** Logger configuration. */
|
||||
struct options {
|
||||
/** Records below this level are dropped before anything is built. */
|
||||
@@ -90,15 +93,32 @@ export namespace ender::log {
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
/**
|
||||
* Render a time point with a runtime chrono format string.
|
||||
*
|
||||
* `std::format`'s format string is compile-time only, so the spec is
|
||||
* wrapped in a replacement field and fed to `std::vformat`; a bare spec
|
||||
* would be read as literal text rather than a chrono conversion.
|
||||
*/
|
||||
[[nodiscard]] inline auto format_time(const std::chrono::system_clock::time_point time,
|
||||
const std::string_view time_format) -> std::string {
|
||||
const auto moment = std::chrono::floor<std::chrono::seconds>(time);
|
||||
const auto pattern = std::string{"{:"}.append(time_format).append("}");
|
||||
return std::vformat(pattern, std::make_format_args(moment));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render one record as a human-readable block: a header line and, when
|
||||
* present, the indented stack frames. Shared by the stream sinks.
|
||||
*
|
||||
* @param time_format A chrono format string applied to the record's
|
||||
* timestamp; defaults to the date and time of day.
|
||||
*/
|
||||
[[nodiscard]] inline auto format_record(const record &entry) -> std::string {
|
||||
auto text = std::format("[{:%H:%M:%S}] {:<8} {}",
|
||||
std::chrono::floor<std::chrono::seconds>(entry.time),
|
||||
to_string(entry.severity),
|
||||
entry.message);
|
||||
[[nodiscard]] inline auto format_record(const record &entry,
|
||||
const std::string_view time_format = default_time_format)
|
||||
-> std::string {
|
||||
const auto stamp = format_time(entry.time, time_format);
|
||||
auto text = std::format("[{}] {:<8} {}", stamp, to_string(entry.severity), entry.message);
|
||||
if (!entry.file.empty()) {
|
||||
text += std::format(" ({}:{})", entry.file, entry.line);
|
||||
}
|
||||
@@ -125,15 +145,18 @@ export namespace ender::log {
|
||||
/** Writes a human-readable line per record to a stream (stderr by default). */
|
||||
class console_sink final: public sink {
|
||||
public:
|
||||
explicit console_sink(std::ostream &stream = std::cerr): stream_(&stream) {}
|
||||
explicit console_sink(std::ostream &stream = std::cerr,
|
||||
std::string time_format = std::string{default_time_format})
|
||||
: stream_(&stream), time_format_(std::move(time_format)) {}
|
||||
|
||||
auto write(const record &entry) -> void override {
|
||||
*stream_ << detail::format_record(entry);
|
||||
*stream_ << detail::format_record(entry, time_format_);
|
||||
stream_->flush();
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostream *stream_;
|
||||
std::string time_format_;
|
||||
};
|
||||
|
||||
/** Keeps every record in memory; useful for tests and in-game consoles. */
|
||||
@@ -174,6 +197,10 @@ export namespace ender::log {
|
||||
std::size_t max_file_size{0};
|
||||
/** Keep at most this many archives, dropping the oldest first; 0 keeps them all. */
|
||||
std::size_t max_archives{0};
|
||||
/** Timestamp format used on each record's header line (chrono syntax). */
|
||||
std::string timestamp_format{std::string{default_time_format}};
|
||||
/** Chrono format for the timestamp inserted into archive names. */
|
||||
std::string archive_time_format{"%Y%m%d-%H%M%S"};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -182,8 +209,9 @@ export namespace ender::log {
|
||||
* `path` is the active file. When the sink opens it and the file already
|
||||
* holds data, that file is renamed to a timestamped archive first, so a run
|
||||
* never appends onto a previous run's log: every start begins a fresh file
|
||||
* and the old one is preserved with the timestamp before its extension, as
|
||||
* `<stem>.<YYYYmmdd-HHMMSS>.log`. The same
|
||||
* and the old one is preserved as `<stem>.<timestamp><extension>` (`.log`
|
||||
* when the active file has no extension), where the timestamp is rendered by
|
||||
* `file_options::archive_time_format` (default `<YYYYmmdd-HHMMSS>`). The same
|
||||
* happens mid-run once the active file passes `file_options::max_file_size`.
|
||||
* `file_options::max_archives` bounds how many archives are kept.
|
||||
*
|
||||
@@ -206,7 +234,7 @@ export namespace ender::log {
|
||||
}
|
||||
|
||||
auto write(const record &entry) -> void override {
|
||||
const auto block = detail::format_record(entry);
|
||||
const auto block = detail::format_record(entry, options_.timestamp_format);
|
||||
// Rotate before writing, but never rotate an empty file: that would
|
||||
// archive nothing and lose the record that is about to be written.
|
||||
if (options_.max_file_size > 0 && size_ > 0 && size_ + block.size() > options_.max_file_size) {
|
||||
@@ -237,23 +265,17 @@ export namespace ender::log {
|
||||
|
||||
auto archive_current() -> void {
|
||||
if (stream_.is_open()) stream_.close();
|
||||
const auto stamp = std::format("{:%Y%m%d-%H%M%S}",
|
||||
std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now()));
|
||||
// Keep the original extension last, with the timestamp in the
|
||||
// middle: `<stem>.<stamp>[.<n>]<ext>`.
|
||||
const auto name = [&](const std::size_t counter) {
|
||||
auto candidate = path_.parent_path() / path_.stem();
|
||||
candidate += ".";
|
||||
candidate += stamp;
|
||||
if (counter > 0) candidate += std::format(".{}", counter);
|
||||
candidate += path_.extension();
|
||||
return candidate;
|
||||
};
|
||||
auto archive = name(0);
|
||||
const auto stamp = detail::format_time(std::chrono::system_clock::now(), options_.archive_time_format);
|
||||
// Keep the extension last: `<stem>.<timestamp><extension>`, falling
|
||||
// back to `.log` when the active file has none.
|
||||
const auto stem = path_.stem().string();
|
||||
const auto extension = path_.has_extension() ? path_.extension().string() : std::string{".log"};
|
||||
auto archive = path_.parent_path() / (stem + "." + stamp + extension);
|
||||
// Two rotations can land in the same second; disambiguate with a
|
||||
// counter rather than overwrite the earlier archive.
|
||||
for (auto counter = 1; std::filesystem::exists(archive); ++counter) {
|
||||
archive = name(counter);
|
||||
archive = path_.parent_path() /
|
||||
(stem + "." + stamp + "." + std::to_string(counter) + extension);
|
||||
}
|
||||
std::filesystem::rename(path_, archive);
|
||||
archives_.push_back(archive);
|
||||
|
||||
Reference in New Issue
Block a user