Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
shell: cmd
run: |
cl /EHsc /std:c++20 /permissive- /I. /DUNICODE /D_UNICODE /GS /sdl ^
cli_args_debugger.cpp seh_wrapper.cpp qrcodegen.cpp ^
cli_args_debugger.cpp log_manager.cpp seh_wrapper.cpp qrcodegen.cpp ^
/Fe:build\cloud-streaming-args-debugger.exe ^
/Fo:obj\ ^
/link d3d11.lib d3dcompiler.lib d2d1.lib dwrite.lib ole32.lib avrt.lib user32.lib shell32.lib gdi32.lib propsys.lib winmm.lib psapi.lib
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@ cmake_install.cmake

# QR Code generator source files (downloaded during build)
qrcodegen.cpp
qrcodegen.hpp
qrcodegen.hpp

# Local static-analysis artifacts
/.analysis/
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_CURRENT_SOURCE_DIR}/build)
add_executable(cloud-streaming-args-debugger
WIN32 # Specify that the application uses WinMain instead of main
cli_args_debugger.cpp
log_manager.cpp
seh_wrapper.cpp
qrcodegen.cpp # Include QR code generator
)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Check out a preview of the application:

# Compile with MSVC
cl /EHsc /std:c++20 /permissive- /I. /DUNICODE /D_UNICODE ^
cli_args_debugger.cpp seh_wrapper.cpp qrcodegen.cpp ^
cli_args_debugger.cpp log_manager.cpp seh_wrapper.cpp qrcodegen.cpp ^
/Fe:build/ArgumentDebugger.exe ^
/Fo:build/ ^
/link d3d11.lib d3dcompiler.lib d2d1.lib dwrite.lib ole32.lib avrt.lib user32.lib shell32.lib gdi32.lib propsys.lib
Expand Down
149 changes: 6 additions & 143 deletions cli_args_debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@
#include "qrcodegen.hpp"
using qrcodegen::QrCode;

// Logger lives in log_manager.{hpp,cpp} so multiple translation units (tests,
// seh_wrapper) can link against the same instance.
#include "log_manager.hpp"

// Use Microsoft::WRL::ComPtr for COM object management
using Microsoft::WRL::ComPtr;

Expand All @@ -101,129 +105,6 @@ using Microsoft::WRL::ComPtr;
throw std::runtime_error(msg); \
} while (0)

// ===========================================================================
// LOGGING SUPPORT
// ===========================================================================

// Simple header-only logger that writes UTF-16 text to a file
// located next to the executable (CloudStreamingArgsDebugger.log).
//
// • InitLogger() – call once from wWinMain right after COM is up.
// • Log(L"text") – append one line with local‑time prefix.
// • ShowLogs() – read the file and place it into loaded_data_ so it is
// rendered in the right‑upper corner (triggered via "logs")

// Global variables for logging (exported for tests)
FILE* g_log_file = nullptr; // File handle for the log file
std::wstring g_logPath; // Path to the log file

namespace // anonymous, internal
{
CRITICAL_SECTION g_log_cs; // Critical section for thread safety
} // namespace

void InitLogger()
{
PWSTR appdata_path = nullptr;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &appdata_path);
if (SUCCEEDED(hr))
{
g_logPath.assign(appdata_path);
CoTaskMemFree(appdata_path);
g_logPath += L"\\CloudStreamingArgsDebugger\\debug.log";
std::wstring dir = g_logPath.substr(0, g_logPath.find_last_of(L"\\"));
CreateDirectoryW(dir.c_str(), nullptr);
}
else
{
wchar_t exePath[MAX_PATH] = L"\0";
GetModuleFileNameW(nullptr, exePath, MAX_PATH);
g_logPath.assign(exePath);
size_t pos = g_logPath.find_last_of(L"\\/");
if (pos != std::wstring::npos)
g_logPath.erase(pos + 1);
g_logPath += L"CloudStreamingArgsDebugger.log";
}

// Open file in append mode with UTF-16LE encoding
// Use _SH_DENYNO to allow other handles to read the file while we have it open
g_log_file = _wfsopen(g_logPath.c_str(), L"a+, ccs=UTF-16LE", _SH_DENYNO);

