mirror of
https://github.com/aristocratos/btop.git
synced 2026-02-20 21:53:19 +02:00
This will only allocate the log message if a log file exists and the log level is enabled. The interface forwards it's arguments to fmt::format. This gets also rid of some global state and hides it in the btop_log.cpp translation unit. Closes: https://github.com/aristocratos/btop/issues/1347
156 lines
3.5 KiB
C++
156 lines
3.5 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.
|
|
seteuid(saved_euid);
|
|
}
|
|
}
|
|
|
|
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),
|
|
"{:%F (%T)} | {}: {}\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
|