270 lines
7.3 KiB
C++
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
|