// If this is a new file, write BOM (Byte Order Mark)
if (g_log_file && ftell(g_log_file) == 0)
{
fputwc(0xFEFF, g_log_file); // UTF-16LE BOM
}

// Initialize critical section for thread-safe logging
InitializeCriticalSection(&g_log_cs);
}

void Log(const std::wstring& text)
{
if (!g_log_file)
return;

// Lock the critical section before accessing the file
EnterCriticalSection(&g_log_cs);

SYSTEMTIME st;
GetLocalTime(&st);

// Format log entry with timestamp
std::wstring entry = L"[";
entry += std::to_wstring(st.wYear) + L"-";

// Month with padding
if (st.wMonth < 10)
entry += L"0";
entry += std::to_wstring(st.wMonth) + L"-";

// Day with padding
if (st.wDay < 10)
entry += L"0";
entry += std::to_wstring(st.wDay) + L" ";

// Hour with padding
if (st.wHour < 10)
entry += L"0";
entry += std::to_wstring(st.wHour) + L":";

// Minute with padding
if (st.wMinute < 10)
entry += L"0";
entry += std::to_wstring(st.wMinute) + L":";

// Second with padding
if (st.wSecond < 10)
entry += L"0";
entry += std::to_wstring(st.wSecond) + L"] ";

// Add the actual log message
entry += text + L"\n";

// Write to file and flush
fputws(entry.c_str(), g_log_file);
fflush(g_log_file);

// Unlock the critical section
LeaveCriticalSection(&g_log_cs);
}

// Simple log function for SEH wrapper to use
// Exported for seh_wrapper.cpp
void LogSEH(const wchar_t* message)
{
if (message)
{
// Convert C-string to wstring for main Log function
Log(std::wstring(message));
// Also output to debug console for immediate visibility
OutputDebugStringW(message);
OutputDebugStringW(L"\n");
}
}

// Helper function: robust conversion from std::wstring to std::string using WideCharToMultiByte (UTF-8)
std::string wstring_to_string(const std::wstring& wstr)
{
Expand Down Expand Up @@ -450,35 +331,17 @@ int WINAPI wWinMain(HINSTANCE h_instance, HINSTANCE, PWSTR, int cmd_show)
Log(L"Application exit, code = " + std::to_wstring(exit_code));
Log(L"wWinMain: leaving, exitCode=" + std::to_wstring(exit_code));

// Close the log file and delete critical section
if (g_log_file)
{
fclose(g_log_file);
g_log_file = nullptr;
}

// Delete critical section
DeleteCriticalSection(&g_log_cs);

CloseLogger();
CoUninitialize();
return exit_code;
}
catch (const std::exception& ex)
{
// Convert char* to wstring for logging
std::wstring wstr(ex.what(), ex.what() + strlen(ex.what()));
Log(L"Unhandled C++ exception");
Log(L"FATAL: " + wstr);

// Close log file before exit
if (g_log_file)
{
fclose(g_log_file);
g_log_file = nullptr;
}

// Delete critical section in exception case as well
DeleteCriticalSection(&g_log_cs);
CloseLogger();

MessageBoxA(nullptr, ex.what(), "Initialization Error", MB_OK | MB_ICONERROR);
return -1;
Expand Down
115 changes: 115 additions & 0 deletions log_manager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#ifndef UNICODE
#define UNICODE
#define _UNICODE
#endif

#include "log_manager.hpp"

#include <knownfolders.h>
#include <share.h>
#include <shlobj.h>

#pragma comment(lib, "shell32")
#pragma comment(lib, "ole32")

FILE* g_log_file = nullptr;
std::wstring g_logPath;

namespace
{
CRITICAL_SECTION g_log_cs;
bool g_log_cs_initialized = false;
} // namespace

void InitLogger()
{
PWSTR appdata_path = nullptr;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &appdata_path);
if (SUCCEEDED(hr))
{
g_logPath.assign(appdata_path);
CoTaskMemFree(appdata_path);
g_logPath += L"\\CloudStreamingArgsDebugger\\debug.log";
std::wstring dir = g_logPath.substr(0, g_logPath.find_last_of(L"\\"));
CreateDirectoryW(dir.c_str(), nullptr);
}
else
{
wchar_t exePath[MAX_PATH] = L"\0";
GetModuleFileNameW(nullptr, exePath, MAX_PATH);
g_logPath.assign(exePath);
size_t pos = g_logPath.find_last_of(L"\\/");
if (pos != std::wstring::npos)
g_logPath.erase(pos + 1);
g_logPath += L"CloudStreamingArgsDebugger.log";
}

g_log_file = _wfsopen(g_logPath.c_str(), L"a+, ccs=UTF-16LE", _SH_DENYNO);

if (g_log_file && ftell(g_log_file) == 0)
{
fputwc(0xFEFF, g_log_file); // UTF-16LE BOM
}

InitializeCriticalSection(&g_log_cs);
g_log_cs_initialized = true;
}

