diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 5064275..44f0176 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -116,7 +116,8 @@ "Bash(npm install *)", "Bash(NODE_PATH=\"C:/home/alex/.npm-global/node_modules\" node build_anslib_logging_guide.js)", "Bash(python \"C:/Users/nghia/AppData/Roaming/Claude/local-agent-mode-sessions/skills-plugin/d8e35aa4-a14e-4e20-b921-ba1b9a3cce86/cdda7cc8-a1c7-42ff-98b4-473ec3e8b9fb/skills/docx/scripts/office/validate.py\" \"C:/Projects/CLionProjects/ANSCORE/docs/ANSLIB_Logging_Guide.docx\")", - "Bash(python \"C:/Users/nghia/AppData/Roaming/Claude/local-agent-mode-sessions/skills-plugin/d8e35aa4-a14e-4e20-b921-ba1b9a3cce86/cdda7cc8-a1c7-42ff-98b4-473ec3e8b9fb/skills/docx/scripts/office/soffice.py\" --headless --convert-to pdf --outdir \"C:/Projects/CLionProjects/ANSCORE/docs\" \"C:/Projects/CLionProjects/ANSCORE/docs/ANSLIB_Logging_Guide.docx\")" + "Bash(python \"C:/Users/nghia/AppData/Roaming/Claude/local-agent-mode-sessions/skills-plugin/d8e35aa4-a14e-4e20-b921-ba1b9a3cce86/cdda7cc8-a1c7-42ff-98b4-473ec3e8b9fb/skills/docx/scripts/office/soffice.py\" --headless --convert-to pdf --outdir \"C:/Projects/CLionProjects/ANSCORE/docs\" \"C:/Projects/CLionProjects/ANSCORE/docs/ANSLIB_Logging_Guide.docx\")", + "Bash(sort -t: -k1 -u)" ] } } diff --git a/ANSLIB/ANSLIB.cpp b/ANSLIB/ANSLIB.cpp index 931b16b..24147ea 100644 --- a/ANSLIB/ANSLIB.cpp +++ b/ANSLIB/ANSLIB.cpp @@ -13,10 +13,12 @@ namespace ANSCENTER // pointers are write-once globals — concurrent reads are race-free. using ANSLogger_LogFnT = void(*)(const char*, const char*, const char*, int); using ANSCENTER_IsDbgEnFnT = int (*)(void); - ANSLogger_LogFnT g_ANSLogger_LogInfo = nullptr; - ANSLogger_LogFnT g_ANSLogger_LogError = nullptr; - ANSLogger_LogFnT g_ANSLogger_LogFatal = nullptr; - ANSCENTER_IsDbgEnFnT g_ANSCENTER_IsDebugViewEnabled = nullptr; + using ANSCENTER_DebugLogFnT = void(*)(const char*); + ANSLogger_LogFnT g_ANSLogger_LogInfo = nullptr; + ANSLogger_LogFnT g_ANSLogger_LogError = nullptr; + ANSLogger_LogFnT g_ANSLogger_LogFatal = nullptr; + ANSCENTER_IsDbgEnFnT g_ANSCENTER_IsDebugViewEnabled = nullptr; + ANSCENTER_DebugLogFnT g_ANSCENTER_DebugLog = nullptr; // Pre-load all ANSCORE DLLs from the Shared folder using full paths. // This ensures the correct versions are loaded regardless of PATH order @@ -45,10 +47,11 @@ namespace ANSCENTER // Use GetModuleHandleA — the DLL is already in this process's module // list courtesy of the LoadLibraryA above, so refcount is unchanged. if (HMODULE lic = GetModuleHandleA("ANSLicensingSystem.dll")) { - g_ANSLogger_LogInfo = (ANSLogger_LogFnT) GetProcAddress(lic, "ANSLogger_LogInfo"); - g_ANSLogger_LogError = (ANSLogger_LogFnT) GetProcAddress(lic, "ANSLogger_LogError"); - g_ANSLogger_LogFatal = (ANSLogger_LogFnT) GetProcAddress(lic, "ANSLogger_LogFatal"); - g_ANSCENTER_IsDebugViewEnabled = (ANSCENTER_IsDbgEnFnT)GetProcAddress(lic, "ANSCENTER_IsDebugViewEnabled"); + g_ANSLogger_LogInfo = (ANSLogger_LogFnT) GetProcAddress(lic, "ANSLogger_LogInfo"); + g_ANSLogger_LogError = (ANSLogger_LogFnT) GetProcAddress(lic, "ANSLogger_LogError"); + g_ANSLogger_LogFatal = (ANSLogger_LogFnT) GetProcAddress(lic, "ANSLogger_LogFatal"); + g_ANSCENTER_IsDebugViewEnabled = (ANSCENTER_IsDbgEnFnT) GetProcAddress(lic, "ANSCENTER_IsDebugViewEnabled"); + g_ANSCENTER_DebugLog = (ANSCENTER_DebugLogFnT)GetProcAddress(lic, "ANSCENTER_DebugLog"); } }); } @@ -288,6 +291,20 @@ namespace ANSCENTER PreloadSharedDllsOnce(); return g_ANSCENTER_IsDebugViewEnabled ? g_ANSCENTER_IsDebugViewEnabled() : 0; } + void ANSLIB::DebugLog(const char* preformattedLine) { + PreloadSharedDllsOnce(); + if (!preformattedLine) return; + if (g_ANSCENTER_DebugLog) { + // Preferred path: licensing DLL applies the prefix scrubber and emits. + g_ANSCENTER_DebugLog(preformattedLine); + return; + } + // Fallback for environments where ANSLicensingSystem.dll is missing: + // emit raw to DebugView + stderr without filtering. Better than dropping. + OutputDebugStringA(preformattedLine); + std::fputs(preformattedLine, stderr); + std::fflush(stderr); + } void ANSLIB::LogInfo(const char* src, const char* msg, const char* file, int line) { PreloadSharedDllsOnce(); if (g_ANSLogger_LogInfo) g_ANSLogger_LogInfo(src, msg, file, line); diff --git a/ANSLIB/ANSLIB.h b/ANSLIB/ANSLIB.h index c6a0679..245f93d 100644 --- a/ANSLIB/ANSLIB.h +++ b/ANSLIB/ANSLIB.h @@ -305,6 +305,14 @@ namespace ANSCENTER { // Used by the ANSLIB_DBG macro below; can also be called directly to // gate expensive log-prep work. static int IsDebugViewEnabled(); + + // DebugView emit. Hands a pre-formatted "[Tag] body\n" line to + // ANSCENTER_DebugLog inside the licensing DLL, which strips noise path + // prefixes (e.g. C:\ProgramData\ANSCENTER\, C:\ProgramData\\) + // before writing to OutputDebugStringA + stderr. Used by ANSLIB_DBG. + // If the licensing DLL is unavailable, falls back to raw emit (no + // filtering) so messages aren't lost. + static void DebugLog(const char* preformattedLine); private: HMODULE dllHandle = nullptr; bool loaded = false; @@ -455,16 +463,22 @@ public: } }; -// ANSLIB_DBG: DebugView logging macro mirroring ANS_DBG, but the gate function -// is dynamic-loaded inside ANSLIB.dll — consumers don't need to link or even -// know about ANSLicensingSystem.dll. Output goes to BOTH OutputDebugStringA -// (DebugView) and stderr, identical format to ANS_DBG: "[tag] formatted\n". +// ANSLIB_DBG: DebugView logging macro mirroring ANS_DBG. Both the gate and the +// emit path are dynamic-loaded inside ANSLIB.dll — consumers don't need to +// link or even know about ANSLicensingSystem.dll. Output goes to BOTH +// OutputDebugStringA (DebugView) and stderr, identical format to ANS_DBG: +// "[tag] formatted\n". +// +// Noise path prefixes (e.g. C:\ProgramData\ANSCENTER\, C:\ProgramData\\) +// are stripped centrally inside ANSCENTER_DebugLog before emit, so log lines +// like "loaded from C:\ProgramData\Jh7O7nUe7vS\Models\Foo" appear as +// "loaded from Models\Foo". // // Usage: ANSLIB_DBG("MyModule", "value=%d ptr=%p", val, ptr); // -// Hot-path cost when gate is OFF: one indirect call (atomic load + branch -// inside ANSLicensingSystem). When gate is ON: snprintf to a 1024-byte stack -// buffer + OutputDebugStringA + fputs. No heap, no per-call allocation. +// Hot-path cost when gate is OFF: one indirect call. When gate is ON: snprintf +// to a 1024-byte stack buffer + cross-DLL call into ANSCENTER_DebugLog (which +// scrubs prefixes then emits). No heap, no per-call allocation in the macro. #ifndef ANSCORE_DEBUGVIEW #define ANSCORE_DEBUGVIEW 1 // 1 = runtime-gated (default), 0 = hard-off (stripped) #endif @@ -473,9 +487,7 @@ public: if (::ANSCENTER::ANSLIB::IsDebugViewEnabled()) { \ char _ansl_dbg_buf[1024]; \ snprintf(_ansl_dbg_buf, sizeof(_ansl_dbg_buf), "[" tag "] " fmt "\n", ##__VA_ARGS__); \ - OutputDebugStringA(_ansl_dbg_buf); \ - fputs(_ansl_dbg_buf, stderr); \ - fflush(stderr); \ + ::ANSCENTER::ANSLIB::DebugLog(_ansl_dbg_buf); \ } \ } while(0) #else diff --git a/core/ANSLicensingSystem/ANSLicense.cpp b/core/ANSLicensingSystem/ANSLicense.cpp index 39605c3..4698b7e 100644 --- a/core/ANSLicensingSystem/ANSLicense.cpp +++ b/core/ANSLicensingSystem/ANSLicense.cpp @@ -14,12 +14,18 @@ #include "base64.h" #include #include +#include #include +#include #include #include #include #include +// Forward-declare OutputDebugStringA without pulling in (which +// drags winsock conflicts as noted in ANSLicense.h). Used by ANSCENTER_DebugLog. +extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA(const char* lpOutputString); + #define MAX_CUSTOMER_NAME_LEN 100 #define FEATURE_1 0x1 #define FEATURE_2 0x2 @@ -30,6 +36,135 @@ #define FEATURE_7 0x40 #define FEATURE_8 0x80 +// ---------------------------------------------------------------------------- +// Log-prefix scrubber. +// +// We don't want noisy fully-qualified per-machine paths in log lines: +// "model loaded from C:\ProgramData\Jh7O7nUe7vS\Models\EngineModels\X" +// becomes +// "model loaded from Models\EngineModels\X" +// +// The prefix list is built once on first use and combines: +// - hardcoded ANSCENTER ProgramData roots (edit kStaticPrefixes below), +// - the OS temp directory resolved at runtime via std::filesystem, +// - per-OS conventional temp/env-var paths (TEMP/TMP on Windows; +// /tmp/, /var/tmp/, TMPDIR on POSIX). +// +// Each prefix is normalised to lowercase, has a trailing separator forced, +// and is added in BOTH `\` and `/` slash flavours so the matcher works on +// either form. Prefixes are sorted longest-first so a more specific prefix +// (e.g. C:\Users\Bob\AppData\Local\Temp\) wins over a shorter overlapping one. +// +// Case-insensitive, handles both `\` and `/` separators. Single pass, O(N*P) +// where N is message length and P is prefix count (~5–10 in practice). Called +// from every SPDLogger::Log* path and from ANSCENTER_DebugLog (the choke point +// used by ANS_DBG / ANSLIB_DBG). +// ---------------------------------------------------------------------------- +namespace { + + inline std::string to_lower_ascii(const std::string& s) { + std::string out(s.size(), '\0'); + for (size_t i = 0; i < s.size(); ++i) { + out[i] = static_cast(std::tolower(static_cast(s[i]))); + } + return out; + } + + // Append both `\` and `/` slash flavours of `raw` to `dst`, lower-cased + // and with a trailing separator forced. Empty / pathologically short + // entries are skipped. + inline void push_prefix_variants(std::vector& dst, const std::string& raw) { + if (raw.size() < 2) return; + std::string normalised = raw; + char last = normalised.back(); + if (last != '\\' && last != '/') normalised.push_back('\\'); + std::string lowerBs = to_lower_ascii(normalised); + std::string lowerFs = lowerBs; + for (char& c : lowerFs) if (c == '\\') c = '/'; + dst.push_back(lowerBs); + if (lowerFs != lowerBs) dst.push_back(lowerFs); + } + + // Build the prefix table once. Function-local static is initialised + // exactly once per process (C++11 magic-static rule, thread-safe). + const std::vector& GetLogPrefixesLower() { + static const std::vector kPrefixesLower = []() { + std::vector v; + + // 1. Hardcoded ANSCENTER ProgramData roots — edit this list to + // add or remove well-known noise prefixes that aren't tied to + // the OS temp/user dir. + static const char* kStaticPrefixes[] = { + "C:\\ProgramData\\ANSCENTER\\", + "C:\\ProgramData\\Jh7O7nUe7vS\\", + nullptr, + }; + for (int i = 0; kStaticPrefixes[i]; ++i) { + push_prefix_variants(v, kStaticPrefixes[i]); + } + + // 2. OS temp directory (Windows: usually %LOCALAPPDATA%\Temp; + // Linux: usually /tmp; macOS: /private/var/folders/...). + try { + push_prefix_variants(v, std::filesystem::temp_directory_path().string()); + } catch (...) { /* ignore — fallbacks below cover most cases */ } + + // 3. Per-OS conventional fallbacks in case temp_directory_path() + // differs from what the env vars say or returns nothing useful. +#ifdef _WIN32 + if (const char* p = std::getenv("TEMP")) push_prefix_variants(v, p); + if (const char* p = std::getenv("TMP")) push_prefix_variants(v, p); + push_prefix_variants(v, "C:\\Windows\\Temp\\"); // SYSTEM/services temp +#else + if (const char* p = std::getenv("TMPDIR")) push_prefix_variants(v, p); + push_prefix_variants(v, "/tmp/"); + push_prefix_variants(v, "/var/tmp/"); +#endif + + // 4. De-duplicate (env vars + filesystem API often agree). + std::sort(v.begin(), v.end()); + v.erase(std::unique(v.begin(), v.end()), v.end()); + + // 5. Sort longest-first so specific prefixes win over shorter + // overlapping ones (e.g. AppData\Local\Temp\ before AppData\). + std::sort(v.begin(), v.end(), + [](const std::string& a, const std::string& b) { + return a.size() > b.size(); + }); + + return v; + }(); + return kPrefixesLower; + } + + inline std::string strip_log_prefixes(const std::string& input) { + if (input.empty()) return input; + + const auto& prefixes = GetLogPrefixesLower(); + std::string lower = to_lower_ascii(input); + + std::string out; + out.reserve(input.size()); + size_t i = 0; + while (i < input.size()) { + bool matched = false; + for (const auto& p : prefixes) { + if (i + p.size() <= input.size() && + lower.compare(i, p.size(), p) == 0) { + i += p.size(); + matched = true; + break; + } + } + if (!matched) { + out.push_back(input[i]); + ++i; + } + } + return out; + } +} + namespace ANSCENTER { template T GetData(const boost::property_tree::ptree& pt, const std::string& key) @@ -242,13 +377,14 @@ namespace ANSCENTER //__FUNCTION__ void SPDLogger::LogTrace(const std::string& source, const std::string& message, const std::string& fileSource, int lineSource) { std::string msg = "[" + path_to_filename(fileSource) + "] [" + std::to_string(lineSource) + "] " + source; + std::string scrubbed = strip_log_prefixes(message); try { auto logger = spdlog::get("anslogger_console"); - if (logger) logger->trace("{}: {}", msg, message); + if (logger) logger->trace("{}: {}", msg, scrubbed); auto eventLogger = spdlog::get("anslogger_event"); - if (eventLogger) eventLogger->trace("{}: {}", msg, message); + if (eventLogger) eventLogger->trace("{}: {}", msg, scrubbed); } catch (const std::exception& e) { std::cerr << "SPDLogger::LogError - Exception: " << e.what() << std::endl; @@ -256,14 +392,15 @@ namespace ANSCENTER } void SPDLogger::LogInfo(const std::string& source, const std::string& message, const std::string& fileSource, int lineSource) { std::string msg = "[" + path_to_filename(fileSource) + "] [" + std::to_string(lineSource) + "] " + source; + std::string scrubbed = strip_log_prefixes(message); try { auto logger = spdlog::get("anslogger_console"); if (logger) { - logger->info("{}: {}", msg, message); + logger->info("{}: {}", msg, scrubbed); } auto eventLogger = spdlog::get("anslogger_event"); - if (eventLogger) eventLogger->info("{}: {}", msg, message); + if (eventLogger) eventLogger->info("{}: {}", msg, scrubbed); } catch (const std::exception& e) { @@ -272,11 +409,12 @@ namespace ANSCENTER } void SPDLogger::LogWarn(const std::string& source, const std::string& message, const std::string& fileSource, int lineSource) { std::string messageSource = "[" + path_to_filename(fileSource) + "] [" + std::to_string(lineSource) + "] " + source; + std::string scrubbed = strip_log_prefixes(message); try { auto logger = spdlog::get("anslogger_console"); if (logger) { - logger->warn("{}: {}", messageSource, message); + logger->warn("{}: {}", messageSource, scrubbed); } else { std::cerr << "SPDLogger::LogWarn - Logger not initialized: anslogger_console" << std::endl; @@ -288,11 +426,12 @@ namespace ANSCENTER } void SPDLogger::LogDebug(const std::string& source, const std::string& message, const std::string& fileSource, int lineSource) { std::string msg = "[" + path_to_filename(fileSource) + "] [" + std::to_string(lineSource) + "] " + source; + std::string scrubbed = strip_log_prefixes(message); try { auto logger = spdlog::get("anslogger_console"); if (logger) { - logger->debug("{}: {}", msg, message); + logger->debug("{}: {}", msg, scrubbed); } } catch (const std::exception& e) { @@ -301,13 +440,14 @@ namespace ANSCENTER } void SPDLogger::LogError(const std::string& source, const std::string& message, const std::string& fileSource, int lineSource) { std::string msg = "[" + path_to_filename(fileSource) + "] [" + std::to_string(lineSource) + "] " + source; + std::string scrubbed = strip_log_prefixes(message); try { auto logger = spdlog::get("anslogger_console"); - if (logger) logger->error("{}: {}", msg, message); + if (logger) logger->error("{}: {}", msg, scrubbed); auto eventLogger = spdlog::get("anslogger_event"); - if (eventLogger) eventLogger->error("{}: {}", msg, message); + if (eventLogger) eventLogger->error("{}: {}", msg, scrubbed); } catch (const std::exception& e) { std::cerr << "SPDLogger::LogError - Exception: " << e.what() << std::endl; @@ -315,13 +455,14 @@ namespace ANSCENTER } void SPDLogger::LogFatal(const std::string& source, const std::string& message, const std::string& fileSource, int lineSource) { std::string msg = "[" + path_to_filename(fileSource) + "] [" + std::to_string(lineSource) + "] " + source; + std::string scrubbed = strip_log_prefixes(message); try { auto logger = spdlog::get("anslogger_console"); - if (logger) logger->critical("{}: {}", msg, message); + if (logger) logger->critical("{}: {}", msg, scrubbed); auto eventLogger = spdlog::get("anslogger_event"); - if (eventLogger) eventLogger->critical("{}: {}", msg, message); + if (eventLogger) eventLogger->critical("{}: {}", msg, scrubbed); } catch (const std::exception& e) { std::cerr << "SPDLogger::LogFatal - Exception: " << e.what() << std::endl; @@ -352,6 +493,19 @@ extern "C" { src ? src : "", msg ? msg : "", file ? file : "", line); } catch (...) {} } + + // DebugView emit path used by ANS_DBG and ANSLIB_DBG. Single choke point + // so the prefix scrubber lives in one place; macros just hand off the + // already-formatted "[Tag] body\n" buffer here. + ANSLICENSE_API void ANSCENTER_DebugLog(const char* buf) { + if (!buf) return; + try { + std::string scrubbed = strip_log_prefixes(buf); + OutputDebugStringA(scrubbed.c_str()); + std::fputs(scrubbed.c_str(), stderr); + std::fflush(stderr); + } catch (...) {} + } } namespace ANSCENTER diff --git a/core/ANSLicensingSystem/ANSLicense.h b/core/ANSLicensingSystem/ANSLicense.h index d18276a..03763bf 100644 --- a/core/ANSLicensingSystem/ANSLicense.h +++ b/core/ANSLicensingSystem/ANSLicense.h @@ -39,36 +39,25 @@ extern "C" { ANSLICENSE_API void ANSLogger_LogInfo (const char* src, const char* msg, const char* file, int line); ANSLICENSE_API void ANSLogger_LogError(const char* src, const char* msg, const char* file, int line); ANSLICENSE_API void ANSLogger_LogFatal(const char* src, const char* msg, const char* file, int line); + + // DebugView choke point shared by ANS_DBG and ANSLIB_DBG. Strips noise + // path prefixes before emitting to OutputDebugStringA + stderr. + ANSLICENSE_API void ANSCENTER_DebugLog(const char* preformattedLine); } // ANS_DBG: Debug logging macro for DebugView (OutputDebugStringA on Windows). // Usage: ANS_DBG("MyModule", "value=%d ptr=%p", val, ptr); // Output: [MyModule] value=42 ptr=0x1234 -// NOTE: We avoid #include here to prevent winsock.h/winsock2.h -// conflicts. Instead, forward-declare OutputDebugStringA directly. -#if ANSCORE_DEBUGVIEW && defined(_WIN32) -extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA(const char* lpOutputString); -// Emit to BOTH DebugView (OutputDebugStringA) AND stderr so console apps -// (ANSCV-UnitTest, ANSLPR-UnitTest, ANSUtilities_test, etc.) show the message -// without needing to attach DebugView. fputs + fflush keeps lines atomic even -// when multiple threads log concurrently. +// +// Routes through ANSCENTER_DebugLog so noise path prefixes (e.g. the +// ProgramData profile dir) are stripped centrally before emit. The macro just +// formats on the caller's stack and hands off the buffer. +#if ANSCORE_DEBUGVIEW #define ANS_DBG(tag, fmt, ...) do { \ if (ANSCENTER_IsDebugViewEnabled()) { \ char _ans_dbg_buf[1024]; \ snprintf(_ans_dbg_buf, sizeof(_ans_dbg_buf), "[" tag "] " fmt "\n", ##__VA_ARGS__); \ - OutputDebugStringA(_ans_dbg_buf); \ - fputs(_ans_dbg_buf, stderr); \ - fflush(stderr); \ - } \ -} while(0) -#elif ANSCORE_DEBUGVIEW -// Non-Windows: stderr only. -#define ANS_DBG(tag, fmt, ...) do { \ - if (ANSCENTER_IsDebugViewEnabled()) { \ - char _ans_dbg_buf[1024]; \ - snprintf(_ans_dbg_buf, sizeof(_ans_dbg_buf), "[" tag "] " fmt "\n", ##__VA_ARGS__); \ - fputs(_ans_dbg_buf, stderr); \ - fflush(stderr); \ + ANSCENTER_DebugLog(_ans_dbg_buf); \ } \ } while(0) #else diff --git a/modules/ANSUtilities/dllmain.cpp b/modules/ANSUtilities/dllmain.cpp index b329cda..0a8e3ce 100644 --- a/modules/ANSUtilities/dllmain.cpp +++ b/modules/ANSUtilities/dllmain.cpp @@ -1,6 +1,7 @@ // dllmain.cpp : Defines the entry point for the DLL application. #include "pch.h" #include "ANSUtilities.h" +#include "Utility.h" #include #include #include @@ -697,7 +698,7 @@ extern "C" ANSULT_API int AESEncryption(const char* inputString, const char* in } extern "C" ANSULT_API int MD5HashFile(const char* filePath, LStrHandle decryptionMessage) { - ANS_DBG("ANSUTIL","MD5HashFile: filePath=%s", filePath ? filePath : "(null)"); + ANS_DBG("ANSUTIL","MD5HashFile: filePath=%s", filePath ? path_to_filename(std::string(filePath)).c_str() : "(null)"); if (filePath == nullptr || decryptionMessage == nullptr) return 0; try { std::string st = ANSCENTER::ANSUtilities::MD5HashFile(filePath);