#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