nf7/file/system_logger.cc

270 lines
7.3 KiB
C++

#include <cinttypes>
#include <deque>
#include <memory>
#include <typeinfo>
#include <utility>
#include <iostream>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/generic_type_info.hh"
#include "common/gui_window.hh"
#include "common/logger.hh"
#include "common/logger_pool.hh"
#include "common/ptr_selector.hh"
using namespace std::literals;
namespace nf7 {
namespace {
class Logger final : public File,
public nf7::DirItem,
public nf7::Logger {
public:
static inline const GenericTypeInfo<Logger> kType = {"System/Logger", {"DirItem"}};
struct Row final {
public:
File::Id file;
std::source_location srcloc;
const char* level;
std::string msg;
std::string path;
std::string location;
std::string Stringify() const noexcept {
std::stringstream st;
st << level << "\n";
st << " " << msg << "\n";
st << " from " << path << "\n";
st << " at " << location;
return st.str();
}
};
Logger(Env& env, uint32_t max_rows = 1024, bool propagate = false, bool freeze = false) noexcept :
File(kType, env), DirItem(DirItem::kMenu),
propagation_pool_(*this, 1), win_(*this, "LogView System/Logger"),
max_rows_(max_rows), propagate_(propagate), freeze_(freeze) {
win_.shown() = true;
}
Logger(Env& env, Deserializer& ar) : Logger(env) {
ar(win_, max_rows_, propagate_, freeze_);
if (max_rows_ == 0) {
throw DeserializeException("max_rows must be 1 or more");
}
}
void Serialize(Serializer& ar) const noexcept override {
ar(win_, max_rows_, propagate_, freeze_);
}
std::unique_ptr<File> Clone(Env& env) const noexcept override {
return std::make_unique<Logger>(env, max_rows_, propagate_, freeze_);
}
void Update() noexcept override;
void UpdateMenu() noexcept override;
void UpdateRowMenu(const Row&) noexcept;
void Write(Item&& item) noexcept override {
if (freeze_) return;
if (rows_.size() >= max_rows_) rows_.pop_front();
if (propagate_) {
propagation_pool_.Write(Item(item));
}
Row row = {
.file = item.file,
.srcloc = item.srcloc,
.level = GetLevelString(item.level),
.msg = std::move(item.msg),
.path = GetPathString(item.file),
.location = GetLocationString(item.srcloc),
};
rows_.push_back(std::move(row));
updated_ = true;
}
File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector<nf7::DirItem, nf7::Logger>(t).Select(this);
}
private:
std::deque<Row> rows_;
const char* popup_ = nullptr;
LoggerPool propagation_pool_;
bool updated_ = false;
// persistent params
gui::Window win_;
uint32_t max_rows_;
bool propagate_;
bool freeze_;
void DropExceededRows() noexcept {
if (rows_.size() <= max_rows_) return;
rows_.erase(rows_.begin(), rows_.end()-max_rows_);
}
std::string GetPathString(File::Id id) const noexcept
try {
return env().GetFile(id).abspath().Stringify();
} catch (File::NotFoundException&) {
return "[EXPIRED]";
}
static const char* GetLevelString(Level lv) noexcept {
switch (lv) {
case kTrace:
return "TRAC";
case kInfo:
return "INFO";
case kWarn:
return "WARN";
case kError:
return "ERRR";
default:
assert(false);
return "X(";
}
}
static std::string GetLocationString(const std::source_location loc) noexcept {
return loc.file_name()+":"s+loc.function_name()+":"s+std::to_string(loc.line());
}
};
void Logger::Update() noexcept {
if (const auto name = std::exchange(popup_, nullptr)) {
ImGui::OpenPopup(name);
}
const auto em = ImGui::GetFontSize();
// config popup
if (ImGui::BeginPopup("ConfigPopup")) {
ImGui::TextUnformatted("System/Logger Config");
ImGui::Spacing();
static const uint32_t kMinRows = 1, kMaxRows = 1024*1024;
if (ImGui::DragScalar("max rows", ImGuiDataType_U32, &max_rows_, 1, &kMinRows, &kMaxRows)) {
DropExceededRows();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("the oldest row is dropped when exceed");
}
ImGui::Checkbox("propagate", &propagate_);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("after handling, passes the msg to outer logger if exists");
}
ImGui::Checkbox("freeze", &freeze_);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("stop handling except propagation");
}
ImGui::EndPopup();
}
// LogView
const auto kInit = [em]() {
ImGui::SetNextWindowSize({48*em, 16*em}, ImGuiCond_FirstUseEver);
};
if (win_.Begin(kInit)) {
constexpr auto kTableFlags =
ImGuiTableFlags_Resizable |
ImGuiTableFlags_Hideable |
ImGuiTableFlags_RowBg |
ImGuiTableFlags_Borders |
ImGuiTableFlags_ContextMenuInBody |
ImGuiTableFlags_SizingStretchProp |
ImGuiTableFlags_ScrollY;
if (ImGui::BeginTable("logs", 4, kTableFlags, ImGui::GetContentRegionAvail(), 0)) {
const bool autoscroll =
std::exchange(updated_, false) && ImGui::GetScrollY() == ImGui::GetScrollMaxY();
ImGui::TableSetupColumn("level");
ImGui::TableSetupColumn("msg");
ImGui::TableSetupColumn("path");
ImGui::TableSetupColumn("location");
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableHeadersRow();
for (const auto& row : rows_) {
ImGui::TableNextRow();
ImGui::PushID(&row);
if (autoscroll && &row == &rows_.back()) {
ImGui::SetScrollHereY();
}
// level column
if (ImGui::TableSetColumnIndex(0)) {
constexpr auto kFlags =
ImGuiSelectableFlags_SpanAllColumns |
ImGuiSelectableFlags_AllowItemOverlap;
ImGui::Selectable(row.level, false, kFlags);
if (ImGui::BeginPopupContextItem()) {
UpdateRowMenu(row);
ImGui::EndPopup();
}
}
// msg column
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted(row.msg.c_str());
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(row.msg.c_str());
}
}
// path column
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted(row.path.c_str());
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(row.path.c_str());
}
}
// location column
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted(row.location.c_str());
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("file: %s", row.srcloc.file_name());
ImGui::Text("func: %s", row.srcloc.function_name());
ImGui::Text("line: %zu", static_cast<size_t>(row.srcloc.line()));
ImGui::Text("col : %zu", static_cast<size_t>(row.srcloc.column()));
ImGui::EndTooltip();
}
}
ImGui::PopID();
}
ImGui::EndTable();
}
}
win_.End();
}
void Logger::UpdateMenu() noexcept {
ImGui::MenuItem("shown", nullptr, &win_.shown());
if (ImGui::MenuItem("config")) {
popup_ = "ConfigPopup";
}
}
void Logger::UpdateRowMenu(const Row& row) noexcept {
if (ImGui::MenuItem("copy as text")) {
ImGui::SetClipboardText(row.Stringify().c_str());
}
}
}
} // namespace nf7