mirror of
https://github.com/ClusterM/open-bamboo-networking.git
synced 2026-10-22 11:02:26 -05:00
Agent-Logs-Url: https://github.com/ClusterM/open-bambu-networking/sessions/7492e612-ac24-4d3d-800d-4ef00350b46c Co-authored-by: ClusterM <4236181+ClusterM@users.noreply.github.com>
297 lines
9.8 KiB
C++
297 lines
9.8 KiB
C++
#include "source_log.hpp"
|
|
|
|
#include "obn/os_compat.hpp"
|
|
|
|
#include <cctype>
|
|
#include <chrono>
|
|
#include <cstdarg>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <ctime>
|
|
#include <string>
|
|
|
|
#if defined(_WIN32)
|
|
# ifndef WIN32_LEAN_AND_MEAN
|
|
# define WIN32_LEAN_AND_MEAN
|
|
# endif
|
|
# ifndef NOMINMAX
|
|
# define NOMINMAX
|
|
# endif
|
|
# include <windows.h>
|
|
#endif
|
|
|
|
namespace obn::source {
|
|
|
|
void noop_logger(void*, int, obn_tchar const*) {}
|
|
|
|
namespace {
|
|
|
|
// Allocate a heap copy of `buf` in the platform-native character width
|
|
// for the Logger callback. The caller -- gstbambusrc on Linux,
|
|
// wxMediaCtrl2 on Windows -- frees it via Bambu_FreeLogMsg, which in
|
|
// turn calls free(). On POSIX we hand back UTF-8 unchanged; on Windows
|
|
// we transcode UTF-8 -> UTF-16 once, since Studio's wide-string
|
|
// callback would otherwise see mojibake for any multi-byte input.
|
|
obn_tchar* strdup_for_logger(const char* buf)
|
|
{
|
|
#if defined(_WIN32)
|
|
int wlen = ::MultiByteToWideChar(CP_UTF8, 0, buf, -1, nullptr, 0);
|
|
if (wlen <= 0) {
|
|
// On a bogus UTF-8 sequence return an empty wide string rather
|
|
// than nullptr -- the Studio side dereferences without checking.
|
|
auto* empty = static_cast<wchar_t*>(std::malloc(sizeof(wchar_t)));
|
|
if (empty) empty[0] = L'\0';
|
|
return empty;
|
|
}
|
|
auto* w = static_cast<wchar_t*>(
|
|
std::malloc(static_cast<std::size_t>(wlen) * sizeof(wchar_t)));
|
|
if (!w) return nullptr;
|
|
::MultiByteToWideChar(CP_UTF8, 0, buf, -1, w, wlen);
|
|
return w;
|
|
#else
|
|
return ::strdup(buf);
|
|
#endif
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace {
|
|
|
|
LogLevel parse_log_level(const char* s, LogLevel fallback)
|
|
{
|
|
if (!s || !*s) return fallback;
|
|
auto eq = [&](const char* a) {
|
|
for (size_t i = 0;; ++i) {
|
|
char x = s[i];
|
|
char y = a[i];
|
|
if (x >= 'A' && x <= 'Z') x = static_cast<char>(x - 'A' + 'a');
|
|
if (x != y) return false;
|
|
if (!x) return true;
|
|
}
|
|
};
|
|
if (eq("trace")) return LL_TRACE;
|
|
if (eq("debug")) return LL_DEBUG;
|
|
if (eq("info")) return LL_INFO;
|
|
if (eq("warn") || eq("warning")) return LL_WARN;
|
|
if (eq("error") || eq("err")) return LL_ERROR;
|
|
if (eq("off") || eq("none") || eq("silent") || eq("0")) return LL_OFF;
|
|
return fallback;
|
|
}
|
|
|
|
const char* level_tag(LogLevel lvl)
|
|
{
|
|
switch (lvl) {
|
|
case LL_TRACE: return "TRACE";
|
|
case LL_DEBUG: return "DEBUG";
|
|
case LL_INFO: return "INFO";
|
|
case LL_WARN: return "WARN";
|
|
case LL_ERROR: return "ERROR";
|
|
case LL_OFF: return "OFF";
|
|
}
|
|
return "?";
|
|
}
|
|
|
|
thread_local std::string g_last_error;
|
|
|
|
} // namespace
|
|
|
|
LogLevel current_log_level()
|
|
{
|
|
static const LogLevel lvl = []() {
|
|
return parse_log_level(std::getenv("OBN_BAMBUSOURCE_LOG_LEVEL"),
|
|
LL_INFO);
|
|
}();
|
|
return lvl;
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
namespace {
|
|
// Look up the absolute path of THIS DLL (the one that contains
|
|
// mirror_log_fp itself) using GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS.
|
|
// The plugin is installed to "<data_dir>\plugins\BambuSource.dll", so
|
|
// we strip the trailing two path components to recover <data_dir> at
|
|
// runtime — meaning the same DLL writes to "%APPDATA%\BambuStudio\"
|
|
// when Studio loads it and to "%APPDATA%\OrcaSlicer\" when Orca does.
|
|
std::string this_dll_data_dir()
|
|
{
|
|
HMODULE h = nullptr;
|
|
if (!::GetModuleHandleExW(
|
|
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
|
|
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
|
reinterpret_cast<LPCWSTR>(&this_dll_data_dir),
|
|
&h) || !h) {
|
|
return {};
|
|
}
|
|
wchar_t wpath[MAX_PATH] = {0};
|
|
DWORD n = ::GetModuleFileNameW(h, wpath, MAX_PATH);
|
|
if (n == 0 || n >= MAX_PATH) return {};
|
|
int u8 = ::WideCharToMultiByte(CP_UTF8, 0, wpath, -1,
|
|
nullptr, 0, nullptr, nullptr);
|
|
if (u8 <= 0) return {};
|
|
std::string path(static_cast<std::size_t>(u8), '\0');
|
|
if (::WideCharToMultiByte(CP_UTF8, 0, wpath, -1,
|
|
path.data(), u8, nullptr, nullptr) <= 0)
|
|
return {};
|
|
if (!path.empty() && path.back() == '\0') path.pop_back();
|
|
// Strip "\BambuSource.dll"
|
|
auto slash = path.find_last_of("\\/");
|
|
if (slash == std::string::npos) return {};
|
|
std::string dir = path.substr(0, slash);
|
|
// Strip "\plugins" if present (defensive: a developer running a
|
|
// build tree directly might have the DLL in a different layout).
|
|
auto slash2 = dir.find_last_of("\\/");
|
|
if (slash2 != std::string::npos) {
|
|
std::string leaf = dir.substr(slash2 + 1);
|
|
for (auto& c : leaf) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
if (leaf == "plugins") dir = dir.substr(0, slash2);
|
|
}
|
|
return dir;
|
|
}
|
|
} // namespace
|
|
#endif
|
|
|
|
// Resolution order:
|
|
// 1. $OBN_BAMBUSOURCE_LOG_FILE (set to "off"/"none"/empty to disable
|
|
// the file mirror entirely; "stderr" routes to stderr).
|
|
// 2. Windows: <this-dll's data dir>\obn-bambusource.log
|
|
// POSIX: $XDG_STATE_HOME/bambu-studio/obn-bambusource.log
|
|
// 3. POSIX: $HOME/.local/state/bambu-studio/obn-bambusource.log
|
|
// Windows: %APPDATA%\BambuStudio\obn-bambusource.log (legacy)
|
|
// 4. Last-resort fallback (TEMP / /tmp).
|
|
FILE* mirror_log_fp()
|
|
{
|
|
static FILE* fp = []() -> FILE* {
|
|
if (const char* env = std::getenv("OBN_BAMBUSOURCE_LOG_FILE")) {
|
|
if (!*env || !std::strcmp(env, "off") ||
|
|
!std::strcmp(env, "none") || !std::strcmp(env, "0"))
|
|
return nullptr;
|
|
if (!std::strcmp(env, "stderr") || !std::strcmp(env, "-"))
|
|
return stderr;
|
|
if (FILE* f = std::fopen(env, "a")) {
|
|
// _IOLBF + nullptr buffer is invalid on MSVC CRT and triggers
|
|
// __fastfail(FAST_FAIL_INVALID_ARG); use _IONBF (no buffering)
|
|
// which is allowed with a NULL buffer on every supported CRT.
|
|
std::setvbuf(f, nullptr, _IONBF, 0);
|
|
std::fprintf(f, "--- obn libBambuSource opened ---\n");
|
|
return f;
|
|
}
|
|
// Fall through to the default search if the user-supplied
|
|
// path could not be opened — better than dropping logs.
|
|
}
|
|
|
|
static std::string p0, p1, p2, p3;
|
|
const char* paths[4] = {nullptr, nullptr, nullptr, nullptr};
|
|
#if defined(_WIN32)
|
|
// Preferred: alongside the host slicer's data dir, recovered
|
|
// from this DLL's installed path. Works for both Studio and
|
|
// Orca without any compile-time switch.
|
|
std::string dd = this_dll_data_dir();
|
|
if (!dd.empty()) {
|
|
p0 = dd + "\\obn-bambusource.log";
|
|
paths[0] = p0.c_str();
|
|
}
|
|
if (const char* appdata = std::getenv("APPDATA")) {
|
|
p1 = std::string(appdata) + "\\BambuStudio\\obn-bambusource.log";
|
|
paths[1] = p1.c_str();
|
|
}
|
|
if (const char* lappdata = std::getenv("LOCALAPPDATA")) {
|
|
p2 = std::string(lappdata) + "\\BambuStudio\\obn-bambusource.log";
|
|
paths[2] = p2.c_str();
|
|
}
|
|
if (const char* tmp = std::getenv("TEMP")) {
|
|
p3 = std::string(tmp) + "\\obn-bambusource.log";
|
|
paths[3] = p3.c_str();
|
|
} else {
|
|
paths[3] = "C:\\obn-bambusource.log";
|
|
}
|
|
#else
|
|
if (const char* xdg = std::getenv("XDG_STATE_HOME")) {
|
|
p0 = std::string(xdg) + "/bambu-studio/obn-bambusource.log";
|
|
paths[0] = p0.c_str();
|
|
}
|
|
if (const char* home = std::getenv("HOME")) {
|
|
p1 = std::string(home) + "/.local/state/bambu-studio/obn-bambusource.log";
|
|
paths[1] = p1.c_str();
|
|
}
|
|
paths[2] = "/tmp/obn-bambusource.log";
|
|
#endif
|
|
for (const char* path : paths) {
|
|
if (!path) continue;
|
|
if (FILE* f = std::fopen(path, "a")) {
|
|
// See note above: _IOLBF + nullptr buffer crashes the MSVC CRT.
|
|
std::setvbuf(f, nullptr, _IONBF, 0);
|
|
std::fprintf(f, "--- obn libBambuSource opened (path=%s) ---\n", path);
|
|
return f;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}();
|
|
return fp;
|
|
}
|
|
|
|
void log_at(LogLevel lvl, Logger logger, void* ctx, const char* fmt, ...)
|
|
{
|
|
if (lvl < current_log_level()) return;
|
|
|
|
char buf[512];
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
std::vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (FILE* fp = mirror_log_fp()) {
|
|
auto now = std::chrono::system_clock::now();
|
|
auto tt = std::chrono::system_clock::to_time_t(now);
|
|
std::tm lt{};
|
|
obn::os::localtime_safe(tt, <);
|
|
char ts[32];
|
|
std::strftime(ts, sizeof(ts), "%F %T", <);
|
|
std::fprintf(fp, "%s [%s] %s\n", ts, level_tag(lvl), buf);
|
|
}
|
|
|
|
if (logger) {
|
|
// Studio expects a heap-allocated buffer that it will free via
|
|
// Bambu_FreeLogMsg. POSIX -> strdup; Windows -> wide-char copy
|
|
// via strdup_for_logger() (caller frees with free()).
|
|
logger(ctx, /*level=*/static_cast<int>(lvl), strdup_for_logger(buf));
|
|
}
|
|
}
|
|
|
|
void log_fmt(Logger logger, void* ctx, const char* fmt, ...)
|
|
{
|
|
if (LL_INFO < current_log_level()) return;
|
|
|
|
char buf[512];
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
std::vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (FILE* fp = mirror_log_fp()) {
|
|
auto now = std::chrono::system_clock::now();
|
|
auto tt = std::chrono::system_clock::to_time_t(now);
|
|
std::tm lt{};
|
|
obn::os::localtime_safe(tt, <);
|
|
char ts[32];
|
|
std::strftime(ts, sizeof(ts), "%F %T", <);
|
|
std::fprintf(fp, "%s [INFO] %s\n", ts, buf);
|
|
}
|
|
|
|
if (logger) {
|
|
logger(ctx, /*level=*/static_cast<int>(LL_INFO), strdup_for_logger(buf));
|
|
}
|
|
}
|
|
|
|
void set_last_error(const char* msg)
|
|
{
|
|
g_last_error.assign(msg ? msg : "");
|
|
}
|
|
|
|
const char* get_last_error()
|
|
{
|
|
return g_last_error.c_str();
|
|
}
|
|
|
|
} // namespace obn::source
|