From f81737ffc6676b5f809cec0e13f000b25130f9c4 Mon Sep 17 00:00:00 2001 From: Tuan Nghia Nguyen Date: Sun, 26 Apr 2026 06:59:42 +1000 Subject: [PATCH] Add debug and log functions to ANSLIB --- .claude/scheduled_tasks.lock | 1 + .claude/settings.local.json | 8 +- ANSLIB/ANSLIB.cpp | 99 ++++-- ANSLIB/ANSLIB.h | 48 ++- core/ANSLicensingSystem/ANSLicense.cpp | 31 +- core/ANSLicensingSystem/ANSLicense.h | 9 + docs/ANSLIB_Logging_Guide.docx | Bin 0 -> 13509 bytes docs/build_anslib_logging_guide.js | 412 +++++++++++++++++++++++++ docs/~$SLIB_Logging_Guide.docx | Bin 0 -> 162 bytes 9 files changed, 585 insertions(+), 23 deletions(-) create mode 100644 .claude/scheduled_tasks.lock create mode 100644 docs/ANSLIB_Logging_Guide.docx create mode 100644 docs/build_anslib_logging_guide.js create mode 100644 docs/~$SLIB_Logging_Guide.docx diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock new file mode 100644 index 0000000..6fc2da4 --- /dev/null +++ b/.claude/scheduled_tasks.lock @@ -0,0 +1 @@ +{"sessionId":"b4ba6a5c-5267-4098-8e7c-152012d3df22","pid":5176,"acquiredAt":1776991716766} \ No newline at end of file diff --git a/.claude/settings.local.json b/.claude/settings.local.json index c52275b..5064275 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -110,7 +110,13 @@ "Bash(tasklist //FI \"IMAGENAME eq ANSLPR-UnitTest.exe\")", "Bash(taskkill //PID 41856 //F)", "PowerShell(Stop-Process -Id 41856 -Force)", - "PowerShell(Start-Process -FilePath \"taskkill.exe\" -ArgumentList \"/PID\",\"41856\",\"/F\" -Verb RunAs -Wait)" + "PowerShell(Start-Process -FilePath \"taskkill.exe\" -ArgumentList \"/PID\",\"41856\",\"/F\" -Verb RunAs -Wait)", + "Bash(npm root *)", + "Bash(npm list *)", + "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\")" ] } } diff --git a/ANSLIB/ANSLIB.cpp b/ANSLIB/ANSLIB.cpp index a49656b..931b16b 100644 --- a/ANSLIB/ANSLIB.cpp +++ b/ANSLIB/ANSLIB.cpp @@ -1,31 +1,61 @@ #include "ANSLIB.h" #include "ANSLibsLoader.h" #include +#include #define LOAD_FUNC(name) \ name##Func = (name##FuncT)GetProcAddress(dllHandle, #name); \ if (!name##Func) { success = false; } namespace ANSCENTER { - ANSLIB::ANSLIB() { + namespace { + // Function-pointer typedefs for the ANSLicensingSystem C-facade exports. + // Resolved exactly once during PreloadSharedDllsOnce(); after that the + // 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; + // Pre-load all ANSCORE DLLs from the Shared folder using full paths. // This ensures the correct versions are loaded regardless of PATH order // (e.g., DLHUB_Runtime_Engine may contain older copies on some machines). - // Once loaded, Windows reuses them for all subsequent implicit dependencies. - const char* sharedDir = "C:\\ProgramData\\ANSCENTER\\Shared\\"; - const char* preloadDlls[] = { - "ANSLicensingSystem.dll", - "anslicensing.dll", - "ANSMOT.dll", - "ANSODEngine.dll", - nullptr - }; - for (int i = 0; preloadDlls[i] != nullptr; i++) { - std::string fullPath = std::string(sharedDir) + preloadDlls[i]; - LoadLibraryA(fullPath.c_str()); - } + // Run exactly once per process so LoadLibrary refcounts don't grow per + // ANSLIB instance. The DLLs are intentionally pinned for process lifetime. + void PreloadSharedDllsOnce() { + static std::once_flag s_preloadFlag; + std::call_once(s_preloadFlag, []() { + const char* sharedDir = "C:\\ProgramData\\ANSCENTER\\Shared\\"; + const char* preloadDlls[] = { + "ANSLicensingSystem.dll", + "anslicensing.dll", + "ANSMOT.dll", + "ANSODEngine.dll", + nullptr + }; + for (int i = 0; preloadDlls[i] != nullptr; i++) { + std::string fullPath = std::string(sharedDir) + preloadDlls[i]; + LoadLibraryA(fullPath.c_str()); + } + // Ensure all shared DLLs (OpenCV, OpenVINO, TRT, ORT) are pre-loaded + ANSCENTER::ANSLibsLoader::Initialize(); - // Ensure all shared DLLs (OpenCV, OpenVINO, TRT, ORT) are pre-loaded - ANSCENTER::ANSLibsLoader::Initialize(); + // Resolve logging C-facade symbols from the (now-pinned) licensing DLL. + // 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"); + } + }); + } + } + + ANSLIB::ANSLIB() { + PreloadSharedDllsOnce(); const char* dllPath = "C:\\ProgramData\\ANSCENTER\\Shared\\ANSODEngine.dll"; dllHandle = LoadLibraryA(dllPath); @@ -99,6 +129,10 @@ namespace ANSCENTER return -1; } if (CreateANSODHandle_CSFunc) { + if (ANSHandle && ReleaseANSODHandleFunc) { + ReleaseANSODHandleFunc(&ANSHandle); + ANSHandle = nullptr; + } const char* result = CreateANSODHandle_CSFunc(&ANSHandle, licenseKey, modelFilePath, modelFileZipPassword, modelThreshold, modelConfThreshold, modelNMSThreshold, 1, modelType, detectionType, loadEngineOnCreation); @@ -118,9 +152,8 @@ namespace ANSCENTER return -1; } if (RunInference_CPPFunc) { - auto imagePtr = std::make_unique(cvImage); try { - cv::Mat* rawPtr = imagePtr.get(); + cv::Mat* rawPtr = &cvImage; int result = RunInference_CPPFunc(&ANSHandle, &rawPtr, cameraId, detectionResult); return result; } @@ -135,9 +168,8 @@ namespace ANSCENTER return -1; } if (RunInferenceComplete_CPPFunc) { - auto imagePtr = std::make_unique(cvImage); try { - cv::Mat* rawPtr = imagePtr.get(); + cv::Mat* rawPtr = &cvImage; int result = RunInferenceComplete_CPPFunc(&ANSHandle, &rawPtr, cameraId, activeROIMode,detectionResult); return result; } @@ -193,6 +225,10 @@ namespace ANSCENTER int autoDetectEngine, int modelType, int detectionType, int loadEngineOnCreation, const char* modelFolder,std::string& labelMap) { if (!loaded || !LoadModelFromFolderFunc) return -1; + if (ANSHandle && ReleaseANSODHandleFunc) { + ReleaseANSODHandleFunc(&ANSHandle); + ANSHandle = nullptr; + } return LoadModelFromFolderFunc(&ANSHandle, licenseKey, modelName, className, detectionScoreThreshold, modelConfThreshold, modelMNSThreshold, autoDetectEngine, modelType, detectionType, loadEngineOnCreation,modelFolder, labelMap); @@ -241,4 +277,27 @@ namespace ANSCENTER if (!loaded || !SetTrackerParametersFunc) return -1; return SetTrackerParametersFunc(&ANSHandle, trackerParams); } + + // ------------------------------------------------------------------------ + // Static logging API. Each call ensures the DLL/symbols are resolved (cheap + // after the first call — std::call_once degenerates to an atomic load) then + // forwards to the C facade. Null-pointer guard makes missing symbols a + // silent no-op rather than a crash. + // ------------------------------------------------------------------------ + int ANSLIB::IsDebugViewEnabled() { + PreloadSharedDllsOnce(); + return g_ANSCENTER_IsDebugViewEnabled ? g_ANSCENTER_IsDebugViewEnabled() : 0; + } + 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); + } + void ANSLIB::LogError(const char* src, const char* msg, const char* file, int line) { + PreloadSharedDllsOnce(); + if (g_ANSLogger_LogError) g_ANSLogger_LogError(src, msg, file, line); + } + void ANSLIB::LogFatal(const char* src, const char* msg, const char* file, int line) { + PreloadSharedDllsOnce(); + if (g_ANSLogger_LogFatal) g_ANSLogger_LogFatal(src, msg, file, line); + } } \ No newline at end of file diff --git a/ANSLIB/ANSLIB.h b/ANSLIB/ANSLIB.h index 9a8bd99..c6a0679 100644 --- a/ANSLIB/ANSLIB.h +++ b/ANSLIB/ANSLIB.h @@ -6,6 +6,7 @@ #include #include #include +#include enum DetectionType { CLASSIFICATION = 0, DETECTION = 1, @@ -263,6 +264,10 @@ namespace ANSCENTER { public: ANSLIB(); ~ANSLIB() noexcept; + ANSLIB(const ANSLIB&) = delete; + ANSLIB& operator=(const ANSLIB&) = delete; + ANSLIB(ANSLIB&&) = delete; + ANSLIB& operator=(ANSLIB&&) = delete; [[nodiscard]] static ANSLIB* Create(); static void Destroy(ANSLIB* instance); [[nodiscard]] int Initialize(const char* licenseKey, @@ -286,6 +291,20 @@ namespace ANSCENTER { [[nodiscard]] int SetPrompt(const char* text); [[nodiscard]] int SetTracker(int trackerType, int enableTracker); [[nodiscard]] int SetTrackerParameters(const char* trackerParams); + + // Logging — process-wide, thread-safe, no instance required. + // Routes to SPDLogger inside ANSLicensingSystem.dll via dynamically-resolved + // C exports. Silently no-ops if the DLL or symbols are unavailable. + static void LogInfo (const char* src, const char* msg, const char* file = "", int line = 0); + static void LogError(const char* src, const char* msg, const char* file = "", int line = 0); + static void LogFatal(const char* src, const char* msg, const char* file = "", int line = 0); + + // DebugView gate. Wraps ANSCENTER_IsDebugViewEnabled() — non-zero when + // logging should be emitted (env var ANSCENTER_DBGVIEW or sentinel file + // C:\ProgramData\ANSCENTER\ansvisdebugview.txt). Cheap on the hot path. + // Used by the ANSLIB_DBG macro below; can also be called directly to + // gate expensive log-prep work. + static int IsDebugViewEnabled(); private: HMODULE dllHandle = nullptr; bool loaded = false; @@ -436,4 +455,31 @@ public: } }; -#endif +// 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". +// +// 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. +#ifndef ANSCORE_DEBUGVIEW +#define ANSCORE_DEBUGVIEW 1 // 1 = runtime-gated (default), 0 = hard-off (stripped) +#endif +#if ANSCORE_DEBUGVIEW +#define ANSLIB_DBG(tag, fmt, ...) do { \ + 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); \ + } \ +} while(0) +#else +#define ANSLIB_DBG(tag, fmt, ...) ((void)0) +#endif + +#endif diff --git a/core/ANSLicensingSystem/ANSLicense.cpp b/core/ANSLicensingSystem/ANSLicense.cpp index 4824638..39605c3 100644 --- a/core/ANSLicensingSystem/ANSLicense.cpp +++ b/core/ANSLicensingSystem/ANSLicense.cpp @@ -327,7 +327,36 @@ namespace ANSCENTER std::cerr << "SPDLogger::LogFatal - Exception: " << e.what() << std::endl; } } - +} + +// C facade — exported with stable C linkage so ANSLIB (and any other consumer) +// can resolve via GetProcAddress without coupling to the C++ class ABI. +// Each call delegates to the process-wide SPDLogger singleton; the magic-static +// inside GetInstance handles thread-safe one-time initialisation. +extern "C" { + ANSLICENSE_API void ANSLogger_LogInfo(const char* src, const char* msg, const char* file, int line) { + try { + ANSCENTER::SPDLogger::GetInstance("ANSLIB").LogInfo( + src ? src : "", msg ? msg : "", file ? file : "", line); + } catch (...) {} + } + ANSLICENSE_API void ANSLogger_LogError(const char* src, const char* msg, const char* file, int line) { + try { + ANSCENTER::SPDLogger::GetInstance("ANSLIB").LogError( + src ? src : "", msg ? msg : "", file ? file : "", line); + } catch (...) {} + } + ANSLICENSE_API void ANSLogger_LogFatal(const char* src, const char* msg, const char* file, int line) { + try { + ANSCENTER::SPDLogger::GetInstance("ANSLIB").LogFatal( + src ? src : "", msg ? msg : "", file ? file : "", line); + } catch (...) {} + } +} + +namespace ANSCENTER +{ + EngineType ANSCENTER::ANSLicenseHelper::CheckHardwareInformation() { ANS_DBG("HWInfo", "CheckHardwareInformation: starting hardware detection"); diff --git a/core/ANSLicensingSystem/ANSLicense.h b/core/ANSLicensingSystem/ANSLicense.h index 35e4164..d18276a 100644 --- a/core/ANSLicensingSystem/ANSLicense.h +++ b/core/ANSLicensingSystem/ANSLicense.h @@ -32,6 +32,15 @@ // filesystem check runs at most once per ~2 s. extern "C" ANSLICENSE_API int ANSCENTER_IsDebugViewEnabled(void); +// C-linkage facade over SPDLogger so consumers can dynamic-load these via +// GetProcAddress without depending on the C++ class ABI. Each call routes to +// the process-wide SPDLogger singleton (thread-safe magic-static init). +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); +} + // 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 diff --git a/docs/ANSLIB_Logging_Guide.docx b/docs/ANSLIB_Logging_Guide.docx new file mode 100644 index 0000000000000000000000000000000000000000..d8ee93c3e8e1a472476c8996ab4ea1e5a08ff0d4 GIT binary patch literal 13509 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 + new Paragraph({ + spacing: { after: 0, line: 260 }, + children: [new TextRun({ text: line || " ", font: FONT_CODE, size: 20 })], + }) + ); + return new Table({ + width: { size: 9360, type: WidthType.DXA }, + columnWidths: [9360], + rows: [ + new TableRow({ + children: [ + new TableCell({ + width: { size: 9360, type: WidthType.DXA }, + shading: { fill: "F4F4F4", type: ShadingType.CLEAR }, + margins: { top: 120, bottom: 120, left: 180, right: 180 }, + borders: { + top: { style: BorderStyle.SINGLE, size: 4, color: "DDDDDD" }, + bottom: { style: BorderStyle.SINGLE, size: 4, color: "DDDDDD" }, + left: { style: BorderStyle.SINGLE, size: 4, color: "DDDDDD" }, + right: { style: BorderStyle.SINGLE, size: 4, color: "DDDDDD" }, + }, + children: paras, + }), + ], + }), + ], + }); +} + +function bullet(text, runs = null) { + return new Paragraph({ + numbering: { reference: "bullets", level: 0 }, + spacing: { after: 80 }, + children: runs || [new TextRun({ text, font: FONT_BODY, size: 22 })], + }); +} + +function spacer() { + return new Paragraph({ spacing: { after: 120 }, children: [new TextRun(" ")] }); +} + +// ---- table builder for the cheat sheets ----------------------------------- +const CELL_BORDER = { style: BorderStyle.SINGLE, size: 4, color: "BFBFBF" }; +const CELL_BORDERS = { top: CELL_BORDER, bottom: CELL_BORDER, left: CELL_BORDER, right: CELL_BORDER }; + +function headerCell(text, width) { + return new TableCell({ + width: { size: width, type: WidthType.DXA }, + borders: CELL_BORDERS, + shading: { fill: "1F3864", type: ShadingType.CLEAR }, + margins: { top: 100, bottom: 100, left: 140, right: 140 }, + children: [new Paragraph({ + children: [new TextRun({ text, font: FONT_BODY, size: 22, bold: true, color: "FFFFFF" })], + })], + }); +} + +function bodyCell(text, width, opts = {}) { + const runs = Array.isArray(text) + ? text + : [new TextRun({ text, font: opts.code ? FONT_CODE : FONT_BODY, size: 20 })]; + return new TableCell({ + width: { size: width, type: WidthType.DXA }, + borders: CELL_BORDERS, + shading: opts.shade ? { fill: opts.shade, type: ShadingType.CLEAR } : undefined, + margins: { top: 80, bottom: 80, left: 140, right: 140 }, + children: [new Paragraph({ children: runs })], + }); +} + +function sheetTable(columnWidths, headers, rows) { + const total = columnWidths.reduce((a, b) => a + b, 0); + return new Table({ + width: { size: total, type: WidthType.DXA }, + columnWidths, + rows: [ + new TableRow({ + tableHeader: true, + children: headers.map((h, i) => headerCell(h, columnWidths[i])), + }), + ...rows.map(row => + new TableRow({ + children: row.map((c, i) => { + if (c && typeof c === 'object' && c.runs) { + return bodyCell(c.runs, columnWidths[i], c.opts || {}); + } + return bodyCell(c, columnWidths[i]); + }), + }) + ), + ], + }); +} + +// ========================================================================= +// Document content +// ========================================================================= +const children = []; + +// ---------- Title ---------- +children.push(new Paragraph({ + spacing: { after: 80 }, + alignment: AlignmentType.LEFT, + children: [new TextRun({ text: "ANSLIB Logging API", font: FONT_BODY, size: 48, bold: true, color: "1F3864" })], +})); +children.push(new Paragraph({ + spacing: { after: 360 }, + children: [new TextRun({ text: "Developer guide — usage examples for ANSLIB_DBG, LogInfo, LogError, LogFatal", font: FONT_BODY, size: 24, italics: true, color: "595959" })], +})); + +// ---------- Overview ---------- +children.push(h1("Overview")); +children.push(p("ANSLIB exposes a small logging API that consumers can use without linking against ANSLicensingSystem.dll or any spdlog headers. All symbols are dynamically loaded inside ANSLIB; users only need to include ANSLIB.h and link ANSLIB.lib.")); +children.push(p("Two flavours are provided:")); +children.push(bullet("ANSLIB_DBG — a printf-style macro for trace breadcrumbs that can be turned on or off remotely (DebugView-style logging).")); +children.push(bullet("ANSCENTER::ANSLIB::LogInfo / LogError / LogFatal — leveled logging that writes through SPDLogger to the rolling log file.")); + +// ---------- Setup ---------- +children.push(h1("Setup")); +children.push(p("Same prerequisites for every example below. Add this to any consumer translation unit:")); +children.push(codeBlock( +`#include "ANSLIB.h" // brings everything: static methods + ANSLIB_DBG macro + +// Optional alias to shorten call sites: +using LOG = ANSCENTER::ANSLIB;`)); +children.push(p("No other includes, no extra .lib to link beyond ANSLIB.lib.")); + +// ---------- Example 1 ---------- +children.push(h1("Example 1 — Function entry/exit (ANSLIB_DBG, the workhorse)")); +children.push(codeBlock( +`int LoadConfig(const std::string& path) { + ANSLIB_DBG("Config", "LoadConfig entry path=%s", path.c_str()); + + if (path.empty()) { + ANSLIB_DBG("Config", "empty path, bailing"); + return -1; + } + + int rc = ParseFile(path); + ANSLIB_DBG("Config", "ParseFile returned rc=%d", rc); + return rc; +}`)); +children.push(p("DebugView output (only when the gate is on):")); +children.push(codeBlock( +`[Config] LoadConfig entry path=C:\\models\\car.cfg +[Config] ParseFile returned rc=0`)); +children.push(p("This is the drop-in replacement for ANS_DBG. Use it for breadcrumb logging that you want to be able to flip on/off remotely.")); + +// ---------- Example 2 ---------- +children.push(h1("Example 2 — Error path (LogError, persisted to spdlog file)")); +children.push(p("When something goes wrong and you want it in the rolling log file (not just DebugView), use LogError:")); +children.push(codeBlock( +`int InitEngine(const std::string& modelPath) { + auto* engine = ANSCENTER::ANSLIB::Create(); + if (!engine) { + LOG::LogError("InitEngine", "ANSLIB::Create returned null", __FILE__, __LINE__); + return -1; + } + + std::string labels; + int rc = engine->Initialize("license-key", modelPath.c_str(), "", + 0.5f, 0.5f, 0.4f, 8, 5, 1, labels); + if (rc < 0) { + char detail[256]; + snprintf(detail, sizeof(detail), + "Initialize failed rc=%d model=%s", rc, modelPath.c_str()); + LOG::LogError("InitEngine", detail, __FILE__, __LINE__); + ANSCENTER::ANSLIB::Destroy(engine); + return rc; + } + + LOG::LogInfo("InitEngine", "engine ready", __FILE__, __LINE__); + return 0; +}`)); +children.push(p("Rolling log file output (ANSLIB.log or whichever name spdlog has):")); +children.push(codeBlock( +`[2026-04-26 14:32:11.483] [error] [foo.cpp] [42] InitEngine: Initialize failed rc=-1 model=C:\\models\\car.zip +[2026-04-26 14:32:11.484] [info] [foo.cpp] [50] InitEngine: engine ready`)); + +// ---------- Example 3 ---------- +children.push(h1("Example 3 — Fatal / unrecoverable (LogFatal)")); +children.push(p("Use when the process can't continue — e.g. license check failed, GPU lost, mandatory model missing:")); +children.push(codeBlock( +`void OnGpuDeviceLost() { + LOG::LogFatal("Engine", + "CUDA device lost, cannot continue inference", + __FILE__, __LINE__); + std::abort(); // or whatever your shutdown path is +}`)); + +// ---------- Example 4 ---------- +children.push(h1("Example 4 — Mixing both (recommended pattern)")); +children.push(p("Use ANSLIB_DBG for the chatty trace breadcrumbs and LogInfo / LogError / LogFatal for things you want preserved in the file:")); +children.push(codeBlock( +`int RunBatch(const std::vector& frames) { + ANSLIB_DBG("Batch", "RunBatch entry frames=%zu", frames.size()); + + if (frames.empty()) { + LOG::LogError("Batch", "called with empty frame list", __FILE__, __LINE__); + return -1; + } + + int processed = 0; + for (const auto& f : frames) { + ANSLIB_DBG("Batch", "processing frame %d (%dx%d)", processed, f.cols, f.rows); + // ...inference... + ++processed; + } + + char summary[128]; + snprintf(summary, sizeof(summary), "RunBatch OK processed=%d", processed); + LOG::LogInfo("Batch", summary, __FILE__, __LINE__); + + ANSLIB_DBG("Batch", "RunBatch exit"); + return processed; +}`)); +children.push(p("DebugView shows the full trace; the log file keeps just the entry/exit summary. This keeps the file small but lets you toggle full tracing remotely when something goes wrong.")); + +// ---------- Example 5 ---------- +children.push(h1("Example 5 — Gating expensive log preparation")); +children.push(p("ANSLIB_DBG already gates the snprintf for you. But if preparing the message itself is expensive (serializing a struct, dumping a Mat, walking a tree), check the gate first manually:")); +children.push(codeBlock( +`void DumpDetections(const std::vector& dets) { + if (!ANSCENTER::ANSLIB::IsDebugViewEnabled()) return; // skip the whole prep + + std::string dump; + for (const auto& d : dets) { + dump += d.className + "(" + std::to_string(d.confidence) + ") "; + } + ANSLIB_DBG("Detections", "%zu objects: %s", dets.size(), dump.c_str()); +}`)); +children.push(p("This skips the entire string-building loop when the gate is off.")); + +// ---------- Example 6 ---------- +children.push(h1("Example 6 — Logging from a class method")); +children.push(p("The __FILE__ / __LINE__ defaults make instance methods cleaner if you don't need them:")); +children.push(codeBlock( +`class MyDetector { +public: + void Process(const cv::Mat& img) { + ANSLIB_DBG("MyDetector", "Process %dx%d", img.cols, img.rows); + + if (img.empty()) { + // No file/line — defaults to "" / 0 + LOG::LogError("MyDetector", "empty image input"); + return; + } + // ... + } +};`)); + +// ---------- Example 7 ---------- +children.push(h1("Example 7 — Macro for \"log everywhere with caller info\" (optional)")); +children.push(p("If you want every LogInfo call to auto-include __FILE__ / __LINE__, define a one-liner in your own code:")); +children.push(codeBlock( +`#define MYAPP_INFO(tag, msg) ::ANSCENTER::ANSLIB::LogInfo (tag, msg, __FILE__, __LINE__) +#define MYAPP_ERROR(tag, msg) ::ANSCENTER::ANSLIB::LogError(tag, msg, __FILE__, __LINE__) + +// Usage: +MYAPP_INFO ("Boot", "starting up"); +MYAPP_ERROR("Net", "connection refused");`)); +children.push(p("This keeps call sites short while preserving file/line tracking in the rolling log.")); + +// ---------- Cheat sheet 1 ---------- +children.push(h1("Cheat sheet — pick the right call")); +children.push(sheetTable( + [3000, 3360, 3000], + ["Want to log…", "Use", "Goes to"], + [ + ["Trace / breadcrumb that's toggled per-incident", + { runs: [codeLine('ANSLIB_DBG("Tag", "fmt %d", x)')] }, + "DebugView + stderr (when gate is on)"], + ["Notable event (started, finished, config loaded)", + { runs: [codeLine('LOG::LogInfo("Tag", "msg", __FILE__, __LINE__)')] }, + "spdlog file + console"], + ["Recoverable error (will retry, will skip, etc.)", + { runs: [codeLine('LOG::LogError("Tag", "msg", __FILE__, __LINE__)')] }, + "spdlog file + console"], + ["Unrecoverable (about to crash / abort)", + { runs: [codeLine('LOG::LogFatal("Tag", "msg", __FILE__, __LINE__)')] }, + "spdlog file + console (critical level)"], + ] +)); +children.push(spacer()); + +// ---------- Cheat sheet 2 ---------- +children.push(h1("Toggling the DebugView gate at runtime")); +children.push(p("LogInfo / LogError / LogFatal are not gated — they always go to the spdlog file. Only ANSLIB_DBG is gated by the mechanism below.")); +children.push(spacer()); +children.push(sheetTable( + [4680, 4680], + ["Action", "Effect"], + [ + [{ runs: [new TextRun({ text: "Drop file ", font: FONT_BODY, size: 20 }), + codeLine("C:\\ProgramData\\ANSCENTER\\ansvisdebugview.txt")] }, + { runs: [new TextRun({ text: "Turns ", font: FONT_BODY, size: 20 }), + codeLine("ANSLIB_DBG"), new TextRun({ text: " (and ", font: FONT_BODY, size: 20 }), + codeLine("ANS_DBG"), new TextRun({ text: ") ON within ~2s", font: FONT_BODY, size: 20 })] }], + ["Delete that file", "Turns them OFF within ~2s"], + [{ runs: [codeLine("set ANSCENTER_DBGVIEW=1"), new TextRun({ text: " (or ", font: FONT_BODY, size: 20 }), + codeLine("=0"), new TextRun({ text: ")", font: FONT_BODY, size: 20 })] }, + "Forces ON or OFF — overrides the file"], + ] +)); + +// ---------- Operational notes ---------- +children.push(h1("Operational notes")); +children.push(bullet("All four entry points (ANSLIB_DBG, LogInfo, LogError, LogFatal) are thread-safe and require no instance — they're static methods that lazy-resolve their underlying DLL symbols on first call.")); +children.push(bullet("If ANSLicensingSystem.dll is missing on the target machine, every logging call silently no-ops (no crash, no exception). The application keeps running.")); +children.push(bullet("ANSLIB_DBG output format is identical to the original ANS_DBG: \"[tag] formatted\\n\" emitted to both OutputDebugStringA and stderr.")); +children.push(bullet("Don't log from static destructors. DLL unload order during process shutdown is undefined on Windows — calling into the licensing DLL after it has been unmapped will crash.")); +children.push(bullet("To strip ANSLIB_DBG entirely at compile time (for performance-critical builds), define -DANSCORE_DEBUGVIEW=0 when compiling the consumer translation unit. The macro becomes ((void)0).")); + +// ========================================================================= +// Document assembly +// ========================================================================= +const doc = new Document({ + styles: { + default: { document: { run: { font: FONT_BODY, size: 22 } } }, + paragraphStyles: [ + { id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true, + run: { size: 36, bold: true, font: FONT_BODY, color: "1F3864" }, + paragraph: { spacing: { before: 360, after: 200 }, outlineLevel: 0 } }, + { id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true, + run: { size: 28, bold: true, font: FONT_BODY, color: "2E74B5" }, + paragraph: { spacing: { before: 280, after: 160 }, outlineLevel: 1 } }, + { id: "Heading3", name: "Heading 3", basedOn: "Normal", next: "Normal", quickFormat: true, + run: { size: 24, bold: true, font: FONT_BODY, color: "404040" }, + paragraph: { spacing: { before: 200, after: 100 }, outlineLevel: 2 } }, + ], + }, + numbering: { + config: [ + { reference: "bullets", + levels: [{ level: 0, format: LevelFormat.BULLET, text: "•", alignment: AlignmentType.LEFT, + style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] }, + ], + }, + sections: [{ + properties: { + page: { + size: { width: 12240, height: 15840 }, + margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 }, + }, + }, + children, + }], +}); + +const outPath = path.join(__dirname, "ANSLIB_Logging_Guide.docx"); +Packer.toBuffer(doc).then(buf => { + fs.writeFileSync(outPath, buf); + console.log("Wrote", outPath, "(" + buf.length + " bytes)"); +}); diff --git a/docs/~$SLIB_Logging_Guide.docx b/docs/~$SLIB_Logging_Guide.docx new file mode 100644 index 0000000000000000000000000000000000000000..b38e8ec628c31d10aef5746aa0ef2cee90b8cc2a GIT binary patch literal 162 zcmWd(DNW2%@Jr9gOazjpm8p3QL;yjC5Qb8QM20*D1qMHcbcPIuOdt=!E@h}>NCnD( yz)=%a!Z=7z2aZG7q+IK>mz5)6*S*Y9~&rw*UaAPaY`% literal 0 HcmV?d00001