diff --git a/.github/workflows/sdl-compliance-pipeline.yml b/.github/workflows/sdl-compliance-pipeline.yml index 6291facd..c2d7184a 100644 --- a/.github/workflows/sdl-compliance-pipeline.yml +++ b/.github/workflows/sdl-compliance-pipeline.yml @@ -45,6 +45,14 @@ jobs: # a pull request then we can checkout the head. fetch-depth: 2 + - name: Install Boost Library with vcpkg + run: | + git clone https://github.com/microsoft/vcpkg.git + .\vcpkg\bootstrap-vcpkg.bat + .\vcpkg\vcpkg install boost-json:x64-windows + env: + VCPKG_DEFAULT_TRIPLET: "x64-windows" + # https://github.com/marketplace/actions/setup-msbuild#specifying-msbuild-architecture-optional - name: Add msbuild to PATH uses: microsoft/setup-msbuild@v2 @@ -84,4 +92,4 @@ jobs: with: # The path of the directory in which to save the SARIF results (../results/cpp.sarif) output: ${{ env.CodeQLResultsDir }} - upload: "always" # Options: 'always', 'failure-only', 'never' + upload: "always" # Options: 'always', 'failure-only', 'never' \ No newline at end of file diff --git a/LogMonitor/LogMonitorTests/ConfigFileParserTests.cpp b/LogMonitor/LogMonitorTests/ConfigFileParserTests.cpp deleted file mode 100644 index f0c0b054..00000000 --- a/LogMonitor/LogMonitorTests/ConfigFileParserTests.cpp +++ /dev/null @@ -1,1759 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// - -#include "pch.h" - -using namespace Microsoft::VisualStudio::CppUnitTestFramework; - -#define BUFFER_SIZE 65536 - -namespace LogMonitorTests -{ - /// - /// Tests of the ConfigFileParser's ReadConfigFile function. This function - /// uses a JsonFileParser object to parse a JSON string, and return a - /// vector of sources with the retrieved configuration. - /// - TEST_CLASS(ConfigFileParserTests) - { - WCHAR bigOutBuf[BUFFER_SIZE]; - - /// - /// Gets the content of the Stdout buffer and returns it in a wstring. - /// - /// \return A wstring with the stdout. - /// - std::wstring RecoverOuput() - { - return std::wstring(bigOutBuf); - } - - /// - /// Removes the braces at the start and end of a string. - /// - /// \param Str A wstring. - /// - /// \return A wstring. - /// - std::wstring RemoveBracesGuidStr(const std::wstring& str) - { - if (str.size() >= 2 && str[0] == L'{' && str[str.length() - 1] == L'}') - { - return str.substr(1, str.length() - 2); - } - return str; - } - - /// - /// Add the path of the created directories, to be removed during - /// cleanup. - /// - std::vector directoriesToDeleteAtCleanup; - - public: - - /// - /// "Redirects" the stdout to our buffer. - /// - TEST_METHOD_INITIALIZE(InitializeLogFileMonitorTests) - { - // - // Set our own buffer in stdout. - // - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - fflush(stdout); - _setmode(_fileno(stdout), _O_U16TEXT); - setvbuf(stdout, (char*)bigOutBuf, _IOFBF, sizeof(bigOutBuf) - sizeof(WCHAR)); - } - - /// - /// Test that the most simple, but valid configuration string is - /// read successfully. - /// - TEST_METHOD(TestBasicConfigFile) - { - std::wstring configFileStr = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - ]\ - }\ - }"; - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - - std::wstring output = RecoverOuput(); - - Assert::IsTrue(success); - Assert::AreEqual(L"", output.c_str()); - } - - /// - /// Tests that EventLog sources, with all their attributes, are read - /// successfully. - /// - TEST_METHOD(TestSourceEventLog) - { - bool startAtOldestRecord = true; - bool eventFormatMultiLine = true; - - // - // Template of a valid configuration string, with an EventLog source. - // - std::wstring configFileStrFormat = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"EventLog\",\ - \"startAtOldestRecord\" : %s,\ - \"eventFormatMultiLine\" : %s,\ - \"channels\" : [\ - {\ - \"name\": \"%s\",\ - \"level\" : \"%s\"\ - },\ - {\ - \"name\": \"%s\",\ - \"level\" : \"%s\"\ - }\ - ]\ - }\ - ]\ - }\ - }"; - - // - // Try reading this values - // - std::wstring firstChannelName = L"system"; - EventChannelLogLevel firstChannelLevel = EventChannelLogLevel::Information; - - std::wstring secondChannelName = L"application"; - EventChannelLogLevel secondChannelLevel = EventChannelLogLevel::Critical; - { - std::wstring configFileStr = Utility::FormatString( - configFileStrFormat.c_str(), - startAtOldestRecord ? L"true" : L"false", - eventFormatMultiLine ? L"true" : L"false", - firstChannelName.c_str(), - LogLevelNames[(int)firstChannelLevel - 1].c_str(), - secondChannelName.c_str(), - LogLevelNames[(int)secondChannelLevel - 1].c_str() - ); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - - std::wstring output = RecoverOuput(); - - // - // The config string was valid - // - Assert::IsTrue(success); - Assert::AreEqual(L"", output.c_str()); - - // - // The source Event Log is valid - // - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::EventLog, (int)settings.Sources[0]->Type); - - std::shared_ptr sourceEventLog = std::reinterpret_pointer_cast(settings.Sources[0]); - - Assert::AreEqual(startAtOldestRecord, sourceEventLog->StartAtOldestRecord); - Assert::AreEqual(eventFormatMultiLine, sourceEventLog->EventFormatMultiLine); - - Assert::AreEqual((size_t)2, sourceEventLog->Channels.size()); - - Assert::AreEqual(firstChannelName.c_str(), sourceEventLog->Channels[0].Name.c_str()); - Assert::AreEqual((int)firstChannelLevel, (int)sourceEventLog->Channels[0].Level); - - Assert::AreEqual(secondChannelName.c_str(), sourceEventLog->Channels[1].Name.c_str()); - Assert::AreEqual((int)secondChannelLevel, (int)sourceEventLog->Channels[1].Level); - } - - // - // Try with different values - // - startAtOldestRecord = false; - eventFormatMultiLine = false; - - firstChannelName = L"security"; - firstChannelLevel = EventChannelLogLevel::Error; - - secondChannelName = L"kernel"; - secondChannelLevel = EventChannelLogLevel::Warning; - - { - std::wstring configFileStr = Utility::FormatString( - configFileStrFormat.c_str(), - startAtOldestRecord ? L"true" : L"false", - eventFormatMultiLine ? L"true" : L"false", - firstChannelName.c_str(), - LogLevelNames[(int)firstChannelLevel - 1].c_str(), - secondChannelName.c_str(), - LogLevelNames[(int)secondChannelLevel - 1].c_str() - ); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - - std::wstring output = RecoverOuput(); - - // - // The config string was valid - // - Assert::IsTrue(success); - Assert::AreEqual(L"", output.c_str()); - - // - // The source Event Log is valid - // - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::EventLog, (int)settings.Sources[0]->Type); - - std::shared_ptr sourceEventLog = std::reinterpret_pointer_cast(settings.Sources[0]); - - Assert::AreEqual(startAtOldestRecord, sourceEventLog->StartAtOldestRecord); - Assert::AreEqual(eventFormatMultiLine, sourceEventLog->EventFormatMultiLine); - - Assert::AreEqual((size_t)2, sourceEventLog->Channels.size()); - - Assert::AreEqual(firstChannelName.c_str(), sourceEventLog->Channels[0].Name.c_str()); - Assert::AreEqual((int)firstChannelLevel, (int)sourceEventLog->Channels[0].Level); - - Assert::AreEqual(secondChannelName.c_str(), sourceEventLog->Channels[1].Name.c_str()); - Assert::AreEqual((int)secondChannelLevel, (int)sourceEventLog->Channels[1].Level); - } - } - - /// - /// Test that default values for optional attributes on an EventLog source - /// are correct. - /// - TEST_METHOD(TestSourceEventLogDefaultValues) - { - std::wstring configFileStr = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"EventLog\",\ - \"channels\" : [\ - {\ - \"name\": \"system\"\ - }\ - ]\ - }\ - ]\ - }\ - }"; - - { - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - - std::wstring output = RecoverOuput(); - - // - // The config string was valid - // - Assert::IsTrue(success); - Assert::AreEqual(L"", output.c_str()); - - // - // The source Event Log is valid - // - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::EventLog, (int)settings.Sources[0]->Type); - - std::shared_ptr sourceEventLog = std::reinterpret_pointer_cast(settings.Sources[0]); - - Assert::AreEqual(false, sourceEventLog->StartAtOldestRecord); - Assert::AreEqual(true, sourceEventLog->EventFormatMultiLine); - - Assert::AreEqual((size_t)1, sourceEventLog->Channels.size()); - - Assert::AreEqual((int)EventChannelLogLevel::Error, (int)sourceEventLog->Channels[0].Level); - } - } - - /// - /// Tests that file sources, with all their attributes, are read - /// successfully. - /// - TEST_METHOD(TestSourceFile) - { - // - // Template of a valid configuration string, with a file source. - // - std::wstring configFileStrFormat = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"File\",\ - \"directory\": \"%s\",\ - \"filter\": \"%s\",\ - \"includeSubdirectories\": %s\ - }\ - ]\ - }\ - }"; - - // - // First, try with this values. - // - bool includeSubdirectories = true; - - std::wstring directory = L"C:\\LogMonitor\\logs"; - std::wstring filter = L"*.*"; - { - std::wstring configFileStr = Utility::FormatString( - configFileStrFormat.c_str(), - Utility::ReplaceAll(directory, L"\\", L"\\\\").c_str(), - filter.c_str(), - includeSubdirectories ? L"true" : L"false" - ); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - - std::wstring output = RecoverOuput(); - - // - // The config string was valid - // - Assert::IsTrue(success); - Assert::AreEqual(L"", output.c_str()); - - // - // The source Event Log is valid - // - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::File, (int)settings.Sources[0]->Type); - - std::shared_ptr sourceFile = std::reinterpret_pointer_cast(settings.Sources[0]); - - Assert::AreEqual(directory.c_str(), sourceFile->Directory.c_str()); - Assert::AreEqual(filter.c_str(), sourceFile->Filter.c_str()); - Assert::AreEqual(includeSubdirectories, sourceFile->IncludeSubdirectories); - } - - // - // Try with different values - // - includeSubdirectories = false; - - directory = L"c:\\\\inetpub\\\\logs"; - filter = L"*.log"; - - { - std::wstring configFileStr = Utility::FormatString( - configFileStrFormat.c_str(), - Utility::ReplaceAll(directory, L"\\", L"\\\\").c_str(), - filter.c_str(), - includeSubdirectories ? L"true" : L"false" - ); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - - std::wstring output = RecoverOuput(); - - // - // The config string was valid - // - Assert::IsTrue(success); - Assert::AreEqual(L"", output.c_str()); - - // - // The source Event Log is valid - // - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::File, (int)settings.Sources[0]->Type); - - std::shared_ptr sourceFile = std::reinterpret_pointer_cast(settings.Sources[0]); - - Assert::AreEqual(directory.c_str(), sourceFile->Directory.c_str()); - Assert::AreEqual(filter.c_str(), sourceFile->Filter.c_str()); - Assert::AreEqual(includeSubdirectories, sourceFile->IncludeSubdirectories); - } - } - - /// - /// Test that default values for optional attributes on a file source are correct. - /// - TEST_METHOD(TestSourceFileDefaultValues) - { - - std::wstring directory = L"C:\\LogMonitor\\logs"; - - std::wstring configFileStrFormat = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"File\",\ - \"directory\": \"%s\"\ - }\ - ]\ - }\ - }"; - - std::wstring configFileStr = Utility::FormatString( - configFileStrFormat.c_str(), - Utility::ReplaceAll(directory, L"\\", L"\\\\").c_str() - ); - - { - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - - std::wstring output = RecoverOuput(); - - // - // The config string was valid - // - Assert::IsTrue(success); - Assert::AreEqual(L"", output.c_str()); - - // - // The source Event Log is valid - // - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::File, (int)settings.Sources[0]->Type); - - std::shared_ptr sourceFile = std::reinterpret_pointer_cast(settings.Sources[0]); - - Assert::AreEqual(directory.c_str(), sourceFile->Directory.c_str()); - Assert::AreEqual(L"", sourceFile->Filter.c_str()); - Assert::AreEqual(false, sourceFile->IncludeSubdirectories); - Assert::AreEqual(300.0, sourceFile->WaitInSeconds); - } - } - - /// - /// Tests that etw sources, with all their attributes, are read - /// successfully. - /// - TEST_METHOD(TestSourceETW) - { - HRESULT hr; - - // - // Used to convert an etw log level value to its string representation. - // - const static std::vector c_LevelToString = - { - L"Unknown", - L"Critical", - L"Error", - L"Warning", - L"Information", - L"Verbose", - }; - - // - // Template of a valid configuration string, with a file source. - // - std::wstring configFileStrFormat = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"ETW\",\ - \"eventFormatMultiLine\" : %s,\ - \"providers\" : [\ - {\ - \"providerName\": \"%s\",\ - \"providerGuid\": \"%s\",\ - \"level\" : \"%s\",\ - \"keywords\" : \"%llu\"\ - },\ - {\ - \"providerName\": \"%s\",\ - \"providerGuid\": \"%s\",\ - \"level\" : \"%s\",\ - \"keywords\" : \"%llu\"\ - }\ - ]\ - }\ - ]\ - }\ - }"; - - // - // First, try this values. - // - bool eventFormatMultiLine = true; - - std::wstring firstProviderName = L"IIS: WWW Server"; - std::wstring firstProviderGuid = L"3A2A4E84-4C21-4981-AE10-3FDA0D9B0F83"; - UCHAR firstProviderLevel = 2; // Error - ULONGLONG firstProviderKeywords = 255; - - std::wstring secondProviderName = L"Microsoft-Windows-IIS-Logging"; - std::wstring secondProviderGuid = L"{7E8AD27F-B271-4EA2-A783-A47BDE29143B}"; - UCHAR secondProviderLevel = 1; // Critical - ULONGLONG secondProviderKeywords = 555; - - { - std::wstring configFileStr = Utility::FormatString( - configFileStrFormat.c_str(), - eventFormatMultiLine ? L"true" : L"false", - firstProviderName.c_str(), - firstProviderGuid.c_str(), - c_LevelToString[(int)firstProviderLevel].c_str(), - firstProviderKeywords, - secondProviderName.c_str(), - secondProviderGuid.c_str(), - c_LevelToString[(int)secondProviderLevel].c_str(), - secondProviderKeywords - ); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - - std::wstring output = RecoverOuput(); - - // - // The config string was valid - // - Assert::IsTrue(success); - Assert::AreEqual(L"", output.c_str()); - - // - // The source Event Log is valid - // - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::ETW, (int)settings.Sources[0]->Type); - - std::shared_ptr sourceEtw = std::reinterpret_pointer_cast(settings.Sources[0]); - - Assert::AreEqual(eventFormatMultiLine, sourceEtw->EventFormatMultiLine); - - Assert::AreEqual((size_t)2, sourceEtw->Providers.size()); - - // - // First provider - // - Assert::AreEqual(firstProviderName.c_str(), sourceEtw->Providers[0].ProviderName.c_str()); - Assert::AreEqual(firstProviderLevel, sourceEtw->Providers[0].Level); - Assert::AreEqual(firstProviderKeywords, sourceEtw->Providers[0].Keywords); - - // - // Check that guids are equal - // - LPWSTR providerGuid1 = NULL; - hr = StringFromCLSID(sourceEtw->Providers[0].ProviderGuid, &providerGuid1); - Assert::IsFalse(FAILED(hr)); - Assert::AreEqual(RemoveBracesGuidStr(firstProviderGuid).c_str(), RemoveBracesGuidStr(std::wstring(providerGuid1)).c_str()); - CoTaskMemFree(providerGuid1); - - // - // Second provider - // - Assert::AreEqual(secondProviderName.c_str(), sourceEtw->Providers[1].ProviderName.c_str()); - Assert::AreEqual(secondProviderLevel, sourceEtw->Providers[1].Level); - Assert::AreEqual(secondProviderKeywords, sourceEtw->Providers[1].Keywords); - - // - // Check that guids are equal - // - LPWSTR providerGuid2 = NULL; - hr = StringFromCLSID(sourceEtw->Providers[1].ProviderGuid, &providerGuid2); - Assert::IsFalse(FAILED(hr)); - Assert::AreEqual(RemoveBracesGuidStr(secondProviderGuid).c_str(), RemoveBracesGuidStr(std::wstring(providerGuid2)).c_str()); - CoTaskMemFree(providerGuid2); - } - - // - // Try different values - // - eventFormatMultiLine = false; - - firstProviderName = L"Microsoft-Windows-SMBClient"; - firstProviderGuid = L"{988C59C5-0A1C-45B6-A555-0C62276E327D}"; - firstProviderLevel = 3; // Warning - firstProviderKeywords = 0xff; - - secondProviderName = L"Microsoft-Windows-SMBWitnessClient"; - secondProviderGuid = L"32254F6C-AA33-46F0-A5E3-1CBCC74BF683"; - secondProviderLevel = 4; // Information - secondProviderKeywords = 0xfe; - - { - std::wstring configFileStr = Utility::FormatString( - configFileStrFormat.c_str(), - eventFormatMultiLine ? L"true" : L"false", - firstProviderName.c_str(), - firstProviderGuid.c_str(), - c_LevelToString[(int)firstProviderLevel].c_str(), - firstProviderKeywords, - secondProviderName.c_str(), - secondProviderGuid.c_str(), - c_LevelToString[(int)secondProviderLevel].c_str(), - secondProviderKeywords - ); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - - std::wstring output = RecoverOuput(); - - // - // The config string was valid - // - Assert::IsTrue(success); - Assert::AreEqual(L"", output.c_str()); - - // - // The source Event Log is valid - // - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::ETW, (int)settings.Sources[0]->Type); - - std::shared_ptr sourceEtw = std::reinterpret_pointer_cast(settings.Sources[0]); - - Assert::AreEqual(eventFormatMultiLine, sourceEtw->EventFormatMultiLine); - - Assert::AreEqual((size_t)2, sourceEtw->Providers.size()); - - // - // First provider - // - Assert::AreEqual(firstProviderName.c_str(), sourceEtw->Providers[0].ProviderName.c_str()); - Assert::AreEqual(firstProviderLevel, sourceEtw->Providers[0].Level); - Assert::AreEqual(firstProviderKeywords, sourceEtw->Providers[0].Keywords); - - // - // Check that guids are equal - // - LPWSTR providerGuid1 = NULL; - hr = StringFromCLSID(sourceEtw->Providers[0].ProviderGuid, &providerGuid1); - Assert::IsFalse(FAILED(hr)); - Assert::AreEqual(RemoveBracesGuidStr(firstProviderGuid).c_str(), RemoveBracesGuidStr(std::wstring(providerGuid1)).c_str()); - CoTaskMemFree(providerGuid1); - - // - // Second provider - // - Assert::AreEqual(secondProviderName.c_str(), sourceEtw->Providers[1].ProviderName.c_str()); - Assert::AreEqual(secondProviderLevel, sourceEtw->Providers[1].Level); - Assert::AreEqual(secondProviderKeywords, sourceEtw->Providers[1].Keywords); - - // - // Check that guids are equal - // - LPWSTR providerGuid2 = NULL; - hr = StringFromCLSID(sourceEtw->Providers[1].ProviderGuid, &providerGuid2); - Assert::IsFalse(FAILED(hr)); - Assert::AreEqual(RemoveBracesGuidStr(secondProviderGuid).c_str(), RemoveBracesGuidStr(std::wstring(providerGuid2)).c_str()); - CoTaskMemFree(providerGuid2); - } - } - - /// - /// Test that default values for optional attributes on an etw source - /// are correct. - /// - TEST_METHOD(TestSourceETWDefaultValues) - { - HRESULT hr; - const static std::vector c_LevelToString = - { - L"Unknown", - L"Critical", - L"Error", - L"Warning", - L"Information", - L"Verbose", - }; - - - std::wstring firstProviderGuid = L"3A2A4E84-4C21-4981-AE10-3FDA0D9B0F83"; - - std::wstring configFileStrFormat = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"ETW\",\ - \"providers\" : [\ - {\ - \"providerGuid\": \"%s\"\ - }\ - ]\ - }\ - ]\ - }\ - }"; - - { - std::wstring configFileStr = Utility::FormatString( - configFileStrFormat.c_str(), - firstProviderGuid.c_str() - ); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - - std::wstring output = RecoverOuput(); - - // - // The config string was valid - // - Assert::IsTrue(success); - Assert::AreEqual(L"", output.c_str()); - - // - // The source Event Log is valid - // - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::ETW, (int)settings.Sources[0]->Type); - - std::shared_ptr sourceEtw = std::reinterpret_pointer_cast(settings.Sources[0]); - - Assert::AreEqual(true, sourceEtw->EventFormatMultiLine); - - Assert::AreEqual((size_t)1, sourceEtw->Providers.size()); - - // - // First provider - // - Assert::AreEqual(L"", sourceEtw->Providers[0].ProviderName.c_str()); - Assert::AreEqual((UCHAR)2, sourceEtw->Providers[0].Level); // Error - Assert::AreEqual((ULONGLONG)0L, sourceEtw->Providers[0].Keywords); - - // - // Check that guids are equal - // - LPWSTR providerGuid1 = NULL; - hr = StringFromCLSID(sourceEtw->Providers[0].ProviderGuid, &providerGuid1); - Assert::IsFalse(FAILED(hr)); - Assert::AreEqual(RemoveBracesGuidStr(firstProviderGuid).c_str(), RemoveBracesGuidStr(std::wstring(providerGuid1)).c_str()); - CoTaskMemFree(providerGuid1); - } - - std::wstring firstProviderName = L"Microsoft-Windows-User-Diagnostic"; - - configFileStrFormat = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"ETW\",\ - \"providers\" : [\ - {\ - \"providerName\": \"%s\"\ - }\ - ]\ - }\ - ]\ - }\ - }"; - - { - std::wstring configFileStr = Utility::FormatString( - configFileStrFormat.c_str(), - firstProviderName.c_str() - ); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - - std::wstring output = RecoverOuput(); - - // - // The config string was valid - // - Assert::IsTrue(success); - Assert::AreEqual(L"", output.c_str()); - - // - // The source Event Log is valid - // - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::ETW, (int)settings.Sources[0]->Type); - - std::shared_ptr sourceEtw = std::reinterpret_pointer_cast(settings.Sources[0]); - - Assert::AreEqual(true, sourceEtw->EventFormatMultiLine); - - Assert::AreEqual((size_t)1, sourceEtw->Providers.size()); - - // - // First provider - // - Assert::AreEqual(firstProviderName.c_str(), sourceEtw->Providers[0].ProviderName.c_str()); - Assert::AreEqual((UCHAR)2, sourceEtw->Providers[0].Level); // Error - Assert::AreEqual((ULONGLONG)0L, sourceEtw->Providers[0].Keywords); - } - } - - /// - /// Test that ReadConfigFile reads attribute names in a case insensitive way. - /// - TEST_METHOD(TestCaseInsensitiveOnAttributeNames) - { - std::wstring configFileStr = - L"{ \ - \"logconfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"EventLog\",\ - \"channels\" : [\ - {\ - \"name\": \"system\",\ - \"level\" : \"Verbose\"\ - }\ - ]\ - }\ - ]\ - }\ - }"; - - { - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - - std::wstring output = RecoverOuput(); - - // - // The config string was valid - // - Assert::IsTrue(success); - Assert::AreEqual(L"", output.c_str()); - - // - // The source Event Log is valid - // - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::EventLog, (int)settings.Sources[0]->Type); - - std::shared_ptr sourceEventLog = std::reinterpret_pointer_cast(settings.Sources[0]); - - - Assert::AreEqual((size_t)1, sourceEventLog->Channels.size()); - - Assert::AreEqual(L"system", sourceEventLog->Channels[0].Name.c_str()); - Assert::AreEqual((int)EventChannelLogLevel::Verbose, (int)sourceEventLog->Channels[0].Level); - } - - configFileStr = - L"{ \ - \"LOGCONFIG\": { \ - \"SourCes\": [ \ - {\ - \"Type\": \"EventLog\",\ - \"CHANNELS\" : [\ - {\ - \"Name\": \"system\",\ - \"Level\" : \"Verbose\"\ - }\ - ]\ - }\ - ]\ - }\ - }"; - - { - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - - std::wstring output = RecoverOuput(); - - // - // The config string was valid - // - Assert::IsTrue(success); - Assert::AreEqual(L"", output.c_str()); - - // - // The source Event Log is valid - // - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::EventLog, (int)settings.Sources[0]->Type); - - std::shared_ptr sourceEventLog = std::reinterpret_pointer_cast(settings.Sources[0]); - - - Assert::AreEqual((size_t)1, sourceEventLog->Channels.size()); - - Assert::AreEqual(L"system", sourceEventLog->Channels[0].Name.c_str()); - Assert::AreEqual((int)EventChannelLogLevel::Verbose, (int)sourceEventLog->Channels[0].Level); - } - } - - /// - /// Test that bad formatted JSON strings throw errors. - /// - TEST_METHOD(TestInvalidJson) - { - std::wstring configFileStr; - - // - // Empty string. - // - configFileStr = - L""; - - { - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - std::function f1 = [&jsonParser, &settings] { bool success = ReadConfigFile(jsonParser, settings); }; - Assert::ExpectException(f1); - } - - // - // Invalid attribute name. - // - configFileStr = - L"{other: false}"; - - { - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - std::function f1 = [&jsonParser, &settings] { bool success = ReadConfigFile(jsonParser, settings); }; - Assert::ExpectException(f1); - } - - // - // Invalid boolean value. - // - configFileStr = - L"{\"boolean\": Negative}"; - - { - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - std::function f1 = [&jsonParser, &settings] { bool success = ReadConfigFile(jsonParser, settings); }; - Assert::ExpectException(f1); - } - - // - // Invalid numeric value. - // - configFileStr = - L"{\"numeric\": 0xff}"; - - { - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - std::function f1 = [&jsonParser, &settings] { bool success = ReadConfigFile(jsonParser, settings); }; - Assert::ExpectException(f1); - } - - // - // Invalid escape sequence. - // - configFileStr = - L"{\"text\": \"\\k\"}"; - - { - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - std::function f1 = [&jsonParser, &settings] { bool success = ReadConfigFile(jsonParser, settings); }; - Assert::ExpectException(f1); - } - - // - // Expected next element on an object. - // - configFileStr = - L"{\"text\": \"\",}"; - - { - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - std::function f1 = [&jsonParser, &settings] { bool success = ReadConfigFile(jsonParser, settings); }; - Assert::ExpectException(f1); - } - - // - // Expected next element on an array. - // - configFileStr = - L"{\"array\":[\"text\": \"\",]}"; - - { - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - std::function f1 = [&jsonParser, &settings] { bool success = ReadConfigFile(jsonParser, settings); }; - Assert::ExpectException(f1); - } - } - - /// - /// Test that valid JSON strings, but invalid values for a configuration string, - /// return false when passed to ReadConfigFile. - /// - TEST_METHOD(TestInvalidConfigFile) - { - std::wstring configFileStr; - - // - // 'LogConfig' root element doesn't exist. - // - configFileStr = - L"{\"other\": { }}"; - - { - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - Assert::IsFalse(success); - } - - // - // LogConfig is an array. - // - configFileStr = - L"{ \ - \"LogConfig\": []\ - }"; - - { - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - Assert::IsFalse(success); - } - - // - // 'Sources' doesn't exist. - // - configFileStr = - L"{ \ - \"LogConfig\": { \ - \"other\": [ \ - {\ - \"type\": \"File\",\ - \"directory\": \"C:\\\\logs\"\ - }\ - ]\ - }\ - }"; - { - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - Assert::IsFalse(success); - } - - // - // 'sources' isn't an array. - // - configFileStr = - L"{ \ - \"LogConfig\": { \ - \"sources\": \ - {\ - \"type\": \"File\",\ - \"directory\": \"C:\\\\logs\"\ - }\ - }\ - }"; - - { - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - Assert::IsFalse(success); - } - - // - // Check a source with invalid type. - // This case is special, because it shouldn't return false, but a - // warning message should be throw. - // - configFileStr = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"Unknown\",\ - \"directory\": \"C:\\\\logs\"\ - }\ - ]\ - }\ - }"; - - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - Assert::IsTrue(success); - - std::wstring output = RecoverOuput(); - - Assert::IsTrue(output.find(L"ERROR") != std::wstring::npos || output.find(L"WARNING") != std::wstring::npos); - } - } - - /// - /// Check that invalid EventLog sources are not returned by ReadConfigFile. - /// - TEST_METHOD(TestInvalidEventLogSource) - { - std::wstring configFileStr; - - // - // 'Channels' doesn't exist. - // - configFileStr = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"EventLog\",\ - \"other\" : [\ - {\ - \"name\": \"system\"\ - }\ - ]\ - }\ - ]\ - }\ - }"; - - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - Assert::IsTrue(success); - - std::wstring output = RecoverOuput(); - - Assert::AreEqual((size_t)0, settings.Sources.size()); - Assert::IsTrue(output.find(L"ERROR") != std::wstring::npos || output.find(L"WARNING") != std::wstring::npos); - } - - // - // 'Channels' isn't an array. - // - configFileStr = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"EventLog\",\ - \"channels\" : \ - {\ - \"name\": \"system\"\ - }\ - }\ - ]\ - }\ - }"; - - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - Assert::IsTrue(success); - - std::wstring output = RecoverOuput(); - - Assert::AreEqual((size_t)0, settings.Sources.size()); - Assert::IsTrue(output.find(L"ERROR") != std::wstring::npos || output.find(L"WARNING") != std::wstring::npos); - } - - // - // Invalid channel. It should have at least a 'name' attribute. - // - configFileStr = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"EventLog\",\ - \"channels\" : [\ - {\ - \"other\": \"system\"\ - }\ - ]\ - }\ - ]\ - }\ - }"; - - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - Assert::IsTrue(success); - - std::wstring output = RecoverOuput(); - - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::EventLog, (int)settings.Sources[0]->Type); - - std::shared_ptr sourceEventLog = std::reinterpret_pointer_cast(settings.Sources[0]); - - Assert::AreEqual((size_t)0, sourceEventLog->Channels.size()); - Assert::IsTrue(output.find(L"WARNING") != std::wstring::npos); - } - - // - // Invalid level. - // This case is special, because the source should be returned, but a - // warning message should be throw. - // - configFileStr = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"EventLog\",\ - \"channels\" : [\ - {\ - \"name\": \"system\",\ - \"level\": \"Invalid\"\ - }\ - ]\ - }\ - ]\ - }\ - }"; - - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - Assert::IsTrue(success); - - std::wstring output = RecoverOuput(); - - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::EventLog, (int)settings.Sources[0]->Type); - - std::shared_ptr sourceEventLog = std::reinterpret_pointer_cast(settings.Sources[0]); - - Assert::AreEqual((size_t)1, sourceEventLog->Channels.size()); - Assert::AreEqual((int)EventChannelLogLevel::Error, (int)sourceEventLog->Channels[0].Level); - Assert::IsTrue(output.find(L"WARNING") != std::wstring::npos); - } - } - - /// - /// Check that invalid File sources are not returned by ReadConfigFile. - /// - TEST_METHOD(TestInvalidFileSource) - { - std::wstring configFileStr; - - // - // 'Directory' doesn't exist - // - configFileStr = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"File\",\ - \"other\": \"C:\\\\logs\"\ - }\ - ]\ - }\ - }"; - - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - Assert::IsTrue(success); - - std::wstring output = RecoverOuput(); - - Assert::AreEqual((size_t)0, settings.Sources.size()); - Assert::IsTrue(output.find(L"ERROR") != std::wstring::npos || output.find(L"WARNING") != std::wstring::npos); - } - } - - TEST_METHOD(TestRootDirectoryConfigurations) - { - - const std::wstring directory = L"C:\\"; - bool includeSubdirectories = false; - - std::wstring configFileStr; - std::wstring configFileStrFormat = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"File\",\ - \"directory\": \"%s\",\ - \"includeSubdirectories\": %s\ - }\ - ]\ - }\ - }"; - - // Valid: Root dir and includeSubdirectories = false - { - configFileStr = Utility::FormatString( - configFileStrFormat.c_str(), - Utility::ReplaceAll(directory, L"\\", L"\\\\").c_str(), - includeSubdirectories ? L"true" : L"false"); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - Assert::IsTrue(success); - - std::wstring output = RecoverOuput(); - Assert::AreEqual(L"", output.c_str()); - } - - // Invalid: Root dir and includeSubdirectories = true - { - includeSubdirectories = true; - configFileStr = Utility::FormatString( - configFileStrFormat.c_str(), - Utility::ReplaceAll(directory, L"\\", L"\\\\").c_str(), - includeSubdirectories ? L"true" : L"false"); - - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - Assert::IsTrue(success); - - std::wstring output = RecoverOuput(); - Assert::IsTrue(output.find(L"WARNING") != std::wstring::npos); - } - } - - /// - /// Check that invalid ETW sources are not returned by ReadConfigFile. - /// - TEST_METHOD(TestInvalidETWSource) - { - std::wstring configFileStr; - - // - // 'providers' doesn't exist - // - configFileStr = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"ETW\",\ - \"other\" : [\ - {\ - \"providerGuid\": \"305FC87B-002A-5E26-D297-60223012CA9C\"\ - }\ - ]\ - }\ - ]\ - }\ - }"; - - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - Assert::IsTrue(success); - - std::wstring output = RecoverOuput(); - - Assert::AreEqual((size_t)0, settings.Sources.size()); - Assert::IsTrue(output.find(L"ERROR") != std::wstring::npos || output.find(L"WARNING") != std::wstring::npos); - } - - // - // Invalid provider - // - configFileStr = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"ETW\",\ - \"providers\" : [\ - {\ - \"level\": \"Information\"\ - }\ - ]\ - }\ - ]\ - }\ - }"; - - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - Assert::IsTrue(success); - - std::wstring output = RecoverOuput(); - - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::ETW, (int)settings.Sources[0]->Type); - - std::shared_ptr SourceEtw = std::reinterpret_pointer_cast(settings.Sources[0]); - - Assert::AreEqual((size_t)0, SourceEtw->Providers.size()); - Assert::IsTrue(output.find(L"WARNING") != std::wstring::npos); - } - - // - // Invalid providerGuid. - // - configFileStr = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"ETW\",\ - \"providers\" : [\ - {\ - \"providerGuid\": \"305FC87B-002A-5E26-D297-60\"\ - }\ - ]\ - }\ - ]\ - }\ - }"; - - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - Assert::IsTrue(success); - - std::wstring output = RecoverOuput(); - - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::ETW, (int)settings.Sources[0]->Type); - - std::shared_ptr SourceEtw = std::reinterpret_pointer_cast(settings.Sources[0]); - - Assert::AreEqual((size_t)0, SourceEtw->Providers.size()); - Assert::IsTrue(output.find(L"WARNING") != std::wstring::npos); - } - - // - // Invalid level. - // This case is special, because the source should be returned, but a - // warning message should be throw. - // - configFileStr = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"ETW\",\ - \"providers\" : [\ - {\ - \"providerGuid\": \"305FC87B-002A-5E26-D297-60223012CA9C\",\ - \"level\": \"Info\"\ - }\ - ]\ - }\ - ]\ - }\ - }"; - - { - fflush(stdout); - ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - Assert::IsTrue(success); - - std::wstring output = RecoverOuput(); - - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::ETW, (int)settings.Sources[0]->Type); - - std::shared_ptr SourceEtw = std::reinterpret_pointer_cast(settings.Sources[0]); - - Assert::AreEqual((size_t)1, SourceEtw->Providers.size()); - Assert::AreEqual((UCHAR)2, SourceEtw->Providers[0].Level); // Error - Assert::IsTrue(output.find(L"WARNING") != std::wstring::npos); - } - } - - /// - /// Check that UTF8 encoded config file is opened and read by OpenConfigFile. - /// - TEST_METHOD(TestUTF8EncodedConfigFileReading) - { - //create a temp folder to hold config - std::wstring tempDirectory = CreateTempDirectory(); - Assert::IsFalse(tempDirectory.empty()); - directoriesToDeleteAtCleanup.push_back(tempDirectory); - // - // Create subdirectory - // - std::wstring subDirectory = tempDirectory + L"\\LogMonitor"; - long status = CreateDirectoryW(subDirectory.c_str(), NULL); - Assert::AreNotEqual(0L, status); - - std::wstring fileName = L"LogMonitorConfigTesting.json"; - std::wstring fullFileName = subDirectory + L"\\" + fileName; - - //create the utf8 encoded config file - std::wstring configFileStr = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - ]\ - }\ - }"; - - std::wofstream wof; - wof.imbue(std::locale(std::locale::empty(), new std::codecvt_utf8)); - wof.open(fullFileName); - wof << configFileStr; - wof.close(); - - //check if the file can be successfully read by OpenConfigFile - LoggerSettings settings; - bool succcess = OpenConfigFile((PWCHAR)fullFileName.c_str(), settings); - Assert::AreEqual(succcess, true); - } - - TEST_METHOD(TestWaitInSeconds){ - // Test WaitInSeconds input as value - TestWaitInSecondsValues(L"242", false); - - // Test WaitInSeconds input as string - TestWaitInSecondsValues(L"359", true); - - // Test WaitInSeconds input is Infinity - TestWaitInSecondsValues(L"INFINITY", true); - } - - TEST_METHOD(TestInvalidWaitInSeconds) { - std::wstring directory = L"C:\\LogMonitor\\logs"; - TestInvalidWaitInSecondsValues(L"-10", false); - TestInvalidWaitInSecondsValues(L"-Inf", true); - } - - private: - void TestWaitInSecondsValues(std::wstring waitInSeconds, bool asString = false) { - std::wstring directory = L"C:\\LogMonitor\\logs"; - std::wstring configFileStr = GetConfigFileStrFormat(directory, waitInSeconds, asString); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - - // - // The config string was valid - // - Assert::IsTrue(success); - - // - // The source Event Log is valid - // - Assert::AreEqual((size_t)1, settings.Sources.size()); - Assert::AreEqual((int)LogSourceType::File, (int)settings.Sources[0]->Type); - - std::shared_ptr sourceFile = std::reinterpret_pointer_cast(settings.Sources[0]); - - if (isinf(std::stod(waitInSeconds))) { - Assert::IsTrue(isinf(sourceFile->WaitInSeconds)); - } - else { - double precision = 1e-6; - Assert::AreEqual(std::stod(waitInSeconds), sourceFile->WaitInSeconds, precision); - } - } - - void TestInvalidWaitInSecondsValues(std::wstring waitInSeconds, bool asString = false) { - std::wstring directory = L"C:\\LogMonitor\\logs"; - std::wstring configFileStr = GetConfigFileStrFormat(directory, waitInSeconds, asString); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - - std::wstring output = RecoverOuput(); - - Assert::IsTrue(success); - Assert::IsTrue(output.find(L"WARNING") != std::wstring::npos); - - Assert::IsTrue(success); - Assert::IsTrue(output.find(L"WARNING") != std::wstring::npos); - } - - std::wstring GetConfigFileStrFormat(std::wstring directory, std::wstring waitInSeconds, bool asString) { - std::wstring configFileStrFormat; - if (asString) { - configFileStrFormat = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"File\",\ - \"directory\": \"%s\",\ - \"waitInSeconds\": \"%s\"\ - }\ - ]\ - }\ - }"; - - return Utility::FormatString( - configFileStrFormat.c_str(), - Utility::ReplaceAll(directory, L"\\", L"\\\\").c_str(), - waitInSeconds.c_str() - ); - } - else { - configFileStrFormat = - L"{ \ - \"LogConfig\": { \ - \"sources\": [ \ - {\ - \"type\": \"File\",\ - \"directory\": \"%s\",\ - \"waitInSeconds\": %f\ - }\ - ]\ - }\ - }"; - return Utility::FormatString( - configFileStrFormat.c_str(), - Utility::ReplaceAll(directory, L"\\", L"\\\\").c_str(), - std::stod(waitInSeconds) - ); - } - } - - TEST_METHOD(TestSourceProcess) - { - // - // Template of a valid configuration string, with a process source. - // - std::wstring configFileStrFormat = - L"{ \ - \"LogConfig\": { \ - \"logFormat\": \"%s\",\ - \"sources\": [ \ - {\ - \"type\": \"Process\",\ - \"customLogFormat\": \"%s\"\ - }\ - ]\ - }\ - }"; - - std::wstring logFormat = L"custom"; - std::wstring customLogFormat = L"{'TimeStamp':'%TimeStamp%', 'source':'%Source%', 'Message':'%Message%'}"; - { - std::wstring configFileStr = Utility::FormatString( - configFileStrFormat.c_str(), - logFormat.c_str(), - customLogFormat.c_str() - ); - - JsonFileParser jsonParser(configFileStr); - LoggerSettings settings; - - bool success = ReadConfigFile(jsonParser, settings); - - std::wstring output = RecoverOuput(); - - // - // The config string was valid - // - Assert::IsTrue(success); - Assert::AreEqual(L"", output.c_str()); - } - } - - }; -} diff --git a/LogMonitor/LogMonitorTests/JsonProcessorTests.cpp b/LogMonitor/LogMonitorTests/JsonProcessorTests.cpp new file mode 100644 index 00000000..5c223e72 --- /dev/null +++ b/LogMonitor/LogMonitorTests/JsonProcessorTests.cpp @@ -0,0 +1,23 @@ +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// + +#include "pch.h" + + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +#define BUFFER_SIZE 65536 + +namespace UtilityTests +{ + /// + /// Tests of Utility class methods. + /// + TEST_CLASS(JsonProcessorTests) + { + public: + + }; +} diff --git a/LogMonitor/LogMonitorTests/LogMonitorTests.cpp b/LogMonitor/LogMonitorTests/LogMonitorTests.cpp index 5aba0f2d..2150ea4b 100644 --- a/LogMonitor/LogMonitorTests/LogMonitorTests.cpp +++ b/LogMonitor/LogMonitorTests/LogMonitorTests.cpp @@ -5,7 +5,6 @@ #include "pch.h" -#include "../src/LogMonitor/ConfigFileParser.cpp" #include "../src/LogMonitor/EtwMonitor.cpp" #include "../src/LogMonitor/EventMonitor.cpp" #include "../src/LogMonitor/JsonFileParser.cpp" diff --git a/LogMonitor/LogMonitorTests/LogMonitorTests.vcxproj b/LogMonitor/LogMonitorTests/LogMonitorTests.vcxproj index 23dffda2..32929bda 100644 --- a/LogMonitor/LogMonitorTests/LogMonitorTests.vcxproj +++ b/LogMonitor/LogMonitorTests/LogMonitorTests.vcxproj @@ -166,6 +166,7 @@ + @@ -174,7 +175,6 @@ Create Create - diff --git a/LogMonitor/LogMonitorTests/LogMonitorTests.vcxproj.filters b/LogMonitor/LogMonitorTests/LogMonitorTests.vcxproj.filters index 26c604af..25533dc9 100644 --- a/LogMonitor/LogMonitorTests/LogMonitorTests.vcxproj.filters +++ b/LogMonitor/LogMonitorTests/LogMonitorTests.vcxproj.filters @@ -21,9 +21,6 @@ Source Files - - Source Files - Source Files @@ -38,6 +35,9 @@ Source Files + + + Source Files diff --git a/LogMonitor/src/LogMonitor/ConfigFileParser.cpp b/LogMonitor/src/LogMonitor/ConfigFileParser.cpp deleted file mode 100644 index c29a835a..00000000 --- a/LogMonitor/src/LogMonitor/ConfigFileParser.cpp +++ /dev/null @@ -1,774 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// - -#include "pch.h" -#include "./Parser/ConfigFileParser.h" -#include "./LogWriter.h" -#include "./FileMonitor/FileMonitorUtilities.h" - -/// ConfigFileParser.cpp -/// -/// Reads the configuration file content (as a string), parsing it with a -/// JsonFileParser object previously created. -/// -/// The main entry point in this file is OpenConfigFile. -/// - -/// -/// Open the config file and convert the document content into json -/// -/// \param FileName Config File name. -/// -/// \return True if the configuration file was valid. Otherwise false -/// -bool OpenConfigFile(_In_ const PWCHAR ConfigFileName, _Out_ LoggerSettings& Config) -{ - bool success; - std::wifstream configFileStream(ConfigFileName); - configFileStream.imbue(std::locale(configFileStream.getloc(), - new std::codecvt_utf8_utf16)); - - if (configFileStream.is_open()) - { - try - { - // - // Convert the document content to a string, to pass it to JsonFileParser constructor. - // - std::wstring configFileStr((std::istreambuf_iterator(configFileStream)), - std::istreambuf_iterator()); - configFileStr.erase(remove(configFileStr.begin(), configFileStr.end(), 0xFEFF), configFileStr.end()); - - JsonFileParser jsonParser(configFileStr); - - success = ReadConfigFile(jsonParser, Config); - } - catch (std::exception& ex) - { - logWriter.TraceError( - Utility::FormatString(L"Failed to read json configuration file. %S", ex.what()).c_str() - ); - success = false; - } - catch (...) - { - logWriter.TraceError( - Utility::FormatString(L"Failed to read json configuration file. Unknown error occurred.").c_str() - ); - success = false; - } - } else { - logWriter.TraceError( - Utility::FormatString( - L"Configuration file '%s' not found. Logs will not be monitored.", - ConfigFileName - ).c_str() - ); - success = false; - } - - return success; -} - -/// -/// Read the root JSON of the config file -/// -/// \param Parser A pre-initialized JSON parser. -/// \param Config Returns a LoggerSettings struct with the values specified -/// in the config file. -/// -/// \return True if the configuration file was valid. Otherwise false -/// -bool -ReadConfigFile( - _In_ JsonFileParser& Parser, - _Out_ LoggerSettings& Config - ) -{ - if (Parser.GetNextDataType() != JsonFileParser::DataType::Object) - { - logWriter.TraceError(L"Failed to parse configuration file. Object expected at the file's root"); - return false; - } - - bool containsLogConfigTag = false; - - if (Parser.BeginParseObject()) - { - do - { - const std::wstring key(Parser.GetKey()); - - if (_wcsnicmp(key.c_str(), JSON_TAG_LOG_CONFIG, _countof(JSON_TAG_LOG_CONFIG)) == 0) - { - containsLogConfigTag = ReadLogConfigObject(Parser, Config); - } - else - { - Parser.SkipValue(); - } - } while (Parser.ParseNextObjectElement()); - } - - return containsLogConfigTag; -} - -/// -/// Read LogConfig tag, that contains the config of the sources -/// -/// \param Parser A pre-initialized JSON parser. -/// \param Config Returns a LoggerSettings struct with the values specified -/// in the config file. -/// -/// \return True if LogConfig tag was valid. Otherwise false -/// -bool -ReadLogConfigObject( - _In_ JsonFileParser& Parser, - _Out_ LoggerSettings& Config - ) -{ - if (Parser.GetNextDataType() != JsonFileParser::DataType::Object) - { - logWriter.TraceError(L"Failed to parse configuration file. 'LogConfig' is expected to be an object"); - Parser.SkipValue(); - return false; - } - - bool sourcesTagFound = false; - if (Parser.BeginParseObject()) - { - std::wstring key; - do - { - key = Parser.GetKey(); - - if (_wcsnicmp(key.c_str(), JSON_TAG_SOURCES, _countof(JSON_TAG_SOURCES)) == 0) - { - if (Parser.GetNextDataType() != JsonFileParser::DataType::Array) - { - logWriter.TraceError(L"Failed to parse configuration file. 'sources' attribute expected to be an array"); - Parser.SkipValue(); - continue; - } - - sourcesTagFound = true; - - if (!Parser.BeginParseArray()) - { - continue; - } - - do - { - AttributesMap sourceAttributes; - - // - // Read all attributes of a source from the config file, - // instantiate it and add it to the end of the vector - // - if (ReadSourceAttributes(Parser, sourceAttributes)) { - if (!AddNewSource(Parser, sourceAttributes, Config.Sources)) - { - logWriter.TraceWarning(L"Failed to parse configuration file. Error reading invalid source."); - } - } - else - { - logWriter.TraceWarning(L"Failed to parse configuration file. Error retrieving source attributes. Invalid source"); - } - - for (auto attributePair : sourceAttributes) - { - if (attributePair.second != nullptr) - { - delete attributePair.second; - } - } - } while (Parser.ParseNextArrayElement()); - } - else if (_wcsnicmp(key.c_str(), JSON_TAG_LOG_FORMAT, _countof(JSON_TAG_LOG_FORMAT)) == 0) - { - Config.LogFormat = std::wstring(Parser.ParseStringValue()); - } - else - { - logWriter.TraceWarning(Utility::FormatString(L"Error parsing configuration file. 'Unknow key %ws in the configuration file.", key.c_str()).c_str()); - Parser.SkipValue(); - } - } while (Parser.ParseNextObjectElement()); - } - - return sourcesTagFound; -} - -/// -/// Look for all the attributes that a single 'source' object contains -/// -/// \param Parser A pre-initialized JSON parser. -/// \param Config Returns an AttributesMap, with all allowed tag names and theirs values -/// -/// \return True if the attributes contained valid values. Otherwise false -/// -bool -ReadSourceAttributes( - _In_ JsonFileParser& Parser, - _Out_ AttributesMap& Attributes - ) -{ - if (Parser.GetNextDataType() != JsonFileParser::DataType::Object) - { - logWriter.TraceError(L"Failed to parse configuration file. Source item expected to be an object"); - Parser.SkipValue(); - return false; - } - - bool success = true; - - if (Parser.BeginParseObject()) - { - do - { - // - // If source reading already fail, just skip attributes^M - // - if (!success) - { - Parser.SkipValue(); - continue; - } - - const std::wstring key(Parser.GetKey()); - - if (_wcsnicmp(key.c_str(), JSON_TAG_TYPE, _countof(JSON_TAG_TYPE)) == 0) - { - const auto& typeString = Parser.ParseStringValue(); - LogSourceType* type = nullptr; - - // - // Check if the string is the name of a valid LogSourceType - // - int sourceTypeArraySize = sizeof(LogSourceTypeNames) / sizeof(LogSourceTypeNames[0]); - for (int i = 0; i < sourceTypeArraySize; i++) - { - if (_wcsnicmp(typeString.c_str(), LogSourceTypeNames[i], typeString.length()) == 0) - { - type = new LogSourceType; - *type = static_cast(i); - } - } - - // - // If the value isn't a valid type, fail. - // - if (type == nullptr) - { - logWriter.TraceError( - Utility::FormatString( - L"Error parsing configuration file. '%s' isn't a valid source type", typeString.c_str() - ).c_str() - ); - - success = false; - } - else - { - Attributes[key] = type; - } - } - else if (_wcsnicmp(key.c_str(), JSON_TAG_CHANNELS, _countof(JSON_TAG_CHANNELS)) == 0) - { - if (Parser.GetNextDataType() != JsonFileParser::DataType::Array) - { - logWriter.TraceError(L"Error parsing configuration file. 'channels' attribute expected to be an array"); - Parser.SkipValue(); - continue; - } - - if (Parser.BeginParseArray()) - { - std::vector* channels = new std::vector(); - - // - // Get only the valid channels of this JSON object. - // - do - { - channels->emplace_back(); - if (!ReadLogChannel(Parser, channels->back())) - { - logWriter.TraceWarning(L"Error parsing configuration file. Discarded invalid channel (it must have a non-empty 'name')."); - channels->pop_back(); - } - } while (Parser.ParseNextArrayElement()); - - Attributes[key] = channels; - } - } - // - // These attributes are string type - // * directory - // * filter - // * lineLogFormat - // - else if (_wcsnicmp(key.c_str(), JSON_TAG_DIRECTORY, _countof(JSON_TAG_DIRECTORY)) == 0) - { - std::wstring directory = Parser.ParseStringValue(); - FileMonitorUtilities::ParseDirectoryValue(directory); - Attributes[key] = new std::wstring(directory); - } - else if (_wcsnicmp(key.c_str(), JSON_TAG_FILTER, _countof(JSON_TAG_FILTER)) == 0 - || _wcsnicmp(key.c_str(), JSON_TAG_CUSTOM_LOG_FORMAT, _countof(JSON_TAG_CUSTOM_LOG_FORMAT)) == 0) - { - Attributes[key] = new std::wstring(Parser.ParseStringValue()); - } - // - // These attributes are boolean type - // * eventFormatMultiLine - // * startAtOldestRecord - // * includeSubdirectories - // - else if ( - _wcsnicmp( - key.c_str(), - JSON_TAG_FORMAT_MULTILINE, - _countof(JSON_TAG_FORMAT_MULTILINE)) == 0 - || _wcsnicmp( - key.c_str(), - JSON_TAG_START_AT_OLDEST_RECORD, - _countof(JSON_TAG_START_AT_OLDEST_RECORD)) == 0 - || _wcsnicmp( - key.c_str(), - JSON_TAG_INCLUDE_SUBDIRECTORIES, - _countof(JSON_TAG_INCLUDE_SUBDIRECTORIES)) == 0 - ) - { - Attributes[key] = new bool{ Parser.ParseBooleanValue() }; - } - else if (_wcsnicmp(key.c_str(), JSON_TAG_PROVIDERS, _countof(JSON_TAG_PROVIDERS)) == 0) - { - if (Parser.GetNextDataType() != JsonFileParser::DataType::Array) - { - logWriter.TraceError(L"Error parsing configuration file. 'providers' attribute expected to be an array"); - Parser.SkipValue(); - continue; - } - - if (Parser.BeginParseArray()) - { - std::vector* providers = new std::vector(); - - // - // Get only the valid providers of this JSON object. - // - do - { - providers->emplace_back(); - if (!ReadETWProvider(Parser, providers->back())) - { - logWriter.TraceWarning(L"Error parsing configuration file. Discarded invalid provider (it must have a non-empty 'providerName' or 'providerGuid')."); - providers->pop_back(); - } - } while (Parser.ParseNextArrayElement()); - - Attributes[key] = providers; - } - } - else if (_wcsnicmp(key.c_str(), JSON_TAG_WAITINSECONDS, _countof(JSON_TAG_WAITINSECONDS)) == 0) - { - try - { - auto parsedValue = new std::double_t(Parser.ParseNumericValue()); - if (*parsedValue < 0) - { - logWriter.TraceError(L"Error parsing configuration file. 'waitInSeconds' attribute must be greater or equal to zero"); - success = false; - } - else - { - Attributes[key] = parsedValue; - } - } - catch(const std::exception& ex) - { - logWriter.TraceError( - Utility::FormatString(L"Error parsing configuration file atrribute 'waitInSeconds'. %S", ex.what()).c_str() - ); - success = false; - } - } - else - { - // - // Discard unwanted attributes - // - Parser.SkipValue(); - } - } while (Parser.ParseNextObjectElement()); - } - - bool isSourceFileValid = ValidateDirectoryAttributes(Attributes); - if (!isSourceFileValid) - { - success = false; - } - - return success; -} - -/// -/// Reads a single 'channel' object from the parser, and store it in the Result -/// -/// \param Parser A pre-initialized JSON parser. -/// \param Result Returns an EventLogChannel struct filled with the values specified in the config file. -/// -/// \return True if the channel is valid. Otherwise false -/// -bool -ReadLogChannel( - _In_ JsonFileParser& Parser, - _Out_ EventLogChannel& Result - ) -{ - if (Parser.GetNextDataType() != JsonFileParser::DataType::Object) - { - logWriter.TraceError(L"Error parsing configuration file. Channel item expected to be an object"); - Parser.SkipValue(); - return false; - } - - if (!Parser.BeginParseObject()) - { - logWriter.TraceError(L"Error parsing configuration file. Error reading channel object"); - return false; - } - - do - { - const auto& key = Parser.GetKey(); - - if (_wcsnicmp(key.c_str(), JSON_TAG_CHANNEL_NAME, _countof(JSON_TAG_CHANNEL_NAME)) == 0) - { - // - // Recover the name of the channel - // - Result.Name = Parser.ParseStringValue(); - } - else if (_wcsnicmp(key.c_str(), JSON_TAG_CHANNEL_LEVEL, _countof(JSON_TAG_CHANNEL_LEVEL)) == 0) - { - // - // Get the level as a string, and convert it to EventChannelLogLevel. - // - std::wstring logLevelStr = Parser.ParseStringValue(); - bool success = Result.SetLevelByString(logLevelStr); - - // - // Print an error message the string doesn't matches with a valid log level name. - // - if (!success) - { - logWriter.TraceWarning( - Utility::FormatString( - L"Error parsing configuration file. '%s' isn't a valid log level. Setting 'Error' level as default", logLevelStr.c_str() - ).c_str() - ); - } - } - else - { - // - // Discard unwanted attributes - // - Parser.SkipValue(); - } - } while (Parser.ParseNextObjectElement()); - - - return Result.IsValid(); -} - -/// -/// Reads a single 'provider' object from the parser, and return it in the Result param -/// -/// \param Parser A pre-initialized JSON parser. -/// \param Result Returns an ETWProvider struct filled with the values specified in the config file. -/// -/// \return True if the channel is valid. Otherwise false -/// -bool -ReadETWProvider( - _In_ JsonFileParser& Parser, - _Out_ ETWProvider& Result - ) -{ - if (Parser.GetNextDataType() != JsonFileParser::DataType::Object) - { - logWriter.TraceError(L"Error parsing configuration file. Provider item expected to be an object"); - Parser.SkipValue(); - return false; - } - - if (!Parser.BeginParseObject()) - { - logWriter.TraceError(L"Error parsing configuration file. Error reading provider object"); - return false; - } - - do - { - const auto& key = Parser.GetKey(); - - if (_wcsnicmp(key.c_str(), JSON_TAG_PROVIDER_NAME, _countof(JSON_TAG_PROVIDER_NAME)) == 0) - { - // - // Recover the name of the provider - // - Result.ProviderName = Parser.ParseStringValue(); - } - else if (_wcsnicmp(key.c_str(), JSON_TAG_PROVIDER_GUID, _countof(JSON_TAG_PROVIDER_GUID)) == 0) - { - // - // Recover the GUID of the provider. If the string isn't a valid - // GUID, ProviderGuidStr will be empty - // - Result.SetProviderGuid(Parser.ParseStringValue()); - } - else if (_wcsnicmp(key.c_str(), JSON_TAG_PROVIDER_LEVEL, _countof(JSON_TAG_PROVIDER_LEVEL)) == 0) - { - // - // Get the level as a string, and convert it to EventChannelLogLevel. - // - std::wstring logLevelStr = Parser.ParseStringValue(); - bool success = Result.StringToLevel(logLevelStr); - - // - // Print an error message the string doesn't matches with a valid log level name. - // - if (!success) - { - logWriter.TraceWarning( - Utility::FormatString( - L"Error parsing configuration file. '%s' isn't a valid log level. Setting 'Error' level as default", logLevelStr.c_str() - ).c_str() - ); - } - } - else if (_wcsnicmp(key.c_str(), JSON_TAG_KEYWORDS, _countof(JSON_TAG_KEYWORDS)) == 0) - { - // - // Recover the GUID of the provider - // - Result.Keywords = wcstoull(Parser.ParseStringValue().c_str(), NULL, 0); - } - else - { - // - // Discard unwanted attributes - // - Parser.SkipValue(); - } - } while (Parser.ParseNextObjectElement()); - - - return Result.IsValid(); -} - -/// -/// Converts the attributes to a valid Source and add it to the vector Sources -/// -/// \param Parser A pre-initialized JSON parser. -/// \param Attributes An AttributesMap that contains the attributes of the new source objet. -/// \param Sources A vector, where the new source is going to be inserted after been instantiated. -/// -/// \return True if Source was created and added successfully. Otherwise false -/// -bool -AddNewSource( - _In_ JsonFileParser& Parser, - _In_ AttributesMap& Attributes, - _Inout_ std::vector >& Sources - ) -{ - // - // Check the source has a type. - // - if (Attributes.find(JSON_TAG_TYPE) == Attributes.end() - || Attributes[JSON_TAG_TYPE] == nullptr) - { - return false; - } - - switch (*(LogSourceType*)Attributes[JSON_TAG_TYPE]) - { - case LogSourceType::EventLog: - { - std::shared_ptr sourceEventLog = std::make_shared< SourceEventLog>(); - - // - // Fill the new EventLog source object, with its attributes - // - if (!SourceEventLog::Unwrap(Attributes, *sourceEventLog)) - { - logWriter.TraceError(L"Error parsing configuration file. Invalid EventLog source (it must have a non-empty 'channels')"); - return false; - } - - Sources.push_back(std::reinterpret_pointer_cast(std::move(sourceEventLog))); - - break; - } - - case LogSourceType::File: - { - std::shared_ptr sourceFile = std::make_shared< SourceFile>(); - - // - // Fill the new File source object, with its attributes - // - if (!SourceFile::Unwrap(Attributes, *sourceFile)) - { - logWriter.TraceError(L"Error parsing configuration file. Invalid File source (it must have a non-empty 'directory')"); - return false; - } - - Sources.push_back(std::reinterpret_pointer_cast(std::move(sourceFile))); - - break; - } - - case LogSourceType::ETW: - { - std::shared_ptr sourceETW = std::make_shared< SourceETW>(); - - // - // Fill the new ETW source object, with its attributes - // - if (!SourceETW::Unwrap(Attributes, *sourceETW)) - { - logWriter.TraceError(L"Error parsing configuration file. Invalid ETW source (it must have a non-empty 'providers')"); - return false; - } - - Sources.push_back(std::reinterpret_pointer_cast(std::move(sourceETW))); - - break; - } - - case LogSourceType::Process: - { - std::shared_ptr sourceProcess = std::make_shared< SourceProcess>(); - - if (!SourceProcess::Unwrap(Attributes, *sourceProcess)) - { - logWriter.TraceError(L"Error parsing configuration file. Invalid Process source)"); - return false; - } - - Sources.push_back(std::reinterpret_pointer_cast(std::move(sourceProcess))); - - break; - } - } - return true; -} - -/// -/// Validates that when root directory is passed, includeSubdirectories is false -/// -/// \param Attributes An AttributesMap that contains the attributes of the new source objet. -/// \return false when root directory is passed, includeSubdirectories = true. Otherwise, true -bool ValidateDirectoryAttributes(_In_ AttributesMap &Attributes) -{ - if (!Utility::ConfigAttributeExists(Attributes, JSON_TAG_DIRECTORY) || - !Utility::ConfigAttributeExists(Attributes, JSON_TAG_INCLUDE_SUBDIRECTORIES)) - { - return true; - } - - std::wstring directory = *(std::wstring *)Attributes[JSON_TAG_DIRECTORY]; - const bool includeSubdirectories = *(bool *)Attributes[JSON_TAG_INCLUDE_SUBDIRECTORIES]; - - // Check if Log file monitor config is valid - const bool isValid = FileMonitorUtilities::IsValidSourceFile(directory, includeSubdirectories); - if (!isValid) - { - logWriter.TraceError( - Utility::FormatString( - L"LoggerSettings: Invalid Source File atrribute 'directory' (%s) and 'includeSubdirectories' (%s)." - L"'includeSubdirectories' attribute cannot be 'true' for the root directory", - directory.c_str(), includeSubdirectories ? L"true" : L"false") - .c_str()); - } - return isValid; -} - -/// -/// Debug function -/// -void _PrintSettings(_Out_ LoggerSettings& Config) -{ - std::wprintf(L"LogConfig:\n"); - std::wprintf(L"\tsources:\n"); - - for (auto source : Config.Sources) - { - switch (source->Type) - { - case LogSourceType::EventLog: - { - std::wprintf(L"\t\tType: EventLog\n"); - std::shared_ptr sourceEventLog = std::reinterpret_pointer_cast(source); - - std::wprintf(L"\t\teventFormatMultiLine: %ls\n", sourceEventLog->EventFormatMultiLine ? L"true" : L"false"); - std::wprintf(L"\t\tstartAtOldestRecord: %ls\n", sourceEventLog->StartAtOldestRecord ? L"true" : L"false"); - - std::wprintf(L"\t\tChannels (%d):\n", (int)sourceEventLog->Channels.size()); - for (auto channel : sourceEventLog->Channels) - { - std::wprintf(L"\t\t\tName: %ls\n", channel.Name.c_str()); - std::wprintf(L"\t\t\tLevel: %d\n", (int)channel.Level); - std::wprintf(L"\n"); - } - std::wprintf(L"\n"); - - break; - } - case LogSourceType::File: - { - std::wprintf(L"\t\tType: File\n"); - std::shared_ptr sourceFile = std::reinterpret_pointer_cast(source); - - std::wprintf(L"\t\tDirectory: %ls\n", sourceFile->Directory.c_str()); - std::wprintf(L"\t\tFilter: %ls\n", sourceFile->Filter.c_str()); - std::wprintf(L"\t\tIncludeSubdirectories: %ls\n", sourceFile->IncludeSubdirectories ? L"true" : L"false"); - std::wprintf(L"\t\twaitInSeconds: %d\n", int(sourceFile->WaitInSeconds)); - std::wprintf(L"\n"); - - break; - } - case LogSourceType::ETW: - { - std::wprintf(L"\t\tType: ETW\n"); - - std::shared_ptr sourceETW = std::reinterpret_pointer_cast(source); - - std::wprintf(L"\t\teventFormatMultiLine: %ls\n", sourceETW->EventFormatMultiLine ? L"true" : L"false"); - - std::wprintf(L"\t\tProviders (%d):\n", (int)sourceETW->Providers.size()); - for (auto provider : sourceETW->Providers) - { - std::wprintf(L"\t\t\tProviderName: %ls\n", provider.ProviderName.c_str()); - std::wprintf(L"\t\t\tProviderGuid: %ls\n", provider.ProviderGuidStr.c_str()); - std::wprintf(L"\t\t\tLevel: %d\n", (int)provider.Level); - std::wprintf(L"\t\t\tKeywords: %llx\n", provider.Keywords); - std::wprintf(L"\n"); - } - std::wprintf(L"\n"); - - break; - } - } // Switch - } -} diff --git a/LogMonitor/src/LogMonitor/JsonProcessor.cpp b/LogMonitor/src/LogMonitor/JsonProcessor.cpp new file mode 100644 index 00000000..4b249960 --- /dev/null +++ b/LogMonitor/src/LogMonitor/JsonProcessor.cpp @@ -0,0 +1,365 @@ +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// + +#include "pch.h" + +/// +/// Loads a JSON file, parses its contents, and processes its configuration settings. +/// +/// The path to the JSON file. +/// The LoggerSettings object to populate with the parsed configuration data. +/// Returns true if the JSON file is successfully loaded and processed; otherwise, +/// returns false on error. +bool loadAndProcessJson(_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"); + processLogConfig(logConfig, Config); + } + catch (const boost::json::system_error& e) { + // Handle JSON-specific parsing errors + logWriter.TraceError( + Utility::FormatString( + L"Error parsing JSON: %S", + e.what() + ).c_str() + ); + return false; + } + catch (const std::exception& e) { + // Handle other standard exceptions + logWriter.TraceError( + Utility::FormatString( + L"An unexpected error occurred: %S", + e.what() + ).c_str() + ); + return false; + } + catch (...) { + // Handle any unknown exceptions + logWriter.TraceError(L"An unknown error occurred."); + return false; + } + + if (parsedJson.is_null()) { + logWriter.TraceError(L"Parsed JSON is null."); + return false; + } + + return true; +} + +/// +/// Reads a JSON file from a given file path and returns its contents as a UTF-8 encoded string. +/// +/// The path to the JSON file. +/// A UTF-8 encoded string containing the JSON file's contents, +/// or an empty string if the file could not be opened. +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(); + wif.close(); + + // Convert the wstring buffer to a UTF-8 string + std::wstring_convert> converter; + std::string jsonString = converter.to_bytes(wss.str()); + + return jsonString; +} + +/// +/// Parses and processes configuration data for an EventLog log source, initializing a SourceEventLog object. +/// +/// JSON configuration data. +/// Map of attributes used to store the configuration details. +/// Vector of log sources. +void handleEventLog( + _In_ const boost::json::value& source, + _In_ AttributesMap& Attributes, + _Inout_ std::vector>& Sources +) { + 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(); + + Attributes[JSON_TAG_START_AT_OLDEST_RECORD] = reinterpret_cast( + std::make_unique(startAtOldest ? L"true" : L"false").release() + ); + + Attributes[JSON_TAG_FORMAT_MULTILINE] = reinterpret_cast( + std::make_unique(multiLine ? L"true" : L"false").release() + ); + + Attributes[JSON_TAG_CUSTOM_LOG_FORMAT] = reinterpret_cast( + std::make_unique(Utility::string_to_wstring(customLogFormat)).release() + ); + + // Process channels if they exist + if (source.as_object().contains("channels")) { + 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"); + + EventLogChannel eventChannel(Utility::string_to_wstring(name), EventChannelLogLevel::Error); + + // Set the level based on the levelString, logging an error if invalid + if (!eventChannel.SetLevelByString(Utility::string_to_wstring(levelString))) { + logWriter.TraceError( + Utility::FormatString( + L"Invalid level string: %S", + Utility::string_to_wstring(levelString).c_str() + ).c_str() + ); + } + + channels->push_back(eventChannel); // Add to the vector + } + + Attributes[JSON_TAG_CHANNELS] = reinterpret_cast(channels); + } + else { + Attributes[JSON_TAG_CHANNELS] = nullptr; + } + + auto sourceEventLog = std::make_shared(); + if (!SourceEventLog::Unwrap(Attributes, *sourceEventLog)) { + logWriter.TraceError(L"Error parsing configuration file. Invalid EventLog source"); + return; + } + + Sources.push_back(std::reinterpret_pointer_cast(std::move(sourceEventLog))); +} + +/// +/// Parses and processes configuration data specific to File type logs, initializing a SourceFile object. +/// +/// JSON value containing configuration data. +/// Map of attributes for storing configuration details. +/// Vector of log sources. +void handleFileLog( + _In_ const boost::json::value& 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"); + + Attributes[JSON_TAG_DIRECTORY] = reinterpret_cast( + std::make_unique(Utility::string_to_wstring(directory)).release() + ); + Attributes[JSON_TAG_FILTER] = reinterpret_cast( + std::make_unique(Utility::string_to_wstring(filter)).release() + ); + Attributes[JSON_TAG_INCLUDE_SUBDIRECTORIES] = reinterpret_cast( + std::make_unique(includeSubdirs ? L"true" : L"false").release() + ); + Attributes[JSON_TAG_CUSTOM_LOG_FORMAT] = reinterpret_cast( + std::make_unique(Utility::string_to_wstring(customLogFormat)).release() + ); + + auto sourceFile = std::make_shared(); + if (!SourceFile::Unwrap(Attributes, *sourceFile)) { + logWriter.TraceError(L"Error parsing configuration file. Invalid File source"); + return; + } + + Sources.push_back(std::reinterpret_pointer_cast(std::move(sourceFile))); +} + +/// +/// Parses and processes configuration details specific to ETW logs, initializing a SourceETW object. +/// +/// JSON value containing configuration data. +/// Map of attributes for storing configuration details. +/// Vector of log sources. +void handleETWLog( + _In_ const boost::json::value& 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(); + + // 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() + ); + Attributes[JSON_TAG_CUSTOM_LOG_FORMAT] = reinterpret_cast( + std::make_unique(Utility::string_to_wstring(customLogFormat)).release() + ); + + 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); + } + + etwProviders.push_back(etwProvider); + } + + // Store the ETW providers in Attributes. + Attributes[JSON_TAG_PROVIDERS] = reinterpret_cast( + std::make_unique>(std::move(etwProviders)).release() + ); + + auto sourceETW = std::make_shared(); + if (!SourceETW::Unwrap(Attributes, *sourceETW)) { + logWriter.TraceError( + L"Error parsing configuration file. " + L"Invalid ETW source (it must have a non-empty 'channels')" + ); + return; + } + + Sources.push_back(std::reinterpret_pointer_cast(std::move(sourceETW))); +} + +/// +/// Parses and processes configuration details specific to Process logs, initializing a SourceProcess object. +/// +/// JSON value with configuration data. +/// Map of attributes for storing configuration details. +/// Vector of log sources. +void handleProcessLog( + _In_ const boost::json::value& source, + _In_ AttributesMap& Attributes, + _Inout_ std::vector>& Sources +) { + std::string customLogFormat = source.at("customLogFormat").as_string().c_str(); + + Attributes[JSON_TAG_CUSTOM_LOG_FORMAT] = reinterpret_cast( + std::make_unique(Utility::string_to_wstring(customLogFormat)).release() + ); + + auto sourceProcess = std::make_shared(); + if (!SourceProcess::Unwrap(Attributes, *sourceProcess)) { + logWriter.TraceError(L"Error parsing configuration file. Invalid Process source"); + return; + } + + Sources.push_back(std::reinterpret_pointer_cast(std::move(sourceProcess))); +} + +/// +/// Processes the logging configuration from a JSON object, populating the LoggerSettings structure. +/// +/// JSON value containing logging configuration details. +/// LoggerSettings structure to populate with the parsed configuration. +void processLogConfig(const boost::json::value& logConfig, _Out_ LoggerSettings& Config) { + if (logConfig.is_object()) { + const boost::json::object& obj = logConfig.as_object(); + + // Check if the "logFormat" field exists in LogConfig + std::string logFormat = getJsonStringCaseInsensitive(obj, "logFormat"); + if (!logFormat.empty()) { + Config.LogFormat = Utility::string_to_wstring(logFormat); + } + + // Process the sources array + if (obj.contains("sources") && obj.at("sources").is_array()) { + const boost::json::array& sources = obj.at("sources").as_array(); + for (const auto& source : sources) { + if (source.is_object()) { + const boost::json::object& srcObj = source.as_object(); + + std::string sourceType = getJsonStringCaseInsensitive(srcObj, "type"); + + AttributesMap sourceAttributes; + + if (sourceType == "EventLog") { + handleEventLog(source, sourceAttributes, Config.Sources); + } else if (sourceType == "File") { + handleFileLog(source, sourceAttributes, Config.Sources); + } else if (sourceType == "ETW") { + handleETWLog(source, sourceAttributes, Config.Sources); + } else if (sourceType == "Process") { + handleProcessLog(source, sourceAttributes, Config.Sources); + } + + cleanupAttributes(sourceAttributes); + } + } + } + else { + logWriter.TraceError(L"Sources array not found or invalid in LogConfig."); + } + } + else { + logWriter.TraceError(L"Invalid LogConfig object."); + } +} + +/// +/// Cleans up dynamically allocated memory in the Attributes map by deleting each non-null attribute pointer. +/// +/// A map of attribute keys to pointers. +void cleanupAttributes(_In_ AttributesMap& Attributes) { + for (auto attributePair : Attributes) + { + if (attributePair.second != nullptr) + { + delete attributePair.second; + } + } +} + +/// +/// Retrieves a string value from a JSON object in a case-insensitive manner. +/// +/// 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()); + } + + logWriter.TraceError( + Utility::FormatString( + L"Key %S not found in JSON object", key.c_str() + ).c_str() + ); +} + diff --git a/LogMonitor/src/LogMonitor/JsonProcessor.h b/LogMonitor/src/LogMonitor/JsonProcessor.h new file mode 100644 index 00000000..a83fa550 --- /dev/null +++ b/LogMonitor/src/LogMonitor/JsonProcessor.h @@ -0,0 +1,53 @@ +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// + +#pragma once + +void handleEventLog( + _In_ const boost::json::value& source, + _In_ AttributesMap& Attributes, + _Inout_ std::vector>& Sources +); + +void handleFileLog( + _In_ const boost::json::value& source, + _In_ AttributesMap& Attributes, + _Inout_ std::vector>& Sources +); + +void handleETWLog( + _In_ const boost::json::value& source, + _In_ AttributesMap& Attributes, + _Inout_ std::vector>& Sources +); + +void handleProcessLog( + _In_ const boost::json::value& source, + _In_ AttributesMap& Attributes, + _Inout_ std::vector>& Sources +); + +bool loadAndProcessJson( + _In_ const PWCHAR jsonFile, + _Out_ LoggerSettings& Config +); + +std::string readJsonFromFile( + _In_ const std::wstring& filePath +); + +void processLogConfig( + const boost::json::value& logConfig, + _Out_ LoggerSettings& Config +); + +void cleanupAttributes( + _In_ AttributesMap& Attributes +); + +std::string getJsonStringCaseInsensitive( + _In_ const boost::json::object& obj, + _In_ const std::string& key +); diff --git a/LogMonitor/src/LogMonitor/LogMonitor.vcxproj b/LogMonitor/src/LogMonitor/LogMonitor.vcxproj index 92b3de94..5976a443 100644 --- a/LogMonitor/src/LogMonitor/LogMonitor.vcxproj +++ b/LogMonitor/src/LogMonitor/LogMonitor.vcxproj @@ -160,10 +160,9 @@ + - - @@ -172,11 +171,10 @@ - - + diff --git a/LogMonitor/src/LogMonitor/LogMonitor.vcxproj.filters b/LogMonitor/src/LogMonitor/LogMonitor.vcxproj.filters index 56eecf60..6a030137 100644 --- a/LogMonitor/src/LogMonitor/LogMonitor.vcxproj.filters +++ b/LogMonitor/src/LogMonitor/LogMonitor.vcxproj.filters @@ -36,12 +36,6 @@ Header Files - - Header Files - - - Header Files - Header Files @@ -54,14 +48,14 @@ Header Files + + Header Files + Source Files - - Source Files - Source Files @@ -80,10 +74,10 @@ Source Files - + Source Files - + Source Files diff --git a/LogMonitor/src/LogMonitor/Main.cpp b/LogMonitor/src/LogMonitor/Main.cpp index 436da509..24baf668 100644 --- a/LogMonitor/src/LogMonitor/Main.cpp +++ b/LogMonitor/src/LogMonitor/Main.cpp @@ -381,7 +381,7 @@ int __cdecl wmain(int argc, WCHAR *argv[]) LoggerSettings settings; //read the config file - bool configFileReadSuccess = OpenConfigFile(configFileName, settings); + bool configFileReadSuccess = loadAndProcessJson(configFileName, settings); //start the monitors if (configFileReadSuccess) diff --git a/LogMonitor/src/LogMonitor/Parser/LoggerSettings.h b/LogMonitor/src/LogMonitor/Parser/LoggerSettings.h index 2817da1a..45cb61e3 100644 --- a/LogMonitor/src/LogMonitor/Parser/LoggerSettings.h +++ b/LogMonitor/src/LogMonitor/Parser/LoggerSettings.h @@ -170,6 +170,12 @@ typedef struct _EventLogChannel std::wstring Name; EventChannelLogLevel Level = EventChannelLogLevel::Error; + _EventLogChannel() + : Name(L""), Level(EventChannelLogLevel::Error) {} + + _EventLogChannel(const std::wstring& name, EventChannelLogLevel level = EventChannelLogLevel::Error) + : Name(name), Level(level) {} + inline bool IsValid() { return !Name.empty(); diff --git a/LogMonitor/src/LogMonitor/Utility.cpp b/LogMonitor/src/LogMonitor/Utility.cpp index d9203e13..69b86dea 100644 --- a/LogMonitor/src/LogMonitor/Utility.cpp +++ b/LogMonitor/src/LogMonitor/Utility.cpp @@ -438,3 +438,22 @@ bool Utility::IsCustomJsonFormat(_Inout_ std::wstring& customLogFormat) return isCustomJSONFormat; } +/// +/// Function to convert wstring to string (UTF-8) +/// +/// +/// +std::string Utility::wstring_to_string(_In_ const std::wstring& wstr) { + std::wstring_convert> converter; + return converter.to_bytes(wstr); +} + +/// +/// Function to convert string to wstring (UTF-8) +/// +/// The input string to be converted +/// A wide string representation of the input string +std::wstring Utility::string_to_wstring(_In_ const std::string& str) { + std::wstring_convert> converter; + return converter.from_bytes(str); +} diff --git a/LogMonitor/src/LogMonitor/Utility.h b/LogMonitor/src/LogMonitor/Utility.h index 11d0d36f..37155671 100644 --- a/LogMonitor/src/LogMonitor/Utility.h +++ b/LogMonitor/src/LogMonitor/Utility.h @@ -90,4 +90,8 @@ class Utility final ); static bool IsCustomJsonFormat(_Inout_ std::wstring& customLogFormat); + + static std::string wstring_to_string(_In_ const std::wstring& wstr); + + static std::wstring string_to_wstring(_In_ const std::string& str); }; diff --git a/LogMonitor/src/LogMonitor/pch.h b/LogMonitor/src/LogMonitor/pch.h index 92483da6..9612d3c3 100644 --- a/LogMonitor/src/LogMonitor/pch.h +++ b/LogMonitor/src/LogMonitor/pch.h @@ -47,6 +47,7 @@ #include "shlwapi.h" #include #include +#include #include "Utility.h" #include "Parser/ConfigFileParser.h" #include "Parser/LoggerSettings.h" @@ -57,5 +58,6 @@ #include "FileMonitor/FileMonitorUtilities.h" #include "LogFileMonitor.h" #include "ProcessMonitor.h" +#include "JsonProcessor.h" #endif //PCH_H