Files
ObsidianDragon/src/util/logger.cpp
DanS 207f9074db feat(diagnostics): make the logging + crash infrastructure actually work (W7-2, W7-3, W7-4)
The Foundation tier — answers the original "make it easier to diagnose problems" ask.

- W7-2 (keystone): the app-level Logger file sink was never initialized, so LOG/LOGF/
  VERBOSE_LOGF went nowhere and dragonx-debug.log didn't exist on Linux/macOS at all.
  main() now calls Logger::init(<config>/dragonx-debug.log) on every platform. Fixed a
  latent deadlock this exposed: init() wrote its banner via write(), which re-locks the
  non-recursive mutex_ it already holds — now written directly. On Windows the raw
  stdout/stderr freopen moved to a separate dragonx-stdout.log so the two writers don't
  contend on one file. Added testLoggerFileSink (also a deadlock guard — it would hang if
  the fix regressed).

- W7-3: no crash handler existed on Linux/macOS. Added an async-signal-safe sigaction
  handler (SIGSEGV/ABRT/BUS/FPE/ILL) that writes the signal id + a backtrace_symbols_fd
  backtrace to dragonx-crash.log, then re-raises the default disposition for a core dump —
  the POSIX counterpart of the Windows SEH filter.

- W7-4: Logger::init now rotates the log to a single .1 backup past 10 MB, so a long or
  verbose session can't grow it unbounded.

Build-clean; ctest 1/1. Remaining Foundation: the QoL bundle (mostly UI). See
docs/wallet-hardening.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 15:31:23 -05:00

129 lines
3.3 KiB
C++

// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "logger.h"
#include <cstdarg>
#include <cstdint>
#include <ctime>
#include <chrono>
#include <iomanip>
#include <sstream>
#include <filesystem>
namespace dragonx {
namespace util {
Logger::Logger() = default;
Logger::~Logger()
{
if (file_.is_open()) {
file_.close();
}
}
Logger& Logger::instance()
{
static Logger logger;
return logger;
}
bool Logger::init(const std::string& path)
{
std::lock_guard<std::mutex> lock(mutex_);
if (file_.is_open()) {
file_.close();
}
// W7-4: cap the log's growth — if the existing file is already large, rotate it to a single .1
// backup before reopening in append mode, so a long-lived or verbose session can't grow it
// without bound.
{
std::error_code ec;
const auto sz = std::filesystem::file_size(path, ec);
constexpr std::uintmax_t kMaxLogBytes = 10ull * 1024ull * 1024ull; // 10 MB
if (!ec && sz > kMaxLogBytes) {
std::filesystem::rename(path, path + ".1", ec); // replaces any previous .1 backup
if (ec) std::filesystem::remove(path, ec); // fall back to truncation if rename fails
}
}
file_.open(path, std::ios::out | std::ios::app);
initialized_ = file_.is_open();
if (initialized_) {
// Write the banner directly, NOT via write(): write() re-locks the non-recursive mutex_ we
// already hold here, which would deadlock (latent — init() was previously never called, W7-2).
file_ << "=== Logger initialized ===" << std::endl;
file_.flush();
}
return initialized_;
}
void Logger::write(const std::string& message)
{
std::lock_guard<std::mutex> lock(mutex_);
// Get current timestamp
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()) % 1000;
std::stringstream ss;
// Reachable from worker/monitor threads — std::localtime shares a process-wide static tm, so use the
// reentrant variant into a local tm (the logger's own mutex can't protect other localtime callers).
std::tm tmv{};
#ifdef _WIN32
localtime_s(&tmv, &time);
#else
localtime_r(&time, &tmv);
#endif
ss << std::put_time(&tmv, "%Y-%m-%d %H:%M:%S");
ss << '.' << std::setfill('0') << std::setw(3) << ms.count();
ss << " | " << message;
std::string line = ss.str();
// Write to file if open
if (file_.is_open()) {
file_ << line << std::endl;
file_.flush();
}
// Also write to stdout in debug mode
#ifdef DRAGONX_DEBUG
printf("%s\n", line.c_str());
#endif
// Forward to callback (e.g. ConsoleTab) with the raw message
if (callback_) {
callback_(message);
}
}
void Logger::writef(const char* format, ...)
{
char buffer[4096];
va_list args;
va_start(args, format);
vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
write(buffer);
}
void Logger::setCallback(std::function<void(const std::string&)> cb)
{
std::lock_guard<std::mutex> lock(mutex_);
callback_ = std::move(cb);
}
} // namespace util
} // namespace dragonx