add System/Logger
This commit is contained in:
parent
c0e6332ba8
commit
3d44fbb9d3
@ -44,11 +44,14 @@ target_sources(nf7
|
||||
|
||||
common/dir.hh
|
||||
common/gui_window.hh
|
||||
common/logger.hh
|
||||
common/logger_pool.hh
|
||||
common/queue.hh
|
||||
common/type_info.hh
|
||||
common/yas.hh
|
||||
|
||||
file/system_dir.cc
|
||||
file/system_logger.cc
|
||||
)
|
||||
target_link_libraries(nf7
|
||||
PRIVATE
|
||||
|
49
common/file_ref.hh
Normal file
49
common/file_ref.hh
Normal file
@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class FileRef final {
|
||||
public:
|
||||
FileRef() = delete;
|
||||
FileRef(File& owner) noexcept : owner_(&owner) {
|
||||
}
|
||||
FileRef(File& owner, File::Path&& p, File::Id id = 0) noexcept :
|
||||
owner_(&owner), path_(std::move(p)), id_(id) {
|
||||
}
|
||||
FileRef(File& owner, File::Id id) noexcept : owner_(&owner), id_(id) {
|
||||
}
|
||||
FileRef(const FileRef&) = default;
|
||||
FileRef(FileRef&&) = default;
|
||||
FileRef& operator=(const FileRef&) = default;
|
||||
FileRef& operator=(FileRef&&) = default;
|
||||
|
||||
File& operator*() const {
|
||||
try {
|
||||
return owner_->env().GetFile(id_);
|
||||
} catch (ExpiredException&) {
|
||||
if (!path_) throw;
|
||||
}
|
||||
auto& ret = owner_->Resolve(*path_);
|
||||
const_cast<File::Id&>(id_) = ret.id();
|
||||
return ret;
|
||||
}
|
||||
const File::Path& path() const noexcept {
|
||||
assert(path_);
|
||||
return *path_;
|
||||
}
|
||||
|
||||
private:
|
||||
File* file_;
|
||||
|
||||
std::optional<File::Path> path_;
|
||||
|
||||
File::Id id_ = 0;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
44
common/logger.hh
Normal file
44
common/logger.hh
Normal file
@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <source_location.hh>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class Logger : public File::Interface {
|
||||
public:
|
||||
enum Level {
|
||||
kTrace,
|
||||
kInfo,
|
||||
kWarn,
|
||||
kError,
|
||||
};
|
||||
struct Item;
|
||||
|
||||
Logger() = default;
|
||||
|
||||
virtual void Write(Item&&) noexcept = 0;
|
||||
};
|
||||
struct Logger::Item final {
|
||||
public:
|
||||
Item(Level lv, std::string_view m, File::Id f = 0, std::source_location s = std::source_location::current()) noexcept :
|
||||
level(lv), msg(m), file(f), srcloc(s) {
|
||||
}
|
||||
Item(const Item&) = default;
|
||||
Item(Item&&) = default;
|
||||
Item& operator=(const Item&) = default;
|
||||
Item& operator=(Item&&) = default;
|
||||
|
||||
Level level;
|
||||
std::string msg;
|
||||
|
||||
File::Id file;
|
||||
std::source_location srcloc;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
79
common/logger_pool.hh
Normal file
79
common/logger_pool.hh
Normal file
@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/logger.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class LoggerPool : public Logger {
|
||||
public:
|
||||
static constexpr size_t kMaxPool = 1024;
|
||||
static constexpr auto kDefaultLoggerName = "_logger";
|
||||
|
||||
LoggerPool(File& owner,
|
||||
size_t search_min_dist = 0,
|
||||
std::string_view name = kDefaultLoggerName) noexcept :
|
||||
owner_(&owner), search_min_dist_(search_min_dist), logger_name_(name) {
|
||||
}
|
||||
~LoggerPool() noexcept {
|
||||
assert(items_.empty());
|
||||
}
|
||||
LoggerPool(const LoggerPool&) = delete;
|
||||
LoggerPool(LoggerPool&&) = delete;
|
||||
LoggerPool& operator=(const LoggerPool&) = delete;
|
||||
LoggerPool& operator=(LoggerPool&&) = delete;
|
||||
|
||||
void Write(Logger::Item&& item) noexcept override {
|
||||
if (items_.size() >= kMaxPool) return;
|
||||
items_.push_back(std::move(item));
|
||||
}
|
||||
void Flush() noexcept
|
||||
try {
|
||||
if (items_.empty()) return;
|
||||
auto& logger = owner_->
|
||||
ancestorOrThrow(search_min_dist_).
|
||||
ResolveUpwardOrThrow(kDefaultLoggerName).
|
||||
ifaceOrThrow<nf7::Logger>();
|
||||
for (auto& item : items_) {
|
||||
item.file = owner_->id();
|
||||
logger.Write(std::move(item));
|
||||
}
|
||||
items_.clear();
|
||||
} catch (File::NotFoundException&) {
|
||||
} catch (File::NotImplementedException&) {
|
||||
}
|
||||
|
||||
private:
|
||||
File* const owner_;
|
||||
|
||||
size_t search_min_dist_;
|
||||
|
||||
std::string logger_name_;
|
||||
|
||||
std::vector<Item> items_;
|
||||
};
|
||||
|
||||
class LoggerSyncPool : private LoggerPool {
|
||||
public:
|
||||
using LoggerPool::LoggerPool;
|
||||
|
||||
void Write(Logger::Item&& item) noexcept override {
|
||||
std::unique_lock<std::mutex> _(mtx_);
|
||||
LoggerPool::Write(std::move(item));
|
||||
}
|
||||
using LoggerPool::Flush;
|
||||
|
||||
private:
|
||||
std::mutex mtx_;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
261
file/system_logger.cc
Normal file
261
file/system_logger.cc
Normal file
@ -0,0 +1,261 @@
|
||||
#include <cinttypes>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <typeinfo>
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/dir.hh"
|
||||
#include "common/gui_window.hh"
|
||||
#include "common/logger.hh"
|
||||
#include "common/logger_pool.hh"
|
||||
#include "common/ptr_selector.hh"
|
||||
#include "common/type_info.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* iface(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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
|
||||
if (win_.Begin()) {
|
||||
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
|
@ -43,7 +43,10 @@ int main(void) {
|
||||
|
||||
ar("Dir"s);
|
||||
ar(std::map<std::string, L> {
|
||||
{ "home"s, Write(ar, "Dir"s, std::map<std::string, L> {}, WINDOW_(false)) },
|
||||
{ "_logger"s,
|
||||
Write(ar, "Logger"s, WINDOW_(true), 1024, false, false) },
|
||||
{ "home"s,
|
||||
Write(ar, "Dir"s, std::map<std::string, L> {}, WINDOW_(false)) },
|
||||
}, WINDOW_(true));
|
||||
|
||||
const auto buf = os.get_shared_buffer();
|
||||
|
Loading…
x
Reference in New Issue
Block a user