diff --git a/LogMonitor/CMakeLists.txt b/LogMonitor/CMakeLists.txt index dfbbd4b..b44b0d5 100644 --- a/LogMonitor/CMakeLists.txt +++ b/LogMonitor/CMakeLists.txt @@ -3,21 +3,30 @@ cmake_minimum_required(VERSION 3.15) # Define the project project(LogMonitor) +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +set(Boost_USE_STATIC_LIBS ON) +set(Boost_USE_STATIC_RUNTIME ON) + # Set C++ standard set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_BUILD_TYPE Release) -set(VCPKG_TARGET_TRIPLET x64-windows) +set(VCPKG_TARGET_TRIPLET x64-windows-static) # Use vcpkg if available -if (DEFINED ENV{VCPKG_ROOT}) +if(DEFINED ENV{VCPKG_ROOT}) set(VCPKG_ROOT $ENV{VCPKG_ROOT}) - set(CMAKE_TOOLCHAIN_FILE "${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Toolchain file for vcpkg" FORCE) - - # Set Boost paths - set(BOOST_ROOT "${VCPKG_ROOT}/installed/x64-windows" CACHE PATH "Boost installation root") - set(Boost_INCLUDE_DIR "${VCPKG_ROOT}/installed/x64-windows/include" CACHE PATH "Boost include directory") + set(CMAKE_TOOLCHAIN_FILE "${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file" FORCE) + set(VCPKG_TARGET_TRIPLET "x64-windows-static" CACHE STRING "Vcpkg target triplet") +endif() + +# Enforce static MSVC runtime (/MT or /MTd) +if(MSVC) + foreach(flag_var CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_DEBUG + CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_DEBUG) + string(REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") + endforeach() endif() # Set Windows SDK version if available @@ -25,11 +34,21 @@ if (DEFINED ENV{SDKVersion}) set(CMAKE_SYSTEM_VERSION $ENV{SDKVersion}) endif() +# Enable Unicode globally +add_definitions(-DUNICODE -D_UNICODE) + +# Enable warnings +if (MSVC) + add_compile_options(/W4) +else() + add_compile_options(-Wall -Wextra -pedantic) +endif() + # Enable testing framework enable_testing() -# Enable Unicode globally -add_definitions(-DUNICODE -D_UNICODE) +# Find dependencies +find_package(nlohmann_json CONFIG REQUIRED) # Include subdirectories for main and test executables add_subdirectory(src) # Add main executable's CMake diff --git a/LogMonitor/LogMonitorTests/CMakeLists.txt b/LogMonitor/LogMonitorTests/CMakeLists.txt index 79c50ab..1b7a63b 100644 --- a/LogMonitor/LogMonitorTests/CMakeLists.txt +++ b/LogMonitor/LogMonitorTests/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.15) project(LogMonitorTests) -find_package(Boost REQUIRED COMPONENTS json) +find_package(nlohmann_json CONFIG REQUIRED) # Automatically gather all test source files file(GLOB_RECURSE TEST_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.cpp") @@ -22,7 +22,7 @@ target_compile_definitions(LogMonitorTests PRIVATE LOGMONITORTESTS_EXPORTS) target_include_directories(LogMonitorTests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} # Includes Utility.h and pch.h ${CMAKE_CURRENT_SOURCE_DIR}/../src - ${Boost_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/../src/LogMonitor ) # Set Windows-specific linker flags @@ -33,8 +33,8 @@ set_target_properties(LogMonitorTests PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" ) -# Link LogMonitor and Boost.JSON -target_link_libraries(LogMonitorTests PRIVATE LogMonitor Boost::json) +# Link LogMonitor and Nlohmann JSON +target_link_libraries(LogMonitorTests PRIVATE LogMonitorLib nlohmann_json::nlohmann_json) # Enable testing enable_testing() diff --git a/LogMonitor/LogMonitorTests/UtilityTests.cpp b/LogMonitor/LogMonitorTests/UtilityTests.cpp index beb9efe..8642d77 100644 --- a/LogMonitor/LogMonitorTests/UtilityTests.cpp +++ b/LogMonitor/LogMonitorTests/UtilityTests.cpp @@ -60,6 +60,7 @@ namespace UtilityTests std::wstring expect = L"say, \\\"hello\\\""; Utility::SanitizeJson(str); Assert::IsTrue(str == expect, L"should escape \""); + str = L"\"hello\""; expect = L"\\\"hello\\\""; Utility::SanitizeJson(str); @@ -69,6 +70,7 @@ namespace UtilityTests expect = L"hello\\r\\nworld"; Utility::SanitizeJson(str); Assert::IsTrue(str == expect, L"should escape \r and \n"); + str = L"\r\nHello\r\n"; expect = L"\\r\\nHello\\r\\n"; Utility::SanitizeJson(str); @@ -78,6 +80,7 @@ namespace UtilityTests expect = L"\\\\Driver\\\\XX\\\\"; Utility::SanitizeJson(str); Assert::IsTrue(str == expect, L"should escape \\"); + str = L"C:\\Drive\\XX"; expect = L"C:\\\\Drive\\\\XX"; Utility::SanitizeJson(str); diff --git a/LogMonitor/LogMonitorTests/pch.h b/LogMonitor/LogMonitorTests/pch.h index c6ad8c3..6c2a695 100644 --- a/LogMonitor/LogMonitorTests/pch.h +++ b/LogMonitor/LogMonitorTests/pch.h @@ -52,6 +52,7 @@ #include #include #include +#include #include "../src/LogMonitor/Utility.h" #include "../src/LogMonitor/Parser/ConfigFileParser.h" #include "../src/LogMonitor/Parser/LoggerSettings.h" diff --git a/LogMonitor/src/CMakeLists.txt b/LogMonitor/src/CMakeLists.txt index 8f570a9..c8c9e60 100644 --- a/LogMonitor/src/CMakeLists.txt +++ b/LogMonitor/src/CMakeLists.txt @@ -2,22 +2,45 @@ cmake_minimum_required(VERSION 3.15) project(LogMonitor) -find_package(Boost REQUIRED COMPONENTS json) +find_package(nlohmann_json CONFIG REQUIRED) +# Gather source files file(GLOB_RECURSE SourceFiles RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.cpp") +file(GLOB_RECURSE HeaderFiles RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.h") -# Define LogMonitor as a library -add_library(LogMonitor ${SourceFiles}) +# Define LogMonitorLib as a static library +add_library(LogMonitorLib STATIC ${SourceFiles}) + +# Set the output name of the static library to "LogMonitor.lib" +set_target_properties(LogMonitorLib PROPERTIES + OUTPUT_NAME "LogMonitor" +) + +# Define LogMonitor as an executable +add_executable(LogMonitor ${SourceFiles}) # Add precompiled headers (PCH) target_precompile_headers(LogMonitor PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/LogMonitor/pch.h) -# Include directories for LogMonitor +# Include directories for both targets +target_include_directories(LogMonitorLib PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/LogMonitor + ${CMAKE_CURRENT_SOURCE_DIR}/LogMonitor/FileMonitor +) + target_include_directories(LogMonitor PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/LogMonitor - ${CMAKE_CURRENT_SOURCE_DIR}/LogMonitor/FileMonitor - ${Boost_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/LogMonitor/FileMonitor ) -# Link Boost JSON to LogMonitor -target_link_libraries(LogMonitor PRIVATE Boost::json) +# Link dependencies +target_link_libraries(LogMonitorLib PRIVATE nlohmann_json::nlohmann_json) +target_link_libraries(LogMonitor PRIVATE LogMonitorLib nlohmann_json::nlohmann_json) + +# Set output path +set_target_properties(LogMonitor PROPERTIES + OUTPUT_NAME "LogMonitor" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" +) diff --git a/LogMonitor/src/LogMonitor/JsonProcessor.cpp b/LogMonitor/src/LogMonitor/JsonProcessor.cpp index b962b66..246ff8a 100644 --- a/LogMonitor/src/LogMonitor/JsonProcessor.cpp +++ b/LogMonitor/src/LogMonitor/JsonProcessor.cpp @@ -5,6 +5,13 @@ #include "pch.h" +#ifdef _WIN32 +#include +#define strcasecmp _stricmp +#endif + +using json = nlohmann::json; + /// /// Loads a JSON file, parses its contents, and processes its configuration settings. /// @@ -16,17 +23,22 @@ bool ReadConfigFile(_In_ const PWCHAR jsonFile, _Out_ LoggerSettings& Config) { std::wstring wstr(jsonFile); std::string jsonString = readJsonFromFile(wstr); - boost::json::value parsedJson; try { - parsedJson = boost::json::parse(jsonString); - const boost::json::value& logConfig = parsedJson.at("LogConfig"); + json parsedJson = json::parse(jsonString); + + if (!parsedJson.contains("LogConfig")) { + logWriter.TraceError(L"Missing 'LogConfig' section in JSON."); + return false; + } + + const auto& logConfig = parsedJson["LogConfig"]; if (!processLogConfig(logConfig, Config)) { logWriter.TraceError(L"Failed to fully process LogConfig."); return false; } } - catch (const boost::system::system_error& e) { + catch (const json::parse_error& e) { logWriter.TraceError( Utility::FormatString( L"Error parsing JSON: %S", @@ -49,11 +61,6 @@ bool ReadConfigFile(_In_ const PWCHAR jsonFile, _Out_ LoggerSettings& Config) { return false; } - if (parsedJson.is_null()) { - logWriter.TraceError(L"Parsed JSON is null."); - return false; - } - return true; } @@ -66,15 +73,14 @@ bool ReadConfigFile(_In_ const PWCHAR jsonFile, _Out_ LoggerSettings& Config) { std::string readJsonFromFile(_In_ const std::wstring& filePath) { // Open the file as a wide character input stream std::wifstream wif(filePath); - if (!wif.is_open()) { logWriter.TraceError(L"Failed to open JSON file."); return ""; - } + } // Read the file into a wide string buffer std::wstringstream wss; - wss << wif.rdbuf(); + wss << wif.rdbuf(); wif.close(); // Convert the wstring buffer to a UTF-8 string @@ -95,14 +101,25 @@ std::string readJsonFromFile(_In_ const std::wstring& filePath) { /// otherwise, returns false if parsing fails. /// bool handleEventLog( - _In_ const boost::json::value& source, + _In_ const json& source, _In_ AttributesMap& Attributes, _Inout_ std::vector>& Sources ) { try { - bool startAtOldest = source.as_object().at("startAtOldestRecord").as_bool(); - bool multiLine = source.at("eventFormatMultiLine").as_bool(); - std::string customLogFormat = source.as_object().at("customLogFormat").as_string().c_str(); + bool startAtOldest = false; + if (source.contains("startAtOldestRecord") && source["startAtOldestRecord"].is_boolean()) { + startAtOldest = source["startAtOldestRecord"].get(); + } + + bool multiLine = false; + if (source.contains("eventFormatMultiLine") && source["eventFormatMultiLine"].is_boolean()) { + multiLine = source["eventFormatMultiLine"].get(); + } + + std::string customLogFormat; + if (source.contains("customLogFormat") && source["customLogFormat"].is_string()) { + customLogFormat = source["customLogFormat"].get(); + } Attributes[JSON_TAG_START_AT_OLDEST_RECORD] = reinterpret_cast( std::make_unique(startAtOldest ? L"true" : L"false").release() @@ -117,11 +134,12 @@ bool handleEventLog( ); // Process channels if they exist - if (source.as_object().contains("channels")) { + if (source.contains("channels") && source["channels"].is_array()) { auto channels = new std::vector(); - for (const auto& channel : source.as_object().at("channels").as_array()) { - std::string name = getJsonStringCaseInsensitive(channel.as_object(), "name"); - std::string levelString = getJsonStringCaseInsensitive(channel.as_object(), "level"); + + for (const auto& channel : source["channels"]) { + std::string name = getJsonStringCaseInsensitive(channel, "name"); + std::string levelString = getJsonStringCaseInsensitive(channel, "level"); EventLogChannel eventChannel(Utility::string_to_wstring(name), EventChannelLogLevel::Error); @@ -131,13 +149,14 @@ bool handleEventLog( logWriter.TraceError( Utility::FormatString( L"Invalid level string: %S", - level.c_str() + levelString.c_str() ).c_str() ); + delete channels; // Prevent leak return false; } - channels->push_back(eventChannel); // Add to the vector + channels->push_back(eventChannel); } Attributes[JSON_TAG_CHANNELS] = reinterpret_cast(channels); @@ -154,16 +173,16 @@ bool handleEventLog( Sources.push_back(std::reinterpret_pointer_cast(std::move(sourceEventLog))); return true; - } - catch (const std::exception& e) { - logWriter.TraceError( - Utility::FormatString( - L"Error parsing EventLog source: %S", - e.what() - ).c_str() - ); - return false; - } + } + catch (const std::exception& e) { + logWriter.TraceError( + Utility::FormatString( + L"Error parsing EventLog source: %S", + e.what() + ).c_str() + ); + return false; + } } /// @@ -177,14 +196,17 @@ bool handleEventLog( /// otherwise, returns false if parsing fails. /// bool handleFileLog( - _In_ const boost::json::value& source, + _In_ const json& source, _In_ AttributesMap& Attributes, _Inout_ std::vector>& Sources ) { - std::string directory = getJsonStringCaseInsensitive(source.as_object(), "directory"); - std::string filter = getJsonStringCaseInsensitive(source.as_object(), "filter"); - bool includeSubdirs = source.at("includeSubdirectories").as_bool(); - std::string customLogFormat = getJsonStringCaseInsensitive(source.as_object(), "customLogFormat"); + std::string directory = getJsonStringCaseInsensitive(source, "directory"); + std::string filter = getJsonStringCaseInsensitive(source, "filter"); + bool includeSubdirs = false; + if (source.contains("includeSubdirectories") && source["includeSubdirectories"].is_boolean()) { + includeSubdirs = source["includeSubdirectories"].get(); + } + std::string customLogFormat = getJsonStringCaseInsensitive(source, "customLogFormat"); Attributes[JSON_TAG_DIRECTORY] = reinterpret_cast( std::make_unique(Utility::string_to_wstring(directory)).release() @@ -206,7 +228,6 @@ bool handleFileLog( } Sources.push_back(std::reinterpret_pointer_cast(std::move(sourceFile))); - return true; } @@ -221,14 +242,21 @@ bool handleFileLog( /// otherwise, returns false if parsing fails. /// bool handleETWLog( - _In_ const boost::json::value& source, + _In_ const nlohmann::json& source, _In_ AttributesMap& Attributes, _Inout_ std::vector>& Sources ) { - bool multiLine = source.at("eventFormatMultiLine").as_bool(); - std::string customLogFormat = source.at("customLogFormat").as_string().c_str(); + bool multiLine = false; + if (source.contains("eventFormatMultiLine") && source["eventFormatMultiLine"].is_boolean()) { + multiLine = source["eventFormatMultiLine"].get(); + } - // Store multiLine and customLogFormat as wide strings in Attributes. + std::string customLogFormat; + if (source.contains("customLogFormat") && source["customLogFormat"].is_string()) { + customLogFormat = source["customLogFormat"].get(); + } + + // Store multiLine and customLogFormat as wide strings in Attributes Attributes[JSON_TAG_FORMAT_MULTILINE] = reinterpret_cast( std::make_unique(multiLine ? L"true" : L"false").release() ); @@ -238,25 +266,25 @@ bool handleETWLog( std::vector etwProviders; - const boost::json::array& providers = source.at("providers").as_array(); - for (const auto& provider : providers) { - std::string providerName = getJsonStringCaseInsensitive(provider.as_object(), "providerName"); - std::string providerGuid = getJsonStringCaseInsensitive(provider.as_object(), "providerGuid"); - std::string level = getJsonStringCaseInsensitive(provider.as_object(), "level"); - - ETWProvider etwProvider; - etwProvider.ProviderName = Utility::string_to_wstring(providerName); - etwProvider.SetProviderGuid(Utility::string_to_wstring(providerGuid)); - etwProvider.StringToLevel(Utility::string_to_wstring(level)); - - // Check if "keywords" exists and process it if available. - auto keywordsIter = provider.as_object().find("keywords"); - if (keywordsIter != provider.as_object().end()) { - std::string keywords = keywordsIter->value().as_string().c_str(); - etwProvider.Keywords = wcstoull(Utility::string_to_wstring(keywords).c_str(), NULL, 0); - } + if (source.contains("providers") && source["providers"].is_array()) { + for (const auto& provider : source["providers"]) { + std::string providerName = getJsonStringCaseInsensitive(provider, "providerName"); + std::string providerGuid = getJsonStringCaseInsensitive(provider, "providerGuid"); + std::string level = getJsonStringCaseInsensitive(provider, "level"); + + ETWProvider etwProvider; + etwProvider.ProviderName = Utility::string_to_wstring(providerName); + etwProvider.SetProviderGuid(Utility::string_to_wstring(providerGuid)); + etwProvider.StringToLevel(Utility::string_to_wstring(level)); + + // Check if "keywords" exists and process it + if (provider.contains("keywords") && provider["keywords"].is_string()) { + std::string keywords = provider["keywords"].get(); + etwProvider.Keywords = wcstoull(Utility::string_to_wstring(keywords).c_str(), nullptr, 0); + } - etwProviders.push_back(etwProvider); + etwProviders.push_back(std::move(etwProvider)); + } } // Store the ETW providers in Attributes. @@ -289,11 +317,14 @@ bool handleETWLog( /// otherwise, returns false if parsing fails. /// bool handleProcessLog( - _In_ const boost::json::value& source, + _In_ const nlohmann::json& source, _In_ AttributesMap& Attributes, _Inout_ std::vector>& Sources ) { - std::string customLogFormat = source.at("customLogFormat").as_string().c_str(); + std::string customLogFormat; + if (source.contains("customLogFormat") && source["customLogFormat"].is_string()) { + customLogFormat = source["customLogFormat"].get(); + } Attributes[JSON_TAG_CUSTOM_LOG_FORMAT] = reinterpret_cast( std::make_unique(Utility::string_to_wstring(customLogFormat)).release() @@ -319,28 +350,32 @@ bool handleProcessLog( /// Returns true if the log configuration is valid and sources are successfully processed; /// otherwise, returns false if the configuration is invalid or sources fail to process. /// -bool processLogConfig(const boost::json::value& logConfig, _Out_ LoggerSettings& Config) { +bool processLogConfig(_In_ const nlohmann::json& logConfig, _Out_ LoggerSettings& Config) { if (!logConfig.is_object()) { logWriter.TraceError(L"Invalid LogConfig object."); return false; } - const boost::json::object& obj = logConfig.as_object(); + const auto& obj = logConfig; + + std::string logFormat; + if (obj.contains("logFormat") && obj["logFormat"].is_string()) { + logFormat = obj["logFormat"].get(); + } - std::string logFormat = getJsonStringCaseInsensitive(obj, "logFormat"); if (!logFormat.empty()) { Config.LogFormat = Utility::string_to_wstring(logFormat); - } else { + } else { logWriter.TraceError(L"LogFormat not found in LogConfig. Using default log format."); - } + } - if (!obj.contains("sources") || !obj.at("sources").is_array()) { + if (!obj.contains("sources") || !obj["sources"].is_array()) { logWriter.TraceError(L"Sources array not found or invalid in LogConfig."); return false; } // Process the sources array - const boost::json::array& sources = obj.at("sources").as_array(); + const auto& sources = obj["sources"]; return processSources(sources, Config); } @@ -354,9 +389,14 @@ bool processLogConfig(const boost::json::value& logConfig, _Out_ LoggerSettings& /// Returns true if all sources are successfully processed; /// otherwise, returns false if any source fails to process. /// -bool processSources(const boost::json::array& sources, _Out_ LoggerSettings& Config) { +bool processSources(_In_ const nlohmann::json& sources, _Out_ LoggerSettings& Config) { bool overallSuccess = true; + if (!sources.is_array()) { + logWriter.TraceError(L"Sources is not an array."); + return false; + } + for (const auto& source : sources) { if (!source.is_object()) { logWriter.TraceError(L"Skipping invalid source entry (not an object)."); @@ -364,8 +404,12 @@ bool processSources(const boost::json::array& sources, _Out_ LoggerSettings& Con continue; } - const boost::json::object& srcObj = source.as_object(); - std::string sourceType = getJsonStringCaseInsensitive(srcObj, "type"); + const auto& srcObj = source; + + std::string sourceType; + if (srcObj.contains("type") && srcObj["type"].is_string()) { + sourceType = srcObj["type"].get(); + } if (sourceType.empty()) { logWriter.TraceError(L"Skipping source with missing or empty type."); @@ -402,13 +446,13 @@ bool processSources(const boost::json::array& sources, _Out_ LoggerSettings& Con Utility::string_to_wstring(sourceType).c_str() ).c_str() ); - overallSuccess = false; + overallSuccess = false; } cleanupAttributes(sourceAttributes); } - return overallSuccess; + return overallSuccess; } /// @@ -431,18 +475,16 @@ void cleanupAttributes(_In_ AttributesMap& Attributes) { /// The JSON object to search for the key. /// The key to search for in the JSON object, case-insensitive. /// string value associated with the key if found -std::string getJsonStringCaseInsensitive(_In_ const boost::json::object& obj, _In_ const std::string& key) { - auto it = std::find_if(obj.begin(), obj.end(), - [&](const auto& item) { - std::string currentKey = std::string(item.key().data()); - std::transform(currentKey.begin(), currentKey.end(), currentKey.begin(), ::tolower); - std::string lowerKey = key; - std::transform(lowerKey.begin(), lowerKey.end(), lowerKey.begin(), ::tolower); - return currentKey == lowerKey; - }); - - if (it != obj.end() && it->value().is_string()) { - return std::string(it->value().as_string().data()); +std::string getJsonStringCaseInsensitive(_In_ const nlohmann::json& obj, _In_ const std::string& key) { + auto lowerKey = key; + std::transform(lowerKey.begin(), lowerKey.end(), lowerKey.begin(), ::tolower); + + for (auto it = obj.begin(); it != obj.end(); ++it) { + std::string currentKey = it.key(); + std::transform(currentKey.begin(), currentKey.end(), currentKey.begin(), ::tolower); + if (currentKey == lowerKey && it.value().is_string()) { + return it.value().get(); + } } logWriter.TraceError( @@ -451,7 +493,6 @@ std::string getJsonStringCaseInsensitive(_In_ const boost::json::object& obj, _I ).c_str() ); - // Return an empty string if the key is not found or the value is not a string. return ""; } diff --git a/LogMonitor/src/LogMonitor/JsonProcessor.h b/LogMonitor/src/LogMonitor/JsonProcessor.h index fd778ed..02a12db 100644 --- a/LogMonitor/src/LogMonitor/JsonProcessor.h +++ b/LogMonitor/src/LogMonitor/JsonProcessor.h @@ -6,25 +6,25 @@ #pragma once bool handleEventLog( - _In_ const boost::json::value& source, + _In_ const nlohmann::json& source, _In_ AttributesMap& Attributes, _Inout_ std::vector>& Sources ); bool handleFileLog( - _In_ const boost::json::value& source, + _In_ const nlohmann::json& source, _In_ AttributesMap& Attributes, _Inout_ std::vector>& Sources ); bool handleETWLog( - _In_ const boost::json::value& source, + _In_ const nlohmann::json& source, _In_ AttributesMap& Attributes, _Inout_ std::vector>& Sources ); bool handleProcessLog( - _In_ const boost::json::value& source, + _In_ const nlohmann::json& source, _In_ AttributesMap& Attributes, _Inout_ std::vector>& Sources ); @@ -39,12 +39,12 @@ std::string readJsonFromFile( ); bool processLogConfig( - const boost::json::value& logConfig, + const nlohmann::json& logConfig, _Out_ LoggerSettings& Config ); bool processSources( - const boost::json::array& sources, + _In_ const nlohmann::json& sources, _Out_ LoggerSettings& Config ); @@ -53,6 +53,6 @@ void cleanupAttributes( ); std::string getJsonStringCaseInsensitive( - _In_ const boost::json::object& obj, + _In_ const nlohmann::json& obj, _In_ const std::string& key -); +); \ No newline at end of file diff --git a/LogMonitor/src/LogMonitor/Utility.cpp b/LogMonitor/src/LogMonitor/Utility.cpp index 69b86de..6093369 100644 --- a/LogMonitor/src/LogMonitor/Utility.cpp +++ b/LogMonitor/src/LogMonitor/Utility.cpp @@ -7,6 +7,7 @@ #include using namespace std; +using json = nlohmann::json; /// @@ -265,47 +266,32 @@ bool Utility::isJsonNumber(_In_ std::wstring& str) /// void Utility::SanitizeJson(_Inout_ std::wstring& str) { - size_t i = 0; - while (i < str.size()) { - auto sub = str.substr(i, 1); - auto s = str.substr(0, i + 1); - if (sub == L"\"") { - if ((i > 0 && str.substr(i - 1, 1) != L"\\" && str.substr(i - 1, 1) != L"~") - || i == 0) - { - str.replace(i, 1, L"\\\""); - i++; - } else if (i > 0 && str.substr(i - 1, 1) == L"~") { - str.replace(i - 1, 1, L""); - i--; - } - } - else if (sub == L"\\") { - if ((i < str.size() - 1 && str.substr(i + 1, 1) != L"\\") - || i == str.size() - 1) - { - str.replace(i, 1, L"\\\\"); - i++; - } - else { - i += 2; - } - } - else if (sub == L"\n") { - if (i == 0 || str.substr(i - 1, 1) != L"\\") { - str.replace(i, 1, L"\\n"); - i++; - } - } - else if (sub == L"\r") { - if ((i > 0 && str.substr(i - 1, 1) != L"\\") - || i == 0) - { - str.replace(i, 1, L"\\r"); - i++; - } + try + { + std::string utf8 = Utility::wstring_to_string(str); + + // Remove any embedded nulls + utf8.erase(std::find(utf8.begin(), utf8.end(), '\0'), utf8.end()); + + // Escape the string using JSON + json j = utf8; + std::string escapedUtf8 = j.dump(); + + // Strip the outer quotes + if (escapedUtf8.length() >= 2 && + escapedUtf8.front() == '"' && + escapedUtf8.back() == '"') + { + escapedUtf8 = escapedUtf8.substr(1, escapedUtf8.length() - 2); } - i++; + + // Convert back to wide string + str = Utility::string_to_wstring(escapedUtf8); + } + catch (const json::exception& e) + { + std::wcerr << L"SanitizeJson failed: " << Utility::string_to_wstring(e.what()) << std::endl; + str = L"[invalid JSON string]"; } } diff --git a/LogMonitor/src/LogMonitor/pch.h b/LogMonitor/src/LogMonitor/pch.h index 9612d3c..b118b3c 100644 --- a/LogMonitor/src/LogMonitor/pch.h +++ b/LogMonitor/src/LogMonitor/pch.h @@ -47,7 +47,7 @@ #include "shlwapi.h" #include #include -#include +#include #include "Utility.h" #include "Parser/ConfigFileParser.h" #include "Parser/LoggerSettings.h" diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5d981bc..b58c7f7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -10,9 +10,10 @@ pool: variables: solution: '**/*.sln' - buildPlatform: 'x64' # Set to x64 or ARM64 depending on the build you want + buildPlatform: 'x64' buildConfiguration: 'Release' VCPKG_ROOT: '$(Build.SourcesDirectory)\vcpkg' + VCPKG_TRIPLET: 'x64-windows-static' VCPKG_CMAKE_OPTIONS: '-DCMAKE_TOOLCHAIN_FILE=$(VCPKG_ROOT)\scripts\buildsystems\vcpkg.cmake' SDKVersion: '' @@ -28,11 +29,10 @@ jobs: git clone https://github.com/microsoft/vcpkg.git $(VCPKG_ROOT) cd $(VCPKG_ROOT) .\bootstrap-vcpkg.bat - .\vcpkg.exe integrate install # Integrate vcpkg with MSBuild + .\vcpkg.exe integrate install - # Install Boost JSON for the selected platform (x64 or ARM64) - task: PowerShell@2 - displayName: 'Install Boost JSON' + displayName: 'Install nlohmann_json' inputs: targetType: 'inline' script: | @@ -40,38 +40,9 @@ jobs: Write-Error "vcpkg.exe not found. Exiting..." exit 1 } - Write-Host "Installing Boost JSON for platform: $(buildPlatform)..." - if ('$(buildPlatform)' -eq 'x64') { - & "$(VCPKG_ROOT)\vcpkg.exe" install boost-json:x64-windows - } elseif ('$(buildPlatform)' -eq 'ARM64') { - & "$(VCPKG_ROOT)\vcpkg.exe" install boost-json:arm64-windows - } else { - Write-Error "Unsupported build platform: $(buildPlatform)" - exit 1 - } - - # Verify vcpkg integration - - task: PowerShell@2 - displayName: 'Verify vcpkg Integration' - inputs: - targetType: 'inline' - script: | - echo "Verifying vcpkg integration for platform: $(buildPlatform)..." - & "$(VCPKG_ROOT)\vcpkg.exe" integrate install - if ('$(buildPlatform)' -eq 'x64') { - if (!(Test-Path "$(VCPKG_ROOT)\installed\x64-windows\include\boost\json.hpp")) { - Write-Error "Boost JSON header not found for x64. Exiting..." - exit 1 - } - } elseif ('$(buildPlatform)' -eq 'ARM64') { - if (!(Test-Path "$(VCPKG_ROOT)\installed\arm64-windows\include\boost\json.hpp")) { - Write-Error "Boost JSON header not found for ARM64. Exiting..." - exit 1 - } - } - Write-Output "vcpkg integration verified." + Write-Host "Installing nlohmann_json for platform: $(VCPKG_TRIPLET)..." + & "$(VCPKG_ROOT)\vcpkg.exe" install nlohmann-json:$(VCPKG_TRIPLET) - # Get installed Windows SDK version - task: PowerShell@2 displayName: 'Check Installed Windows SDK Version' inputs: @@ -104,13 +75,12 @@ jobs: exit 1 } - # Configure CMake based on selected platform - task: CMake@1 displayName: 'Configure CMake' inputs: workingDirectory: '$(Build.SourcesDirectory)\LogMonitor' cmakeArgs: | - -A $(buildPlatform) -S $(Build.SourcesDirectory)\LogMonitor -B $(Build.BinariesDirectory)\LogMonitor\$(buildPlatform) + -A $(buildPlatform) -S $(Build.SourcesDirectory)\LogMonitor -B $(Build.BinariesDirectory)\LogMonitor\$(buildPlatform) $(VCPKG_CMAKE_OPTIONS) -DVCPKG_TARGET_TRIPLET=$(VCPKG_TRIPLET) - task: CMake@1 displayName: 'Build with CMake' @@ -118,20 +88,17 @@ jobs: workingDirectory: '$(Build.BinariesDirectory)\LogMonitor\$(buildPlatform)' cmakeArgs: '--build . --config $(buildConfiguration) --parallel' - # Component Governance - task: ComponentGovernanceComponentDetection@0 inputs: scanType: 'Register' verbosity: 'Verbose' alertWarningLevel: 'Low' - # Install Visual Studio Test Platform - task: VisualStudioTestPlatformInstaller@1 inputs: packageFeedSelector: 'nugetOrg' versionSelector: 'latestPreRelease' - # Run Tests - task: VSTest@2 inputs: testSelector: 'testAssemblies' @@ -142,7 +109,6 @@ jobs: rerunFailedTests: true rerunMaxAttempts: 3 - # Publish build artifacts - task: PublishPipelineArtifact@1 inputs: targetPath: '$(Build.BinariesDirectory)\LogMonitor\$(buildPlatform)\$(buildConfiguration)\' diff --git a/build.cmd b/build.cmd index 27ea814..0318b59 100644 --- a/build.cmd +++ b/build.cmd @@ -1 +1,64 @@ -msbuild LogMonitor\LogMonitor.sln /p:platform=x64 /p:configuration=Release \ No newline at end of file +@echo off +setlocal + +REM Set the path to the LogMonitor folder +set PROJECT_DIR=%~dp0LogMonitor + +REM Set up environment variables +set VCPKG_DIR=%~dp0vcpkg +set BUILD_DIR=%~dp0build +set TOOLCHAIN_FILE=%VCPKG_DIR%\scripts\buildsystems\vcpkg.cmake +set BUILD_TYPE=Release +set BUILD_ARCH=x64 +set VCPKG_TRIPLET=%BUILD_ARCH%-windows-static + +REM Step 1: Clone and bootstrap vcpkg if not already done +if not exist "%VCPKG_DIR%\vcpkg.exe" ( + echo === Cloning vcpkg === + git clone https://github.com/microsoft/vcpkg.git "%VCPKG_DIR%" + echo === Bootstrapping vcpkg === + pushd "%VCPKG_DIR%" + .\bootstrap-vcpkg.bat + popd +) + +REM Step 2: Install nlohmann-json dependencies +echo === Installing nlohmann dependencies === +"%VCPKG_DIR%\vcpkg.exe" install nlohmann-json:%VCPKG_TRIPLET% + +if errorlevel 1 ( + echo Failed to install nlohmann-json dependencies. + exit /b 1 +) + +REM Create the build directory if it doesn't exist +if not exist "%PROJECT_DIR%\build" ( + mkdir "%PROJECT_DIR%\build" +) + +REM Navigate into the build directory +cd /d "%PROJECT_DIR%\build" + +REM Run CMake configuration with toolchain and triplet +cmake .. ^ + -DCMAKE_TOOLCHAIN_FILE=%TOOLCHAIN_FILE% ^ + -DCMAKE_BUILD_TYPE=%BUILD_TYPE% ^ + -DVCPKG_TARGET_TRIPLET=%VCPKG_TRIPLET% ^ + -A %BUILD_ARCH% + +if errorlevel 1 ( + echo CMake configuration failed. + exit /b 1 +) + +REM Build the project +cmake --build . --config %BUILD_TYPE% --parallel + +if errorlevel 1 ( + echo Build failed. + exit /b 1 +) + +echo === Build completed successfully === + +endlocal