void Log(const std::wstring& text)
{
if (!g_log_file || !g_log_cs_initialized)
return;

EnterCriticalSection(&g_log_cs);

SYSTEMTIME st;
GetLocalTime(&st);

std::wstring entry = L"[";
entry += std::to_wstring(st.wYear) + L"-";
if (st.wMonth < 10)
entry += L"0";
entry += std::to_wstring(st.wMonth) + L"-";
if (st.wDay < 10)
entry += L"0";
entry += std::to_wstring(st.wDay) + L" ";
if (st.wHour < 10)
entry += L"0";
entry += std::to_wstring(st.wHour) + L":";
if (st.wMinute < 10)
entry += L"0";
entry += std::to_wstring(st.wMinute) + L":";
if (st.wSecond < 10)
entry += L"0";
entry += std::to_wstring(st.wSecond) + L"] ";

entry += text + L"\n";

fputws(entry.c_str(), g_log_file);
fflush(g_log_file);

LeaveCriticalSection(&g_log_cs);
}

void LogSEH(const wchar_t* message)
{
if (!message)
return;
Log(std::wstring(message));
OutputDebugStringW(message);
OutputDebugStringW(L"\n");
}

void CloseLogger()
{
if (g_log_file)
{
fclose(g_log_file);
g_log_file = nullptr;
}
if (g_log_cs_initialized)
{
DeleteCriticalSection(&g_log_cs);
g_log_cs_initialized = false;
}
}
33 changes: 33 additions & 0 deletions log_manager.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once

#include <cstdio>
#include <string>
#include <windows.h>

// UTF-16LE log file written next to the executable (or to %APPDATA%\CloudStreamingArgsDebugger\).
//
// InitLogger() - call once from wWinMain after COM is initialized.
// Log(L"...") - append one line prefixed with local time, thread-safe.
// LogSEH(...) - thin wrapper used from the SEH-guarded audio thread.
// CloseLogger() - flush, close the file, and release the critical section.
// GetLogPath() - path to the log file (empty before InitLogger()).
//
// The raw globals `g_log_file` and `g_logPath` remain visible with extern
// linkage so that existing unit tests (tests/logging_tests.cpp) keep compiling.

extern FILE* g_log_file;
extern std::wstring g_logPath;

void InitLogger();
void Log(const std::wstring& text);
void LogSEH(const wchar_t* message);
void CloseLogger();

inline const std::wstring& GetLogPath()
{
return g_logPath;
}
inline FILE* GetLogFile()
{
return g_log_file;
}
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ set(TEST_SOURCES
set(PARENT_SOURCES
../qrcodegen.cpp
../cli_args_debugger.cpp
../log_manager.cpp
../seh_wrapper.cpp
)

Expand Down
2 changes: 1 addition & 1 deletion validate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ echo "🔍 Checking for common C++ syntax issues..."
# Check for missing semicolons, unmatched braces, etc.
syntax_errors=0

for file in cli_args_debugger.cpp seh_wrapper.cpp; do
for file in cli_args_debugger.cpp seh_wrapper.cpp log_manager.cpp; do
if [ -f "$file" ]; then
echo "Checking $file..."

Expand Down
Loading