mirror of
https://github.com/aristocratos/btop.git
synced 2026-06-11 09:14:22 -04:00
Change log timestamps from the custom format to ISO 8601 with UTC
indicator:
Before: 2026-05-14 (10:31:47) | DEBUG: ...
After: 2026-05-14T10:31:47Z | DEBUG: ...
The previous format used parentheses around the time and omitted any
timezone indicator, making timestamps ambiguous. The logger uses
std::chrono::system_clock which measures time since Unix epoch (UTC
on all platforms), but the old format did not communicate this.
ISO 8601 with the Z suffix is an unambiguous, universally-recognized
format parseable by standard functions across languages:
- C++ (C++20): std::chrono::from_stream(ss, "%FT%TZ", tp)
- C: strptime(s, "%Y-%m-%dT%H:%M:%SZ", &tm)
- Python: datetime.fromisoformat("2026-05-14T10:31:47Z")
This matters for correlating btop log events with system logs (e.g.,
macOS pmset power events) that use local time with explicit timezone
offsets.
159 lines
3.6 KiB
C++
159 lines
3.6 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
#include "btop_log.hpp"
|
|
|
|
#include "btop_shared.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cerrno>
|
|
#include <chrono>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <ios>
|
|
#include <iterator>
|
|
#include <mutex>
|
|
#include <optional>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <system_error>
|
|
#include <utility>
|
|
|
|
#include <fmt/base.h>
|
|
#include <fmt/chrono.h>
|
|
#include <fmt/format.h>
|
|
#include <fmt/std.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
namespace Logger {
|
|
namespace {
|
|
constexpr auto ONE_MEGABYTE = 1024 << 10;
|
|
|
|
struct State {
|
|
std::mutex lock {};
|
|
std::once_flag print_header;
|
|
Level level = Level::ERROR;
|
|
std::optional<fs::path> path {};
|
|
};
|
|
|
|
[[nodiscard]] auto get_state() -> State& {
|
|
static State state;
|
|
return state;
|
|
}
|
|
|
|
auto rotate_log_file(fs::path& path) {
|
|
std::error_code errc {};
|
|
if (fs::exists(path, errc) && !errc && fs::file_size(path, errc) > ONE_MEGABYTE && !errc) {
|
|
auto old_path = path;
|
|
old_path += ".1";
|
|
if (fs::exists(old_path, errc) && !errc) {
|
|
fs::remove(old_path, errc);
|
|
}
|
|
if (!errc) {
|
|
fs::rename(path, old_path, errc);
|
|
}
|
|
}
|
|
return !errc;
|
|
}
|
|
|
|
class DropPrivilegeGuard {
|
|
private:
|
|
uid_t saved_euid {};
|
|
|
|
public:
|
|
DropPrivilegeGuard() {
|
|
saved_euid = geteuid();
|
|
auto real_uid = getuid();
|
|
if (saved_euid != real_uid && seteuid(real_uid) != 0) {
|
|
throw std::runtime_error(
|
|
fmt::format("Failed to drop privileges to write log file: {}", strerror(errno))
|
|
);
|
|
}
|
|
}
|
|
|
|
~DropPrivilegeGuard() noexcept {
|
|
if (saved_euid != geteuid()) {
|
|
// Silently drop error status.
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wunused-result"
|
|
seteuid(saved_euid);
|
|
#pragma GCC diagnostic pop
|
|
}
|
|
}
|
|
|
|
DropPrivilegeGuard(const DropPrivilegeGuard&) = delete;
|
|
DropPrivilegeGuard& operator=(const DropPrivilegeGuard&) = delete;
|
|
};
|
|
} // namespace
|
|
|
|
void init(const fs::path& path) {
|
|
auto& state = get_state();
|
|
std::lock_guard guard { state.lock };
|
|
state.path = std::make_optional(path);
|
|
}
|
|
|
|
void set_log_level(Level level) {
|
|
auto& state = get_state();
|
|
std::lock_guard guard { state.lock };
|
|
state.level = level;
|
|
}
|
|
|
|
void set_log_level(const std::string_view level) {
|
|
auto& state = get_state();
|
|
auto it = std::ranges::find(log_levels, level);
|
|
if (it != log_levels.end()) {
|
|
std::lock_guard guard { state.lock };
|
|
state.level = static_cast<Level>(std::distance(log_levels.begin(), it));
|
|
}
|
|
}
|
|
|
|
namespace detail {
|
|
[[nodiscard]] auto is_enabled(Level level) -> bool {
|
|
auto& state = get_state();
|
|
return state.level >= level && state.path.has_value();
|
|
}
|
|
|
|
void log_write(Level level, const std::string_view msg) {
|
|
auto& state = get_state();
|
|
|
|
auto guard = std::lock_guard { state.lock };
|
|
|
|
if (!is_enabled(level) || !state.path.has_value()) {
|
|
return;
|
|
}
|
|
|
|
std::string buffer {};
|
|
std::call_once(state.print_header, [&buffer]() {
|
|
fmt::format_to(std::back_inserter(buffer), "\n===> btop++ v{}\n", Global::Version);
|
|
});
|
|
auto now = std::chrono::system_clock::now();
|
|
auto seconds = std::chrono::time_point_cast<std::chrono::seconds>(now);
|
|
fmt::format_to(
|
|
std::back_inserter(buffer),
|
|
"{:%FT%T}Z | {}: {}\n",
|
|
seconds,
|
|
log_levels.at(std::to_underlying(level)),
|
|
msg
|
|
);
|
|
|
|
DropPrivilegeGuard privilege_guard {};
|
|
|
|
auto& path = state.path.value();
|
|
if (!rotate_log_file(path)) {
|
|
return;
|
|
}
|
|
|
|
auto file = std::ofstream { path, std::ios::app };
|
|
if (!file.is_open()) {
|
|
return;
|
|
}
|
|
file << buffer;
|
|
}
|
|
} // namespace detail
|
|
|
|
} // namespace Logger
|